33 |
33 |
34 public class ValueConversions { |
34 public class ValueConversions { |
35 private static final Class<?> THIS_CLASS = ValueConversions.class; |
35 private static final Class<?> THIS_CLASS = ValueConversions.class; |
36 private static final Lookup IMPL_LOOKUP = MethodHandles.lookup(); |
36 private static final Lookup IMPL_LOOKUP = MethodHandles.lookup(); |
37 |
37 |
38 private static EnumMap<Wrapper, MethodHandle>[] newWrapperCaches(int n) { |
38 /** Thread-safe canonicalized mapping from Wrapper to MethodHandle |
39 @SuppressWarnings("unchecked") // generic array creation |
39 * with unsynchronized reads and synchronized writes. |
40 EnumMap<Wrapper, MethodHandle>[] caches |
40 * It's safe to publish MethodHandles by data race because they are immutable. */ |
41 = (EnumMap<Wrapper, MethodHandle>[]) new EnumMap<?,?>[n]; |
41 private static class WrapperCache { |
|
42 /** EnumMap uses preconstructed array internally, which is constant during it's lifetime. */ |
|
43 private final EnumMap<Wrapper, MethodHandle> map = new EnumMap<>(Wrapper.class); |
|
44 |
|
45 public MethodHandle get(Wrapper w) { |
|
46 return map.get(w); |
|
47 } |
|
48 public synchronized MethodHandle put(final Wrapper w, final MethodHandle mh) { |
|
49 // Simulate CAS to avoid racy duplication |
|
50 MethodHandle prev = map.putIfAbsent(w, mh); |
|
51 if (prev != null) return prev; |
|
52 return mh; |
|
53 } |
|
54 } |
|
55 |
|
56 private static WrapperCache[] newWrapperCaches(int n) { |
|
57 WrapperCache[] caches = new WrapperCache[n]; |
42 for (int i = 0; i < n; i++) |
58 for (int i = 0; i < n; i++) |
43 caches[i] = new EnumMap<>(Wrapper.class); |
59 caches[i] = new WrapperCache(); |
44 return caches; |
60 return caches; |
45 } |
61 } |
46 |
62 |
47 /// Converting references to values. |
63 /// Converting references to values. |
48 |
64 |
49 // There are several levels of this unboxing conversions: |
65 // There are several levels of this unboxing conversions: |
50 // no conversions: exactly Integer.valueOf, etc. |
66 // no conversions: exactly Integer.valueOf, etc. |
51 // implicit conversions sanctioned by JLS 5.1.2, etc. |
67 // implicit conversions sanctioned by JLS 5.1.2, etc. |
52 // explicit conversions as allowed by explicitCastArguments |
68 // explicit conversions as allowed by explicitCastArguments |
53 |
69 |
|
70 static int unboxInteger(Integer x) { |
|
71 return x; |
|
72 } |
54 static int unboxInteger(Object x, boolean cast) { |
73 static int unboxInteger(Object x, boolean cast) { |
55 if (x instanceof Integer) |
74 if (x instanceof Integer) |
56 return ((Integer) x).intValue(); |
75 return (Integer) x; |
57 return primitiveConversion(Wrapper.INT, x, cast).intValue(); |
76 return primitiveConversion(Wrapper.INT, x, cast).intValue(); |
58 } |
77 } |
59 |
78 |
|
79 static byte unboxByte(Byte x) { |
|
80 return x; |
|
81 } |
60 static byte unboxByte(Object x, boolean cast) { |
82 static byte unboxByte(Object x, boolean cast) { |
61 if (x instanceof Byte) |
83 if (x instanceof Byte) |
62 return ((Byte) x).byteValue(); |
84 return (Byte) x; |
63 return primitiveConversion(Wrapper.BYTE, x, cast).byteValue(); |
85 return primitiveConversion(Wrapper.BYTE, x, cast).byteValue(); |
64 } |
86 } |
65 |
87 |
|
88 static short unboxShort(Short x) { |
|
89 return x; |
|
90 } |
66 static short unboxShort(Object x, boolean cast) { |
91 static short unboxShort(Object x, boolean cast) { |
67 if (x instanceof Short) |
92 if (x instanceof Short) |
68 return ((Short) x).shortValue(); |
93 return (Short) x; |
69 return primitiveConversion(Wrapper.SHORT, x, cast).shortValue(); |
94 return primitiveConversion(Wrapper.SHORT, x, cast).shortValue(); |
70 } |
95 } |
71 |
96 |
|
97 static boolean unboxBoolean(Boolean x) { |
|
98 return x; |
|
99 } |
72 static boolean unboxBoolean(Object x, boolean cast) { |
100 static boolean unboxBoolean(Object x, boolean cast) { |
73 if (x instanceof Boolean) |
101 if (x instanceof Boolean) |
74 return ((Boolean) x).booleanValue(); |
102 return (Boolean) x; |
75 return (primitiveConversion(Wrapper.BOOLEAN, x, cast).intValue() & 1) != 0; |
103 return (primitiveConversion(Wrapper.BOOLEAN, x, cast).intValue() & 1) != 0; |
76 } |
104 } |
77 |
105 |
|
106 static char unboxCharacter(Character x) { |
|
107 return x; |
|
108 } |
78 static char unboxCharacter(Object x, boolean cast) { |
109 static char unboxCharacter(Object x, boolean cast) { |
79 if (x instanceof Character) |
110 if (x instanceof Character) |
80 return ((Character) x).charValue(); |
111 return (Character) x; |
81 return (char) primitiveConversion(Wrapper.CHAR, x, cast).intValue(); |
112 return (char) primitiveConversion(Wrapper.CHAR, x, cast).intValue(); |
82 } |
113 } |
83 |
114 |
|
115 static long unboxLong(Long x) { |
|
116 return x; |
|
117 } |
84 static long unboxLong(Object x, boolean cast) { |
118 static long unboxLong(Object x, boolean cast) { |
85 if (x instanceof Long) |
119 if (x instanceof Long) |
86 return ((Long) x).longValue(); |
120 return (Long) x; |
87 return primitiveConversion(Wrapper.LONG, x, cast).longValue(); |
121 return primitiveConversion(Wrapper.LONG, x, cast).longValue(); |
88 } |
122 } |
89 |
123 |
|
124 static float unboxFloat(Float x) { |
|
125 return x; |
|
126 } |
90 static float unboxFloat(Object x, boolean cast) { |
127 static float unboxFloat(Object x, boolean cast) { |
91 if (x instanceof Float) |
128 if (x instanceof Float) |
92 return ((Float) x).floatValue(); |
129 return (Float) x; |
93 return primitiveConversion(Wrapper.FLOAT, x, cast).floatValue(); |
130 return primitiveConversion(Wrapper.FLOAT, x, cast).floatValue(); |
94 } |
131 } |
95 |
132 |
|
133 static double unboxDouble(Double x) { |
|
134 return x; |
|
135 } |
96 static double unboxDouble(Object x, boolean cast) { |
136 static double unboxDouble(Object x, boolean cast) { |
97 if (x instanceof Double) |
137 if (x instanceof Double) |
98 return ((Double) x).doubleValue(); |
138 return (Double) x; |
99 return primitiveConversion(Wrapper.DOUBLE, x, cast).doubleValue(); |
139 return primitiveConversion(Wrapper.DOUBLE, x, cast).doubleValue(); |
100 } |
140 } |
101 |
141 |
102 private static MethodType unboxType(Wrapper wrap) { |
142 private static MethodType unboxType(Wrapper wrap, int kind) { |
|
143 if (kind == 0) |
|
144 return MethodType.methodType(wrap.primitiveType(), wrap.wrapperType()); |
103 return MethodType.methodType(wrap.primitiveType(), Object.class, boolean.class); |
145 return MethodType.methodType(wrap.primitiveType(), Object.class, boolean.class); |
104 } |
146 } |
105 |
147 |
106 private static final EnumMap<Wrapper, MethodHandle>[] |
148 private static final WrapperCache[] UNBOX_CONVERSIONS = newWrapperCaches(4); |
107 UNBOX_CONVERSIONS = newWrapperCaches(2); |
149 |
108 |
150 private static MethodHandle unbox(Wrapper wrap, int kind) { |
109 private static MethodHandle unbox(Wrapper wrap, boolean cast) { |
151 // kind 0 -> strongly typed with NPE |
110 EnumMap<Wrapper, MethodHandle> cache = UNBOX_CONVERSIONS[(cast?1:0)]; |
152 // kind 1 -> strongly typed but zero for null, |
|
153 // kind 2 -> asType rules: accept multiple box types but only widening conversions with NPE |
|
154 // kind 3 -> explicitCastArguments rules: allow narrowing conversions, zero for null |
|
155 WrapperCache cache = UNBOX_CONVERSIONS[kind]; |
111 MethodHandle mh = cache.get(wrap); |
156 MethodHandle mh = cache.get(wrap); |
112 if (mh != null) { |
157 if (mh != null) { |
113 return mh; |
158 return mh; |
114 } |
159 } |
115 // slow path |
160 // slow path |
116 switch (wrap) { |
161 switch (wrap) { |
117 case OBJECT: |
162 case OBJECT: |
118 mh = IDENTITY; break; |
|
119 case VOID: |
163 case VOID: |
120 mh = IGNORE; break; |
164 throw new IllegalArgumentException("unbox "+wrap); |
121 } |
|
122 if (mh != null) { |
|
123 cache.put(wrap, mh); |
|
124 return mh; |
|
125 } |
165 } |
126 // look up the method |
166 // look up the method |
127 String name = "unbox" + wrap.wrapperSimpleName(); |
167 String name = "unbox" + wrap.wrapperSimpleName(); |
128 MethodType type = unboxType(wrap); |
168 MethodType type = unboxType(wrap, kind); |
129 try { |
169 try { |
130 mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); |
170 mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); |
131 } catch (ReflectiveOperationException ex) { |
171 } catch (ReflectiveOperationException ex) { |
132 mh = null; |
172 mh = null; |
133 } |
173 } |
134 if (mh != null) { |
174 if (mh != null) { |
135 mh = MethodHandles.insertArguments(mh, 1, cast); |
175 if (kind > 0) { |
136 cache.put(wrap, mh); |
176 boolean cast = (kind != 2); |
137 return mh; |
177 mh = MethodHandles.insertArguments(mh, 1, cast); |
|
178 } |
|
179 if (kind == 1) { // casting but exact (null -> zero) |
|
180 mh = mh.asType(unboxType(wrap, 0)); |
|
181 } |
|
182 return cache.put(wrap, mh); |
138 } |
183 } |
139 throw new IllegalArgumentException("cannot find unbox adapter for " + wrap |
184 throw new IllegalArgumentException("cannot find unbox adapter for " + wrap |
140 + (cast ? " (cast)" : "")); |
185 + (kind <= 1 ? " (exact)" : kind == 3 ? " (cast)" : "")); |
141 } |
186 } |
142 |
187 |
|
188 /** Return an exact unboxer for the given primitive type. */ |
|
189 public static MethodHandle unboxExact(Wrapper type) { |
|
190 return unbox(type, 0); |
|
191 } |
|
192 |
|
193 /** Return an exact unboxer for the given primitive type, with optional null-to-zero conversion. |
|
194 * The boolean says whether to throw an NPE on a null value (false means unbox a zero). |
|
195 * The type of the unboxer is of a form like (Integer)int. |
|
196 */ |
|
197 public static MethodHandle unboxExact(Wrapper type, boolean throwNPE) { |
|
198 return unbox(type, throwNPE ? 0 : 1); |
|
199 } |
|
200 |
|
201 /** Return a widening unboxer for the given primitive type. |
|
202 * Widen narrower primitive boxes to the given type. |
|
203 * Do not narrow any primitive values or convert null to zero. |
|
204 * The type of the unboxer is of a form like (Object)int. |
|
205 */ |
|
206 public static MethodHandle unboxWiden(Wrapper type) { |
|
207 return unbox(type, 2); |
|
208 } |
|
209 |
|
210 /** Return a casting unboxer for the given primitive type. |
|
211 * Widen or narrow primitive values to the given type, or convert null to zero, as needed. |
|
212 * The type of the unboxer is of a form like (Object)int. |
|
213 */ |
143 public static MethodHandle unboxCast(Wrapper type) { |
214 public static MethodHandle unboxCast(Wrapper type) { |
144 return unbox(type, true); |
215 return unbox(type, 3); |
145 } |
|
146 |
|
147 public static MethodHandle unbox(Class<?> type) { |
|
148 return unbox(Wrapper.forPrimitiveType(type), false); |
|
149 } |
|
150 |
|
151 public static MethodHandle unboxCast(Class<?> type) { |
|
152 return unbox(Wrapper.forPrimitiveType(type), true); |
|
153 } |
216 } |
154 |
217 |
155 static private final Integer ZERO_INT = 0, ONE_INT = 1; |
218 static private final Integer ZERO_INT = 0, ONE_INT = 1; |
156 |
219 |
157 /// Primitive conversions |
220 /// Primitive conversions |
244 // be exact, since return casts are hard to compose |
307 // be exact, since return casts are hard to compose |
245 Class<?> boxType = wrap.wrapperType(); |
308 Class<?> boxType = wrap.wrapperType(); |
246 return MethodType.methodType(boxType, wrap.primitiveType()); |
309 return MethodType.methodType(boxType, wrap.primitiveType()); |
247 } |
310 } |
248 |
311 |
249 private static final EnumMap<Wrapper, MethodHandle>[] |
312 private static final WrapperCache[] BOX_CONVERSIONS = newWrapperCaches(1); |
250 BOX_CONVERSIONS = newWrapperCaches(2); |
313 |
251 |
314 public static MethodHandle boxExact(Wrapper wrap) { |
252 private static MethodHandle box(Wrapper wrap, boolean exact) { |
315 WrapperCache cache = BOX_CONVERSIONS[0]; |
253 EnumMap<Wrapper, MethodHandle> cache = BOX_CONVERSIONS[(exact?1:0)]; |
|
254 MethodHandle mh = cache.get(wrap); |
316 MethodHandle mh = cache.get(wrap); |
255 if (mh != null) { |
317 if (mh != null) { |
256 return mh; |
|
257 } |
|
258 // slow path |
|
259 switch (wrap) { |
|
260 case OBJECT: |
|
261 mh = IDENTITY; break; |
|
262 case VOID: |
|
263 mh = ZERO_OBJECT; |
|
264 break; |
|
265 } |
|
266 if (mh != null) { |
|
267 cache.put(wrap, mh); |
|
268 return mh; |
318 return mh; |
269 } |
319 } |
270 // look up the method |
320 // look up the method |
271 String name = "box" + wrap.wrapperSimpleName(); |
321 String name = "box" + wrap.wrapperSimpleName(); |
272 MethodType type = boxType(wrap); |
322 MethodType type = boxType(wrap); |
273 if (exact) { |
323 try { |
274 try { |
324 mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); |
275 mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); |
325 } catch (ReflectiveOperationException ex) { |
276 } catch (ReflectiveOperationException ex) { |
326 mh = null; |
277 mh = null; |
327 } |
278 } |
328 if (mh != null) { |
279 } else { |
329 return cache.put(wrap, mh); |
280 mh = box(wrap, !exact).asType(type.erase()); |
330 } |
281 } |
331 throw new IllegalArgumentException("cannot find box adapter for " + wrap); |
282 if (mh != null) { |
|
283 cache.put(wrap, mh); |
|
284 return mh; |
|
285 } |
|
286 throw new IllegalArgumentException("cannot find box adapter for " |
|
287 + wrap + (exact ? " (exact)" : "")); |
|
288 } |
|
289 |
|
290 public static MethodHandle box(Class<?> type) { |
|
291 boolean exact = false; |
|
292 // e.g., boxShort(short)Short if exact, |
|
293 // e.g., boxShort(short)Object if !exact |
|
294 return box(Wrapper.forPrimitiveType(type), exact); |
|
295 } |
|
296 |
|
297 public static MethodHandle box(Wrapper type) { |
|
298 boolean exact = false; |
|
299 return box(type, exact); |
|
300 } |
332 } |
301 |
333 |
302 /// Constant functions |
334 /// Constant functions |
303 |
335 |
304 static void ignore(Object x) { |
336 static void ignore(Object x) { |
421 |
450 |
422 static double identity(double x) { |
451 static double identity(double x) { |
423 return x; |
452 return x; |
424 } |
453 } |
425 |
454 |
426 private static final MethodHandle IDENTITY, CAST_REFERENCE, ZERO_OBJECT, IGNORE, EMPTY; |
455 private static final MethodHandle IDENTITY, CAST_REFERENCE, IGNORE, EMPTY; |
427 static { |
456 static { |
428 try { |
457 try { |
429 MethodType idType = MethodType.genericMethodType(1); |
458 MethodType idType = MethodType.genericMethodType(1); |
430 MethodType ignoreType = idType.changeReturnType(void.class); |
459 MethodType ignoreType = idType.changeReturnType(void.class); |
431 MethodType zeroObjectType = MethodType.genericMethodType(0); |
|
432 IDENTITY = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", idType); |
460 IDENTITY = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", idType); |
433 CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType); |
461 CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType); |
434 ZERO_OBJECT = IMPL_LOOKUP.findStatic(THIS_CLASS, "zeroObject", zeroObjectType); |
|
435 IGNORE = IMPL_LOOKUP.findStatic(THIS_CLASS, "ignore", ignoreType); |
462 IGNORE = IMPL_LOOKUP.findStatic(THIS_CLASS, "ignore", ignoreType); |
436 EMPTY = IMPL_LOOKUP.findStatic(THIS_CLASS, "empty", ignoreType.dropParameterTypes(0, 1)); |
463 EMPTY = IMPL_LOOKUP.findStatic(THIS_CLASS, "empty", ignoreType.dropParameterTypes(0, 1)); |
437 } catch (NoSuchMethodException | IllegalAccessException ex) { |
464 } catch (NoSuchMethodException | IllegalAccessException ex) { |
438 throw newInternalError("uncaught exception", ex); |
465 throw newInternalError("uncaught exception", ex); |
439 } |
466 } |
440 } |
467 } |
441 |
468 |
442 private static final EnumMap<Wrapper, MethodHandle>[] WRAPPER_CASTS |
469 public static MethodHandle ignore() { |
443 = newWrapperCaches(1); |
470 return IGNORE; |
444 |
|
445 /** Return a method that casts its sole argument (an Object) to the given type |
|
446 * and returns it as the given type. |
|
447 */ |
|
448 public static MethodHandle cast(Class<?> type) { |
|
449 return cast(type, CAST_REFERENCE); |
|
450 } |
|
451 public static MethodHandle cast(Class<?> type, MethodHandle castReference) { |
|
452 if (type.isPrimitive()) throw new IllegalArgumentException("cannot cast primitive type "+type); |
|
453 MethodHandle mh; |
|
454 Wrapper wrap = null; |
|
455 EnumMap<Wrapper, MethodHandle> cache = null; |
|
456 if (Wrapper.isWrapperType(type)) { |
|
457 wrap = Wrapper.forWrapperType(type); |
|
458 cache = WRAPPER_CASTS[0]; |
|
459 mh = cache.get(wrap); |
|
460 if (mh != null) return mh; |
|
461 } |
|
462 mh = MethodHandles.insertArguments(castReference, 0, type); |
|
463 if (cache != null) |
|
464 cache.put(wrap, mh); |
|
465 return mh; |
|
466 } |
471 } |
467 |
472 |
468 public static MethodHandle identity() { |
473 public static MethodHandle identity() { |
469 return IDENTITY; |
474 return IDENTITY; |
470 } |
475 } |
710 static byte fromBoolean(boolean x) { |
714 static byte fromBoolean(boolean x) { |
711 // see javadoc for MethodHandles.explicitCastArguments |
715 // see javadoc for MethodHandles.explicitCastArguments |
712 return (x ? (byte)1 : (byte)0); |
716 return (x ? (byte)1 : (byte)0); |
713 } |
717 } |
714 |
718 |
715 private static final EnumMap<Wrapper, MethodHandle>[] |
719 private static final WrapperCache[] CONVERT_PRIMITIVE_FUNCTIONS = newWrapperCaches(Wrapper.values().length); |
716 CONVERT_PRIMITIVE_FUNCTIONS = newWrapperCaches(Wrapper.values().length); |
|
717 |
720 |
718 public static MethodHandle convertPrimitive(Wrapper wsrc, Wrapper wdst) { |
721 public static MethodHandle convertPrimitive(Wrapper wsrc, Wrapper wdst) { |
719 EnumMap<Wrapper, MethodHandle> cache = CONVERT_PRIMITIVE_FUNCTIONS[wsrc.ordinal()]; |
722 WrapperCache cache = CONVERT_PRIMITIVE_FUNCTIONS[wsrc.ordinal()]; |
720 MethodHandle mh = cache.get(wdst); |
723 MethodHandle mh = cache.get(wdst); |
721 if (mh != null) { |
724 if (mh != null) { |
722 return mh; |
725 return mh; |
723 } |
726 } |
724 // slow path |
727 // slow path |
725 Class<?> src = wsrc.primitiveType(); |
728 Class<?> src = wsrc.primitiveType(); |
726 Class<?> dst = wdst.primitiveType(); |
729 Class<?> dst = wdst.primitiveType(); |
727 MethodType type = src == void.class ? MethodType.methodType(dst) : MethodType.methodType(dst, src); |
730 MethodType type = MethodType.methodType(dst, src); |
728 if (wsrc == wdst) { |
731 if (wsrc == wdst) { |
729 mh = identity(src); |
732 mh = MethodHandles.identity(src); |
730 } else if (wsrc == Wrapper.VOID) { |
|
731 mh = zeroConstantFunction(wdst); |
|
732 } else if (wdst == Wrapper.VOID) { |
|
733 mh = MethodHandles.dropArguments(EMPTY, 0, src); // Defer back to MethodHandles. |
|
734 } else if (wsrc == Wrapper.OBJECT) { |
|
735 mh = unboxCast(dst); |
|
736 } else if (wdst == Wrapper.OBJECT) { |
|
737 mh = box(src); |
|
738 } else { |
733 } else { |
739 assert(src.isPrimitive() && dst.isPrimitive()); |
734 assert(src.isPrimitive() && dst.isPrimitive()); |
740 try { |
735 try { |
741 mh = IMPL_LOOKUP.findStatic(THIS_CLASS, src.getSimpleName()+"To"+capitalize(dst.getSimpleName()), type); |
736 mh = IMPL_LOOKUP.findStatic(THIS_CLASS, src.getSimpleName()+"To"+capitalize(dst.getSimpleName()), type); |
742 } catch (ReflectiveOperationException ex) { |
737 } catch (ReflectiveOperationException ex) { |
743 mh = null; |
738 mh = null; |
744 } |
739 } |
745 } |
740 } |
746 if (mh != null) { |
741 if (mh != null) { |
747 assert(mh.type() == type) : mh; |
742 assert(mh.type() == type) : mh; |
748 cache.put(wdst, mh); |
743 return cache.put(wdst, mh); |
749 return mh; |
|
750 } |
744 } |
751 |
745 |
752 throw new IllegalArgumentException("cannot find primitive conversion function for " + |
746 throw new IllegalArgumentException("cannot find primitive conversion function for " + |
753 src.getSimpleName()+" -> "+dst.getSimpleName()); |
747 src.getSimpleName()+" -> "+dst.getSimpleName()); |
754 } |
748 } |