--- a/jdk/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java Tue Aug 09 09:42:01 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java Tue Aug 09 10:00:31 2016 +0200
@@ -27,6 +27,7 @@
import jdk.internal.misc.Unsafe;
import jdk.internal.vm.annotation.ForceInline;
+import jdk.internal.vm.annotation.Stable;
import sun.invoke.util.ValueConversions;
import sun.invoke.util.VerifyAccess;
import sun.invoke.util.VerifyType;
@@ -190,14 +191,15 @@
boolean doesAlloc = (which == LF_NEWINVSPECIAL);
String linkerName, lambdaName;
switch (which) {
- case LF_INVVIRTUAL: linkerName = "linkToVirtual"; lambdaName = "DMH.invokeVirtual"; break;
- case LF_INVSTATIC: linkerName = "linkToStatic"; lambdaName = "DMH.invokeStatic"; break;
- case LF_INVSTATIC_INIT:linkerName = "linkToStatic"; lambdaName = "DMH.invokeStaticInit"; break;
- case LF_INVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.invokeSpecial"; break;
- case LF_INVINTERFACE: linkerName = "linkToInterface"; lambdaName = "DMH.invokeInterface"; break;
- case LF_NEWINVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.newInvokeSpecial"; break;
+ case LF_INVVIRTUAL: linkerName = "linkToVirtual"; lambdaName = "invokeVirtual"; break;
+ case LF_INVSTATIC: linkerName = "linkToStatic"; lambdaName = "invokeStatic"; break;
+ case LF_INVSTATIC_INIT:linkerName = "linkToStatic"; lambdaName = "invokeStaticInit"; break;
+ case LF_INVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "invokeSpecial"; break;
+ case LF_INVINTERFACE: linkerName = "linkToInterface"; lambdaName = "invokeInterface"; break;
+ case LF_NEWINVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "newInvokeSpecial"; break;
default: throw new InternalError("which="+which);
}
+
MethodType mtypeWithArg = mtype.appendParameterTypes(MemberName.class);
if (doesAlloc)
mtypeWithArg = mtypeWithArg
@@ -240,11 +242,26 @@
names[LINKER_CALL] = new Name(linker, outArgs);
lambdaName += "_" + shortenSignature(basicTypeSignature(mtype));
LambdaForm lform = new LambdaForm(lambdaName, ARG_LIMIT, names, result);
+
// This is a tricky bit of code. Don't send it through the LF interpreter.
- lform.compileToBytecode();
+ lform.compileToBytecode(Holder.class);
return lform;
}
+ /*
+ * NOTE: This method acts as an API hook for use by the
+ * GenerateJLIClassesPlugin to generate a class wrapping DirectMethodHandle
+ * methods for an array of method types.
+ */
+ static byte[] generateDMHClassBytes(String className, MethodType[] methodTypes, int[] types) {
+ LambdaForm[] forms = new LambdaForm[methodTypes.length];
+ for (int i = 0; i < forms.length; i++) {
+ forms[i] = makePreparedLambdaForm(methodTypes[i], types[i]);
+ methodTypes[i] = forms[i].methodType();
+ }
+ return InvokerBytecodeGenerator.generateCodeBytesForMultiple(className, forms, methodTypes);
+ }
+
static Object findDirectMethodHandle(Name name) {
if (name.function == NF_internalMemberName ||
name.function == NF_internalMemberNameEnsureInit ||
@@ -487,7 +504,7 @@
}
// Caching machinery for field accessors:
- private static byte
+ private static final byte
AF_GETFIELD = 0,
AF_PUTFIELD = 1,
AF_GETSTATIC = 2,
@@ -497,7 +514,7 @@
AF_LIMIT = 6;
// Enumerate the different field kinds using Wrapper,
// with an extra case added for checked references.
- private static int
+ private static final int
FT_LAST_WRAPPER = Wrapper.values().length-1,
FT_UNCHECKED_REF = Wrapper.OBJECT.ordinal(),
FT_CHECKED_REF = FT_LAST_WRAPPER+1,
@@ -507,6 +524,7 @@
+ (isVolatile ? FT_LIMIT : 0)
+ ftypeKind);
}
+ @Stable
private static final LambdaForm[] ACCESSOR_FORMS
= new LambdaForm[afIndex(AF_LIMIT, false, 0)];
private static int ftypeKind(Class<?> ftype) {
@@ -549,10 +567,11 @@
return lform;
}
private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatile, Class<?> ftype) {
- int afIndex = afIndex(formOp, isVolatile, ftypeKind(ftype));
+ int ftypeKind = ftypeKind(ftype);
+ int afIndex = afIndex(formOp, isVolatile, ftypeKind);
LambdaForm lform = ACCESSOR_FORMS[afIndex];
if (lform != null) return lform;
- lform = makePreparedFieldLambdaForm(formOp, isVolatile, ftypeKind(ftype));
+ lform = makePreparedFieldLambdaForm(formOp, isVolatile, ftypeKind);
ACCESSOR_FORMS[afIndex] = lform; // don't bother with a CAS
return lform;
}
@@ -682,4 +701,15 @@
throw newInternalError(ex);
}
}
+
+ static {
+ // The DMH class will contain pre-generated DirectMethodHandles resolved
+ // speculatively using MemberName.getFactory().resolveOrNull. However, that
+ // doesn't initialize the class, which subtly breaks inlining etc. By forcing
+ // initialization of the Holder class we avoid these issues.
+ UNSAFE.ensureClassInitialized(Holder.class);
+ }
+
+ /* Placeholder class for DirectMethodHandles generated ahead of time */
+ private final class Holder {}
}
--- a/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Tue Aug 09 09:42:01 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Tue Aug 09 10:00:31 2016 +0200
@@ -70,7 +70,7 @@
private static final String LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V";
/** Name of its super class*/
- private static final String superName = OBJ;
+ private static final String INVOKER_SUPER_NAME = OBJ;
/** Name of new class */
private final String className;
@@ -296,12 +296,15 @@
/**
* Set up class file generation.
*/
- private void classFilePrologue() {
+ private ClassWriter classFilePrologue() {
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
- cw.visit(Opcodes.V1_8, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, superName, null);
+ cw.visit(Opcodes.V1_8, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, INVOKER_SUPER_NAME, null);
cw.visitSource(sourceFile, null);
+ return cw;
+ }
+ private void methodPrologue() {
String invokerDesc = invokerType.toMethodDescriptorString();
mv = cw.visitMethod(Opcodes.ACC_STATIC, invokerName, invokerDesc, null, null);
}
@@ -309,7 +312,7 @@
/**
* Tear down class file generation.
*/
- private void classFileEpilogue() {
+ private void methodEpilogue() {
mv.visitMaxs(0, 0);
mv.visitEnd();
}
@@ -644,6 +647,44 @@
*/
private byte[] generateCustomizedCodeBytes() {
classFilePrologue();
+ addMethod();
+ bogusMethod(lambdaForm);
+
+ final byte[] classFile = toByteArray();
+ maybeDump(className, classFile);
+ return classFile;
+ }
+
+ /*
+ * NOTE: This is used from GenerateJLIClassesPlugin via
+ * DirectMethodHandle::generateDMHClassBytes.
+ *
+ * Generate customized code for a set of LambdaForms of specified types into
+ * a class with a specified name.
+ */
+ static byte[] generateCodeBytesForMultiple(String className,
+ LambdaForm[] forms, MethodType[] types) {
+ assert(forms.length == types.length);
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
+ cw.visit(Opcodes.V1_8, Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER,
+ className, null, INVOKER_SUPER_NAME, null);
+ cw.visitSource(className.substring(className.lastIndexOf('/') + 1), null);
+ for (int i = 0; i < forms.length; i++) {
+ InvokerBytecodeGenerator g
+ = new InvokerBytecodeGenerator(className, forms[i], types[i]);
+ g.setClassWriter(cw);
+ g.addMethod();
+ }
+ return cw.toByteArray();
+ }
+
+ private void setClassWriter(ClassWriter cw) {
+ this.cw = cw;
+ }
+
+ private void addMethod() {
+ methodPrologue();
// Suppress this method in backtraces displayed to the user.
mv.visitAnnotation(LF_HIDDEN_SIG, true);
@@ -748,19 +789,19 @@
// return statement
emitReturn(onStack);
- classFileEpilogue();
- bogusMethod(lambdaForm);
+ methodEpilogue();
+ }
- final byte[] classFile;
+ /*
+ * @throws BytecodeGenerationException if something goes wrong when
+ * generating the byte code
+ */
+ private byte[] toByteArray() {
try {
- classFile = cw.toByteArray();
+ return cw.toByteArray();
} catch (RuntimeException e) {
- // ASM throws RuntimeException if something goes wrong - capture these and wrap them in a meaningful
- // exception to support falling back to LambdaForm interpretation
throw new BytecodeGenerationException(e);
}
- maybeDump(className, classFile);
- return classFile;
}
@SuppressWarnings("serial")
@@ -1607,6 +1648,7 @@
private byte[] generateLambdaFormInterpreterEntryPointBytes() {
classFilePrologue();
+ methodPrologue();
// Suppress this method in backtraces displayed to the user.
mv.visitAnnotation(LF_HIDDEN_SIG, true);
@@ -1645,7 +1687,7 @@
// return statement
emitReturnInsn(basicType(rtype));
- classFileEpilogue();
+ methodEpilogue();
bogusMethod(invokerType);
final byte[] classFile = cw.toByteArray();
@@ -1666,6 +1708,7 @@
private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) {
MethodType dstType = typeForm.erasedType();
classFilePrologue();
+ methodPrologue();
// Suppress this method in backtraces displayed to the user.
mv.visitAnnotation(LF_HIDDEN_SIG, true);
@@ -1685,7 +1728,6 @@
// Maybe unbox
Class<?> dptype = dstType.parameterType(i);
if (dptype.isPrimitive()) {
- Class<?> sptype = dstType.basicType().wrap().parameterType(i);
Wrapper dstWrapper = Wrapper.forBasicType(dptype);
Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int
emitUnboxing(srcWrapper);
@@ -1713,7 +1755,7 @@
}
emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value.
- classFileEpilogue();
+ methodEpilogue();
bogusMethod(dstType);
final byte[] classFile = cw.toByteArray();
--- a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java Tue Aug 09 09:42:01 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java Tue Aug 09 10:00:31 2016 +0200
@@ -773,6 +773,26 @@
}
}
+ /**
+ * Generate optimizable bytecode for this form after first looking for a
+ * pregenerated version in a specified class.
+ */
+ void compileToBytecode(Class<?> lookupClass) {
+ if (vmentry != null && isCompiled) {
+ return; // already compiled somehow
+ }
+ MethodType invokerType = methodType();
+ assert(vmentry == null || vmentry.getMethodType().basicType().equals(invokerType));
+ MemberName member = new MemberName(lookupClass, debugName, invokerType, REF_invokeStatic);
+ MemberName resolvedMember = MemberName.getFactory().resolveOrNull(REF_invokeStatic, member, lookupClass);
+ if (resolvedMember != null) {
+ vmentry = resolvedMember;
+ isCompiled = true;
+ } else {
+ compileToBytecode();
+ }
+ }
+
private static void computeInitialPreparedForms() {
// Find all predefined invokers and associate them with canonical empty lambda forms.
for (MemberName m : MemberName.getFactory().getMethods(LambdaForm.class, false, null, null, null)) {
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java Tue Aug 09 09:42:01 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/GenerateJLIClassesPlugin.java Tue Aug 09 10:00:31 2016 +0200
@@ -24,9 +24,11 @@
*/
package jdk.tools.jlink.internal.plugins;
+import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.EnumSet;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -48,14 +50,26 @@
private static final String BMH_SPECIES_PARAM = "bmh-species";
+ private static final String DMH_PARAM = "dmh";
+
private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
private static final String BMH = "java/lang/invoke/BoundMethodHandle";
+ private static final Method BMH_FACTORY_METHOD;
- private static final Method FACTORY_METHOD;
+ private static final String DMH = "java/lang/invoke/DirectMethodHandle$Holder";
+ private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
+ private static final String DMH_INVOKE_STATIC = "invokeStatic";
+ private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
+ private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
+ private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
+ private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
+ private static final Method DMH_FACTORY_METHOD;
List<String> speciesTypes;
+ Map<String, List<String>> dmhMethods;
+
public GenerateJLIClassesPlugin() {
}
@@ -87,11 +101,9 @@
/**
* @return the default Species forms to generate.
*
- * This list was derived from running a Java concatenating strings
- * with -Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT set
- * plus a subset of octane. A better long-term solution is to define
- * and run a set of quick generators and extracting this list as a
- * step in the build process.
+ * This list was derived from running a small startup benchmark.
+ * A better long-term solution is to define and run a set of quick
+ * generators and extracting this list as a step in the build process.
*/
public static List<String> defaultSpecies() {
return List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I",
@@ -100,18 +112,51 @@
"LILL", "I", "LLILL");
}
+ /**
+ * @return the list of default DirectMethodHandle methods to generate.
+ */
+ public static Map<String, List<String>> defaultDMHMethods() {
+ return Map.of(
+ DMH_INVOKE_VIRTUAL, List.of("_L", "L_L", "LI_I"),
+ DMH_INVOKE_SPECIAL, List.of("L_I", "L_L", "LF_L", "LD_L", "LL_L",
+ "L3_L", "L4_L", "L5_L", "L6_L", "L7_L", "LI_I", "LI_L", "LIL_I",
+ "LII_I", "LII_L", "LLI_L", "LLI_I", "LILI_I", "LIIL_L",
+ "LIILL_L", "LIILL_I", "LIIL_I", "LILIL_I", "LILILL_I",
+ "LILII_I", "LI3_I", "LI3L_I", "LI3LL_I", "LI3_L", "LI4_I"),
+ DMH_INVOKE_STATIC, List.of("II_I", "IL_I", "ILIL_I", "ILII_I",
+ "_I", "_L", "_V", "D_L", "F_L", "I_I", "II_L", "LI_L",
+ "L_V", "L_L", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L",
+ "L7_L", "L8_L", "L9_L", "L9I_L", "L9II_L", "L9IIL_L",
+ "L10_L", "L11_L", "L12_L", "L13_L", "L13I_L", "L13II_L")
+ );
+ }
+
+ // Map from DirectMethodHandle method type to internal ID
+ private static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
+ Map.of(
+ DMH_INVOKE_VIRTUAL, 0,
+ DMH_INVOKE_STATIC, 1,
+ DMH_INVOKE_SPECIAL, 2,
+ DMH_NEW_INVOKE_SPECIAL, 3,
+ DMH_INVOKE_INTERFACE, 4,
+ DMH_INVOKE_STATIC_INIT, 5
+ );
+
@Override
public void configure(Map<String, String> config) {
String mainArgument = config.get(NAME);
// Enable by default
boolean bmhEnabled = true;
+ boolean dmhEnabled = true;
if (mainArgument != null) {
- Set<String> args = Arrays.stream(mainArgument.split(","))
- .collect(Collectors.toSet());
+ List<String> args = Arrays.asList(mainArgument.split(","));
if (!args.contains(BMH_PARAM)) {
bmhEnabled = false;
}
+ if (!args.contains(DMH_PARAM)) {
+ dmhEnabled = false;
+ }
}
if (!bmhEnabled) {
@@ -132,40 +177,63 @@
speciesTypes = bmhSpecies.stream()
.map(type -> expandSignature(type))
.collect(Collectors.toList());
+ }
- // Validation check
- for (String type : speciesTypes) {
- for (char c : type.toCharArray()) {
- if ("LIJFD".indexOf(c) < 0) {
- throw new PluginException("All characters must "
- + "correspond to a basic field type: LIJFD");
+ // DirectMethodHandles
+ if (!dmhEnabled) {
+ dmhMethods = Map.of();
+ } else {
+ dmhMethods = new HashMap<>();
+ for (String dmhParam : DMH_METHOD_TYPE_MAP.keySet()) {
+ String args = config.get(dmhParam);
+ if (args != null && !args.isEmpty()) {
+ List<String> dmhMethodTypes = Arrays.stream(args.split(","))
+ .map(String::trim)
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toList());
+ dmhMethods.put(dmhParam, dmhMethodTypes);
+ // Validation check
+ for (String type : dmhMethodTypes) {
+ String[] typeParts = type.split("_");
+ // check return type (second part)
+ if (typeParts.length != 2 || typeParts[1].length() != 1
+ || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
+ throw new PluginException(
+ "Method type signature must be of form [LJIFD]*_[LJIFDV]");
+ }
+ // expand and check arguments (first part)
+ expandSignature(typeParts[0]);
}
}
}
+ if (dmhMethods.isEmpty()) {
+ dmhMethods = defaultDMHMethods();
+ }
+ }
+ }
+
+ private static void requireBasicType(char c) {
+ if ("LIJFD".indexOf(c) < 0) {
+ throw new PluginException(
+ "Character " + c + " must correspond to a basic field type: LIJFD");
}
}
@Override
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
- in.entries().forEach(data -> {
- if (("/java.base/" + BMH + ".class").equals(data.path())) {
- // Add BoundMethodHandle unchanged
- out.add(data);
- speciesTypes.forEach(types -> generateConcreteClass(types, data, out));
- } else {
- out.add(data);
- }
- });
-
+ // Copy all but DMH_ENTRY to out
+ in.transformAndCopy(entry -> entry.path().equals(DMH_ENTRY) ? null : entry, out);
+ speciesTypes.forEach(types -> generateBMHClass(types, out));
+ generateDMHClass(out);
return out.build();
}
@SuppressWarnings("unchecked")
- private void generateConcreteClass(String types, ResourcePoolEntry data, ResourcePoolBuilder out) {
+ private void generateBMHClass(String types, ResourcePoolBuilder out) {
try {
// Generate class
Map.Entry<String, byte[]> result = (Map.Entry<String, byte[]>)
- FACTORY_METHOD.invoke(null, types);
+ BMH_FACTORY_METHOD.invoke(null, types);
String className = result.getKey();
byte[] bytes = result.getValue();
@@ -179,13 +247,47 @@
}
}
+ private void generateDMHClass(ResourcePoolBuilder out) {
+ int count = 0;
+ for (List<String> entry : dmhMethods.values()) {
+ count += entry.size();
+ }
+ MethodType[] methodTypes = new MethodType[count];
+ int[] dmhTypes = new int[count];
+ int index = 0;
+ for (Map.Entry<String, List<String>> entry : dmhMethods.entrySet()) {
+ String dmhType = entry.getKey();
+ for (String type : entry.getValue()) {
+ methodTypes[index] = asMethodType(type);
+ dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType);
+ index++;
+ }
+ }
+ try {
+ byte[] bytes = (byte[])DMH_FACTORY_METHOD
+ .invoke(null,
+ DMH,
+ methodTypes,
+ dmhTypes);
+ ResourcePoolEntry ndata = ResourcePoolEntry.create(DMH_ENTRY, bytes);
+ out.add(ndata);
+ } catch (Exception ex) {
+ throw new PluginException(ex);
+ }
+ }
+ private static final String DMH_ENTRY = "/java.base/" + DMH + ".class";
+
static {
try {
Class<?> BMHFactory = Class.forName("java.lang.invoke.BoundMethodHandle$Factory");
- Method genClassMethod = BMHFactory.getDeclaredMethod("generateConcreteBMHClassBytes",
+ BMH_FACTORY_METHOD = BMHFactory.getDeclaredMethod("generateConcreteBMHClassBytes",
String.class);
- genClassMethod.setAccessible(true);
- FACTORY_METHOD = genClassMethod;
+ BMH_FACTORY_METHOD.setAccessible(true);
+
+ Class<?> DMHFactory = Class.forName("java.lang.invoke.DirectMethodHandle");
+ DMH_FACTORY_METHOD = DMHFactory.getDeclaredMethod("generateDMHClassBytes",
+ String.class, MethodType[].class, int[].class);
+ DMH_FACTORY_METHOD.setAccessible(true);
} catch (Exception e) {
throw new PluginException(e);
}
@@ -202,6 +304,7 @@
count *= 10;
count += (c - '0');
} else {
+ requireBasicType(c);
for (int j = 1; j < count; j++) {
sb.append(last);
}
@@ -210,9 +313,52 @@
count = 0;
}
}
- for (int j = 1; j < count; j++) {
- sb.append(last);
+
+ // ended with a number, e.g., "L2": append last char count - 1 times
+ if (count > 1) {
+ requireBasicType(last);
+ for (int j = 1; j < count; j++) {
+ sb.append(last);
+ }
}
return sb.toString();
}
+
+ private static MethodType asMethodType(String basicSignatureString) {
+ String[] parts = basicSignatureString.split("_");
+ assert(parts.length == 2);
+ assert(parts[1].length() == 1);
+ String parameters = expandSignature(parts[0]);
+ Class<?> rtype = primitiveType(parts[1].charAt(0));
+ Class<?>[] ptypes = new Class<?>[parameters.length()];
+ for (int i = 0; i < ptypes.length; i++) {
+ ptypes[i] = primitiveType(parameters.charAt(i));
+ }
+ return MethodType.methodType(rtype, ptypes);
+ }
+
+ private static Class<?> primitiveType(char c) {
+ switch (c) {
+ case 'F':
+ return float.class;
+ case 'D':
+ return double.class;
+ case 'I':
+ return int.class;
+ case 'L':
+ return Object.class;
+ case 'J':
+ return long.class;
+ case 'V':
+ return void.class;
+ case 'Z':
+ case 'B':
+ case 'S':
+ case 'C':
+ throw new IllegalArgumentException("Not a valid primitive: " + c +
+ " (use I instead)");
+ default:
+ throw new IllegalArgumentException("Not a primitive: " + c);
+ }
+ }
}