1 /* |
|
2 * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Sun designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Sun in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 * CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 * have any questions. |
|
24 */ |
|
25 |
|
26 package com.sun.jmx.mbeanserver; |
|
27 |
|
28 import static com.sun.jmx.mbeanserver.Util.*; |
|
29 |
|
30 import static javax.management.openmbean.SimpleType.*; |
|
31 |
|
32 import com.sun.jmx.remote.util.EnvHelp; |
|
33 |
|
34 import java.beans.ConstructorProperties; |
|
35 import java.io.InvalidObjectException; |
|
36 import java.lang.annotation.ElementType; |
|
37 import java.lang.ref.WeakReference; |
|
38 import java.lang.reflect.Array; |
|
39 import java.lang.reflect.Constructor; |
|
40 import java.lang.reflect.Field; |
|
41 import java.lang.reflect.GenericArrayType; |
|
42 import java.lang.reflect.Method; |
|
43 import java.lang.reflect.Modifier; |
|
44 import java.lang.reflect.ParameterizedType; |
|
45 import java.lang.reflect.Proxy; |
|
46 import java.lang.reflect.Type; |
|
47 import java.util.ArrayList; |
|
48 import java.util.Arrays; |
|
49 import java.util.BitSet; |
|
50 import java.util.Collection; |
|
51 import java.util.Comparator; |
|
52 import java.util.HashSet; |
|
53 import java.util.List; |
|
54 import java.util.Map; |
|
55 import java.util.Set; |
|
56 import java.util.SortedMap; |
|
57 import java.util.SortedSet; |
|
58 import java.util.TreeSet; |
|
59 import java.util.WeakHashMap; |
|
60 |
|
61 import javax.management.JMX; |
|
62 import javax.management.ObjectName; |
|
63 import javax.management.openmbean.ArrayType; |
|
64 import javax.management.openmbean.CompositeData; |
|
65 import javax.management.openmbean.CompositeDataInvocationHandler; |
|
66 import javax.management.openmbean.CompositeDataSupport; |
|
67 import javax.management.openmbean.CompositeDataView; |
|
68 import javax.management.openmbean.CompositeType; |
|
69 import javax.management.openmbean.OpenDataException; |
|
70 import javax.management.openmbean.OpenType; |
|
71 import javax.management.openmbean.SimpleType; |
|
72 import javax.management.openmbean.TabularData; |
|
73 import javax.management.openmbean.TabularDataSupport; |
|
74 import javax.management.openmbean.TabularType; |
|
75 |
|
76 /** |
|
77 <p>A converter between Java types and the limited set of classes |
|
78 defined by Open MBeans.</p> |
|
79 |
|
80 <p>A Java type is an instance of java.lang.reflect.Type. For our |
|
81 purposes, it is either a Class, such as String.class or int.class; |
|
82 or a ParameterizedType, such as List<String> or Map<Integer, |
|
83 String[]>. On J2SE 1.4 and earlier, it can only be a Class.</p> |
|
84 |
|
85 <p>Each Type is associated with an OpenConverter. The |
|
86 OpenConverter defines an OpenType corresponding to the Type, plus a |
|
87 Java class corresponding to the OpenType. For example:</p> |
|
88 |
|
89 <pre> |
|
90 Type Open class OpenType |
|
91 ---- ---------- -------- |
|
92 Integer Integer SimpleType.INTEGER |
|
93 int int SimpleType.INTEGER |
|
94 Integer[] Integer[] ArrayType(1, SimpleType.INTEGER) |
|
95 int[] Integer[] ArrayType(SimpleType.INTEGER, true) |
|
96 String[][] String[][] ArrayType(2, SimpleType.STRING) |
|
97 List<String> String[] ArrayType(1, SimpleType.STRING) |
|
98 ThreadState (an Enum) String SimpleType.STRING |
|
99 Map<Integer, String[]> TabularData TabularType( |
|
100 CompositeType( |
|
101 {"key", SimpleType.INTEGER}, |
|
102 {"value", |
|
103 ArrayType(1, |
|
104 SimpleType.STRING)}), |
|
105 indexNames={"key"}) |
|
106 </pre> |
|
107 |
|
108 <p>Apart from simple types, arrays, and collections, Java types are |
|
109 converted through introspection into CompositeType. The Java type |
|
110 must have at least one getter (method such as "int getSize()" or |
|
111 "boolean isBig()"), and we must be able to deduce how to |
|
112 reconstruct an instance of the Java class from the values of the |
|
113 getters using one of various heuristics.</p> |
|
114 |
|
115 @since 1.6 |
|
116 */ |
|
117 public abstract class OpenConverter { |
|
118 private OpenConverter(Type targetType, OpenType openType, |
|
119 Class openClass) { |
|
120 this.targetType = targetType; |
|
121 this.openType = openType; |
|
122 this.openClass = openClass; |
|
123 } |
|
124 |
|
125 /** <p>Convert an instance of openClass into an instance of targetType. */ |
|
126 public final Object fromOpenValue(MXBeanLookup lookup, Object value) |
|
127 throws InvalidObjectException { |
|
128 if (value == null) |
|
129 return null; |
|
130 else |
|
131 return fromNonNullOpenValue(lookup, value); |
|
132 } |
|
133 |
|
134 abstract Object fromNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
135 throws InvalidObjectException; |
|
136 |
|
137 /** <p>Throw an appropriate InvalidObjectException if we will not be able |
|
138 to convert back from the open data to the original Java object.</p> */ |
|
139 void checkReconstructible() throws InvalidObjectException { |
|
140 // subclasses override if action necessary |
|
141 } |
|
142 |
|
143 /** <p>Convert an instance of targetType into an instance of openClass. */ |
|
144 final Object toOpenValue(MXBeanLookup lookup, Object value) |
|
145 throws OpenDataException { |
|
146 if (value == null) |
|
147 return null; |
|
148 else |
|
149 return toNonNullOpenValue(lookup, value); |
|
150 } |
|
151 |
|
152 abstract Object toNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
153 throws OpenDataException; |
|
154 |
|
155 /** <p>True if and only if this OpenConverter's toOpenValue and fromOpenValue |
|
156 methods are the identity function.</p> */ |
|
157 boolean isIdentity() { |
|
158 return false; |
|
159 } |
|
160 |
|
161 /** <p>True if and only if isIdentity() and even an array of the underlying type |
|
162 is transformed as the identity. This is true for Integer and |
|
163 ObjectName, for instance, but not for int.</p> */ |
|
164 final Type getTargetType() { |
|
165 return targetType; |
|
166 } |
|
167 |
|
168 final OpenType getOpenType() { |
|
169 return openType; |
|
170 } |
|
171 |
|
172 /* The Java class corresponding to getOpenType(). This is the class |
|
173 named by getOpenType().getClassName(), except that it may be a |
|
174 primitive type or an array of primitive type. */ |
|
175 final Class getOpenClass() { |
|
176 return openClass; |
|
177 } |
|
178 |
|
179 private final Type targetType; |
|
180 private final OpenType openType; |
|
181 private final Class openClass; |
|
182 |
|
183 private static final class ConverterMap |
|
184 extends WeakHashMap<Type, WeakReference<OpenConverter>> {} |
|
185 |
|
186 private static final ConverterMap converterMap = new ConverterMap(); |
|
187 |
|
188 /** Following List simply serves to keep a reference to predefined |
|
189 OpenConverters so they don't get garbage collected. */ |
|
190 private static final List<OpenConverter> permanentConverters = newList(); |
|
191 |
|
192 private static synchronized OpenConverter getConverter(Type type) { |
|
193 WeakReference<OpenConverter> wr = converterMap.get(type); |
|
194 return (wr == null) ? null : wr.get(); |
|
195 } |
|
196 |
|
197 private static synchronized void putConverter(Type type, |
|
198 OpenConverter conv) { |
|
199 WeakReference<OpenConverter> wr = |
|
200 new WeakReference<OpenConverter>(conv); |
|
201 converterMap.put(type, wr); |
|
202 } |
|
203 |
|
204 private static synchronized void putPermanentConverter(Type type, |
|
205 OpenConverter conv) { |
|
206 putConverter(type, conv); |
|
207 permanentConverters.add(conv); |
|
208 } |
|
209 |
|
210 static { |
|
211 /* Set up the mappings for Java types that map to SimpleType. */ |
|
212 |
|
213 final OpenType[] simpleTypes = { |
|
214 BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE, |
|
215 DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING, |
|
216 VOID, |
|
217 }; |
|
218 |
|
219 for (int i = 0; i < simpleTypes.length; i++) { |
|
220 final OpenType t = simpleTypes[i]; |
|
221 Class c; |
|
222 try { |
|
223 c = Class.forName(t.getClassName(), false, |
|
224 ObjectName.class.getClassLoader()); |
|
225 } catch (ClassNotFoundException e) { |
|
226 // the classes that these predefined types declare must exist! |
|
227 throw new Error(e); |
|
228 } |
|
229 final OpenConverter conv = new IdentityConverter(c, t, c); |
|
230 putPermanentConverter(c, conv); |
|
231 |
|
232 if (c.getName().startsWith("java.lang.")) { |
|
233 try { |
|
234 final Field typeField = c.getField("TYPE"); |
|
235 final Class primitiveType = (Class) typeField.get(null); |
|
236 final OpenConverter primitiveConv = |
|
237 new IdentityConverter(primitiveType, t, primitiveType); |
|
238 putPermanentConverter(primitiveType, |
|
239 primitiveConv); |
|
240 if (primitiveType != void.class) { |
|
241 final Class<?> primitiveArrayType = |
|
242 Array.newInstance(primitiveType, 0).getClass(); |
|
243 final OpenType primitiveArrayOpenType = |
|
244 ArrayType.getPrimitiveArrayType(primitiveArrayType); |
|
245 final OpenConverter primitiveArrayConv = |
|
246 new IdentityConverter(primitiveArrayType, |
|
247 primitiveArrayOpenType, |
|
248 primitiveArrayType); |
|
249 putPermanentConverter(primitiveArrayType, |
|
250 primitiveArrayConv); |
|
251 } |
|
252 } catch (NoSuchFieldException e) { |
|
253 // OK: must not be a primitive wrapper |
|
254 } catch (IllegalAccessException e) { |
|
255 // Should not reach here |
|
256 assert(false); |
|
257 } |
|
258 } |
|
259 } |
|
260 } |
|
261 |
|
262 /** Get the converter for the given Java type, creating it if necessary. */ |
|
263 public static synchronized OpenConverter toConverter(Type objType) |
|
264 throws OpenDataException { |
|
265 |
|
266 if (inProgress.containsKey(objType)) |
|
267 throw new OpenDataException("Recursive data structure"); |
|
268 |
|
269 OpenConverter conv; |
|
270 |
|
271 conv = getConverter(objType); |
|
272 if (conv != null) |
|
273 return conv; |
|
274 |
|
275 inProgress.put(objType, objType); |
|
276 try { |
|
277 conv = makeConverter(objType); |
|
278 } finally { |
|
279 inProgress.remove(objType); |
|
280 } |
|
281 |
|
282 putConverter(objType, conv); |
|
283 return conv; |
|
284 } |
|
285 |
|
286 private static OpenConverter makeConverter(Type objType) |
|
287 throws OpenDataException { |
|
288 |
|
289 /* It's not yet worth formalizing these tests by having for example |
|
290 an array of factory classes, each of which says whether it |
|
291 recognizes the Type (Chain of Responsibility pattern). */ |
|
292 if (objType instanceof GenericArrayType) { |
|
293 Type componentType = |
|
294 ((GenericArrayType) objType).getGenericComponentType(); |
|
295 return makeArrayOrCollectionConverter(objType, componentType); |
|
296 } else if (objType instanceof Class) { |
|
297 Class<?> objClass = (Class<?>) objType; |
|
298 if (objClass.isEnum()) { |
|
299 // Huge hack to avoid compiler warnings here. The ElementType |
|
300 // parameter is ignored but allows us to obtain a type variable |
|
301 // T that matches <T extends Enum<T>>. |
|
302 return makeEnumConverter(objClass, ElementType.class); |
|
303 } else if (objClass.isArray()) { |
|
304 Type componentType = objClass.getComponentType(); |
|
305 return makeArrayOrCollectionConverter(objClass, componentType); |
|
306 } else if (JMX.isMXBeanInterface(objClass)) { |
|
307 return makeMXBeanConverter(objClass); |
|
308 } else { |
|
309 return makeCompositeConverter(objClass); |
|
310 } |
|
311 } else if (objType instanceof ParameterizedType) { |
|
312 return makeParameterizedConverter((ParameterizedType) objType); |
|
313 } else |
|
314 throw new OpenDataException("Cannot map type: " + objType); |
|
315 } |
|
316 |
|
317 private static <T extends Enum<T>> OpenConverter |
|
318 makeEnumConverter(Class<?> enumClass, Class<T> fake) { |
|
319 Class<T> enumClassT = Util.cast(enumClass); |
|
320 return new EnumConverter<T>(enumClassT); |
|
321 } |
|
322 |
|
323 /* Make the converter for an array type, or a collection such as |
|
324 * List<String> or Set<Integer>. We never see one-dimensional |
|
325 * primitive arrays (e.g. int[]) here because they use the identity |
|
326 * converter and are registered as such in the static initializer. |
|
327 */ |
|
328 private static OpenConverter |
|
329 makeArrayOrCollectionConverter(Type collectionType, Type elementType) |
|
330 throws OpenDataException { |
|
331 |
|
332 final OpenConverter elementConverter = toConverter(elementType); |
|
333 final OpenType<?> elementOpenType = elementConverter.getOpenType(); |
|
334 final ArrayType<?> openType = ArrayType.getArrayType(elementOpenType); |
|
335 final Class<?> elementOpenClass = elementConverter.getOpenClass(); |
|
336 |
|
337 final Class<?> openArrayClass; |
|
338 final String openArrayClassName; |
|
339 if (elementOpenClass.isArray()) |
|
340 openArrayClassName = "[" + elementOpenClass.getName(); |
|
341 else |
|
342 openArrayClassName = "[L" + elementOpenClass.getName() + ";"; |
|
343 try { |
|
344 openArrayClass = Class.forName(openArrayClassName); |
|
345 } catch (ClassNotFoundException e) { |
|
346 throw openDataException("Cannot obtain array class", e); |
|
347 } |
|
348 |
|
349 if (collectionType instanceof ParameterizedType) { |
|
350 return new CollectionConverter(collectionType, |
|
351 openType, openArrayClass, |
|
352 elementConverter); |
|
353 } else { |
|
354 if (elementConverter.isIdentity()) { |
|
355 return new IdentityConverter(collectionType, |
|
356 openType, |
|
357 openArrayClass); |
|
358 } else { |
|
359 return new ArrayConverter(collectionType, |
|
360 openType, |
|
361 openArrayClass, |
|
362 elementConverter); |
|
363 } |
|
364 } |
|
365 } |
|
366 |
|
367 private static final String[] keyArray = {"key"}; |
|
368 private static final String[] keyValueArray = {"key", "value"}; |
|
369 |
|
370 private static OpenConverter |
|
371 makeTabularConverter(Type objType, boolean sortedMap, |
|
372 Type keyType, Type valueType) |
|
373 throws OpenDataException { |
|
374 |
|
375 final String objTypeName = objType.toString(); |
|
376 final OpenConverter keyConverter = toConverter(keyType); |
|
377 final OpenConverter valueConverter = toConverter(valueType); |
|
378 final OpenType keyOpenType = keyConverter.getOpenType(); |
|
379 final OpenType valueOpenType = valueConverter.getOpenType(); |
|
380 final CompositeType rowType = |
|
381 new CompositeType(objTypeName, |
|
382 objTypeName, |
|
383 keyValueArray, |
|
384 keyValueArray, |
|
385 new OpenType[] {keyOpenType, valueOpenType}); |
|
386 final TabularType tabularType = |
|
387 new TabularType(objTypeName, objTypeName, rowType, keyArray); |
|
388 return new TabularConverter(objType, sortedMap, tabularType, |
|
389 keyConverter, valueConverter); |
|
390 } |
|
391 |
|
392 /* We know how to translate List<E>, Set<E>, SortedSet<E>, |
|
393 Map<K,V>, SortedMap<K,V>, and that's it. We don't accept |
|
394 subtypes of those because we wouldn't know how to deserialize |
|
395 them. We don't accept Queue<E> because it is unlikely people |
|
396 would use that as a parameter or return type in an MBean. */ |
|
397 private static OpenConverter |
|
398 makeParameterizedConverter(ParameterizedType objType) throws OpenDataException { |
|
399 |
|
400 final Type rawType = objType.getRawType(); |
|
401 |
|
402 if (rawType instanceof Class) { |
|
403 Class c = (Class<?>) rawType; |
|
404 if (c == List.class || c == Set.class || c == SortedSet.class) { |
|
405 Type[] actuals = objType.getActualTypeArguments(); |
|
406 assert(actuals.length == 1); |
|
407 if (c == SortedSet.class) |
|
408 mustBeComparable(c, actuals[0]); |
|
409 return makeArrayOrCollectionConverter(objType, actuals[0]); |
|
410 } else { |
|
411 boolean sortedMap = (c == SortedMap.class); |
|
412 if (c == Map.class || sortedMap) { |
|
413 Type[] actuals = objType.getActualTypeArguments(); |
|
414 assert(actuals.length == 2); |
|
415 if (sortedMap) |
|
416 mustBeComparable(c, actuals[0]); |
|
417 return makeTabularConverter(objType, sortedMap, |
|
418 actuals[0], actuals[1]); |
|
419 } |
|
420 } |
|
421 } |
|
422 throw new OpenDataException("Cannot convert type: " + objType); |
|
423 } |
|
424 |
|
425 private static OpenConverter makeMXBeanConverter(Type t) |
|
426 throws OpenDataException { |
|
427 return new MXBeanConverter(t); |
|
428 } |
|
429 |
|
430 private static OpenConverter makeCompositeConverter(Class c) |
|
431 throws OpenDataException { |
|
432 |
|
433 // For historical reasons GcInfo implements CompositeData but we |
|
434 // shouldn't count its CompositeData.getCompositeType() field as |
|
435 // an item in the computed CompositeType. |
|
436 final boolean gcInfoHack = |
|
437 (c.getName().equals("com.sun.management.GcInfo") && |
|
438 c.getClassLoader() == null); |
|
439 |
|
440 final List<Method> methods = |
|
441 MBeanAnalyzer.eliminateCovariantMethods(Arrays.asList(c.getMethods())); |
|
442 final SortedMap<String,Method> getterMap = newSortedMap(); |
|
443 |
|
444 /* Select public methods that look like "T getX()" or "boolean |
|
445 isX()", where T is not void and X is not the empty |
|
446 string. Exclude "Class getClass()" inherited from Object. */ |
|
447 for (Method method : methods) { |
|
448 final String propertyName = propertyName(method); |
|
449 |
|
450 if (propertyName == null) |
|
451 continue; |
|
452 if (gcInfoHack && propertyName.equals("CompositeType")) |
|
453 continue; |
|
454 |
|
455 Method old = |
|
456 getterMap.put(decapitalize(propertyName), |
|
457 method); |
|
458 if (old != null) { |
|
459 final String msg = |
|
460 "Class " + c.getName() + " has method name clash: " + |
|
461 old.getName() + ", " + method.getName(); |
|
462 throw new OpenDataException(msg); |
|
463 } |
|
464 } |
|
465 |
|
466 final int nitems = getterMap.size(); |
|
467 |
|
468 if (nitems == 0) { |
|
469 throw new OpenDataException("Can't map " + c.getName() + |
|
470 " to an open data type"); |
|
471 } |
|
472 |
|
473 final Method[] getters = new Method[nitems]; |
|
474 final String[] itemNames = new String[nitems]; |
|
475 final OpenType[] openTypes = new OpenType[nitems]; |
|
476 int i = 0; |
|
477 for (Map.Entry<String,Method> entry : getterMap.entrySet()) { |
|
478 itemNames[i] = entry.getKey(); |
|
479 final Method getter = entry.getValue(); |
|
480 getters[i] = getter; |
|
481 final Type retType = getter.getGenericReturnType(); |
|
482 openTypes[i] = toConverter(retType).getOpenType(); |
|
483 i++; |
|
484 } |
|
485 |
|
486 CompositeType compositeType = |
|
487 new CompositeType(c.getName(), |
|
488 c.getName(), |
|
489 itemNames, // field names |
|
490 itemNames, // field descriptions |
|
491 openTypes); |
|
492 |
|
493 return new CompositeConverter(c, |
|
494 compositeType, |
|
495 itemNames, |
|
496 getters); |
|
497 } |
|
498 |
|
499 /* Converter for classes where the open data is identical to the |
|
500 original data. This is true for any of the SimpleType types, |
|
501 and for an any-dimension array of those. It is also true for |
|
502 primitive types as of JMX 1.3, since an int[] needs to |
|
503 can be directly represented by an ArrayType, and an int needs no mapping |
|
504 because reflection takes care of it. */ |
|
505 private static final class IdentityConverter extends OpenConverter { |
|
506 IdentityConverter(Type targetType, OpenType openType, |
|
507 Class openClass) { |
|
508 super(targetType, openType, openClass); |
|
509 } |
|
510 |
|
511 boolean isIdentity() { |
|
512 return true; |
|
513 } |
|
514 |
|
515 final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) { |
|
516 return value; |
|
517 } |
|
518 |
|
519 public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value) { |
|
520 return value; |
|
521 } |
|
522 } |
|
523 |
|
524 private static final class EnumConverter<T extends Enum<T>> |
|
525 extends OpenConverter { |
|
526 |
|
527 EnumConverter(Class<T> enumClass) { |
|
528 super(enumClass, SimpleType.STRING, String.class); |
|
529 this.enumClass = enumClass; |
|
530 } |
|
531 |
|
532 final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) { |
|
533 return ((Enum) value).name(); |
|
534 } |
|
535 |
|
536 // return type could be T, but after erasure that would be |
|
537 // java.lang.Enum, which doesn't exist on J2SE 1.4 |
|
538 public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
539 throws InvalidObjectException { |
|
540 try { |
|
541 return Enum.valueOf(enumClass, (String) value); |
|
542 } catch (Exception e) { |
|
543 throw invalidObjectException("Cannot convert to enum: " + |
|
544 value, e); |
|
545 } |
|
546 } |
|
547 |
|
548 private final Class<T> enumClass; |
|
549 } |
|
550 |
|
551 private static final class ArrayConverter extends OpenConverter { |
|
552 ArrayConverter(Type targetType, |
|
553 ArrayType openArrayType, Class openArrayClass, |
|
554 OpenConverter elementConverter) { |
|
555 super(targetType, openArrayType, openArrayClass); |
|
556 this.elementConverter = elementConverter; |
|
557 } |
|
558 |
|
559 final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
560 throws OpenDataException { |
|
561 Object[] valueArray = (Object[]) value; |
|
562 final int len = valueArray.length; |
|
563 final Object[] openArray = (Object[]) |
|
564 Array.newInstance(getOpenClass().getComponentType(), len); |
|
565 for (int i = 0; i < len; i++) { |
|
566 openArray[i] = |
|
567 elementConverter.toOpenValue(lookup, valueArray[i]); |
|
568 } |
|
569 return openArray; |
|
570 } |
|
571 |
|
572 public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue) |
|
573 throws InvalidObjectException { |
|
574 final Object[] openArray = (Object[]) openValue; |
|
575 final Type targetType = getTargetType(); |
|
576 final Object[] valueArray; |
|
577 final Type componentType; |
|
578 if (targetType instanceof GenericArrayType) { |
|
579 componentType = |
|
580 ((GenericArrayType) targetType).getGenericComponentType(); |
|
581 } else if (targetType instanceof Class && |
|
582 ((Class<?>) targetType).isArray()) { |
|
583 componentType = ((Class<?>) targetType).getComponentType(); |
|
584 } else { |
|
585 throw new IllegalArgumentException("Not an array: " + |
|
586 targetType); |
|
587 } |
|
588 valueArray = (Object[]) Array.newInstance((Class<?>) componentType, |
|
589 openArray.length); |
|
590 for (int i = 0; i < openArray.length; i++) { |
|
591 valueArray[i] = |
|
592 elementConverter.fromOpenValue(lookup, openArray[i]); |
|
593 } |
|
594 return valueArray; |
|
595 } |
|
596 |
|
597 void checkReconstructible() throws InvalidObjectException { |
|
598 elementConverter.checkReconstructible(); |
|
599 } |
|
600 |
|
601 /** OpenConverter for the elements of this array. If this is an |
|
602 array of arrays, the converter converts the second-level arrays, |
|
603 not the deepest elements. */ |
|
604 private final OpenConverter elementConverter; |
|
605 } |
|
606 |
|
607 private static final class CollectionConverter extends OpenConverter { |
|
608 CollectionConverter(Type targetType, |
|
609 ArrayType openArrayType, |
|
610 Class openArrayClass, |
|
611 OpenConverter elementConverter) { |
|
612 super(targetType, openArrayType, openArrayClass); |
|
613 this.elementConverter = elementConverter; |
|
614 |
|
615 /* Determine the concrete class to be used when converting |
|
616 back to this Java type. We convert all Lists to ArrayList |
|
617 and all Sets to TreeSet. (TreeSet because it is a SortedSet, |
|
618 so works for both Set and SortedSet.) */ |
|
619 Type raw = ((ParameterizedType) targetType).getRawType(); |
|
620 Class c = (Class<?>) raw; |
|
621 if (c == List.class) |
|
622 collectionClass = ArrayList.class; |
|
623 else if (c == Set.class) |
|
624 collectionClass = HashSet.class; |
|
625 else if (c == SortedSet.class) |
|
626 collectionClass = TreeSet.class; |
|
627 else { // can't happen |
|
628 assert(false); |
|
629 collectionClass = null; |
|
630 } |
|
631 } |
|
632 |
|
633 final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
634 throws OpenDataException { |
|
635 final Collection valueCollection = (Collection) value; |
|
636 if (valueCollection instanceof SortedSet) { |
|
637 Comparator comparator = |
|
638 ((SortedSet) valueCollection).comparator(); |
|
639 if (comparator != null) { |
|
640 final String msg = |
|
641 "Cannot convert SortedSet with non-null comparator: " + |
|
642 comparator; |
|
643 throw new OpenDataException(msg); |
|
644 } |
|
645 } |
|
646 final Object[] openArray = (Object[]) |
|
647 Array.newInstance(getOpenClass().getComponentType(), |
|
648 valueCollection.size()); |
|
649 int i = 0; |
|
650 for (Object o : valueCollection) |
|
651 openArray[i++] = elementConverter.toOpenValue(lookup, o); |
|
652 return openArray; |
|
653 } |
|
654 |
|
655 public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue) |
|
656 throws InvalidObjectException { |
|
657 final Object[] openArray = (Object[]) openValue; |
|
658 final Collection<Object> valueCollection; |
|
659 try { |
|
660 valueCollection = Util.cast(collectionClass.newInstance()); |
|
661 } catch (Exception e) { |
|
662 throw invalidObjectException("Cannot create collection", e); |
|
663 } |
|
664 for (Object o : openArray) { |
|
665 Object value = elementConverter.fromOpenValue(lookup, o); |
|
666 if (!valueCollection.add(value)) { |
|
667 final String msg = |
|
668 "Could not add " + o + " to " + |
|
669 collectionClass.getName() + |
|
670 " (duplicate set element?)"; |
|
671 throw new InvalidObjectException(msg); |
|
672 } |
|
673 } |
|
674 return valueCollection; |
|
675 } |
|
676 |
|
677 void checkReconstructible() throws InvalidObjectException { |
|
678 elementConverter.checkReconstructible(); |
|
679 } |
|
680 |
|
681 private final Class<? extends Collection> collectionClass; |
|
682 private final OpenConverter elementConverter; |
|
683 } |
|
684 |
|
685 private static final class MXBeanConverter extends OpenConverter { |
|
686 MXBeanConverter(Type intf) { |
|
687 super(intf, SimpleType.OBJECTNAME, ObjectName.class); |
|
688 } |
|
689 |
|
690 final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
691 throws OpenDataException { |
|
692 lookupNotNull(lookup, OpenDataException.class); |
|
693 ObjectName name = lookup.mxbeanToObjectName(value); |
|
694 if (name == null) |
|
695 throw new OpenDataException("No name for object: " + value); |
|
696 return name; |
|
697 } |
|
698 |
|
699 public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
700 throws InvalidObjectException { |
|
701 lookupNotNull(lookup, InvalidObjectException.class); |
|
702 ObjectName name = (ObjectName) value; |
|
703 Object mxbean = |
|
704 lookup.objectNameToMXBean(name, (Class<?>) getTargetType()); |
|
705 if (mxbean == null) { |
|
706 final String msg = |
|
707 "No MXBean for name: " + name; |
|
708 throw new InvalidObjectException(msg); |
|
709 } |
|
710 return mxbean; |
|
711 } |
|
712 |
|
713 private <T extends Exception> void |
|
714 lookupNotNull(MXBeanLookup lookup, Class<T> excClass) |
|
715 throws T { |
|
716 if (lookup == null) { |
|
717 final String msg = |
|
718 "Cannot convert MXBean interface in this context"; |
|
719 T exc; |
|
720 try { |
|
721 Constructor<T> con = excClass.getConstructor(String.class); |
|
722 exc = con.newInstance(msg); |
|
723 } catch (Exception e) { |
|
724 throw new RuntimeException(e); |
|
725 } |
|
726 throw exc; |
|
727 } |
|
728 } |
|
729 } |
|
730 |
|
731 private static final class TabularConverter extends OpenConverter { |
|
732 TabularConverter(Type targetType, |
|
733 boolean sortedMap, |
|
734 TabularType tabularType, |
|
735 OpenConverter keyConverter, |
|
736 OpenConverter valueConverter) { |
|
737 super(targetType, tabularType, TabularData.class); |
|
738 this.sortedMap = sortedMap; |
|
739 this.keyConverter = keyConverter; |
|
740 this.valueConverter = valueConverter; |
|
741 } |
|
742 |
|
743 final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
744 throws OpenDataException { |
|
745 final Map<Object, Object> valueMap = Util.cast(value); |
|
746 if (valueMap instanceof SortedMap) { |
|
747 Comparator comparator = ((SortedMap) valueMap).comparator(); |
|
748 if (comparator != null) { |
|
749 final String msg = |
|
750 "Cannot convert SortedMap with non-null comparator: " + |
|
751 comparator; |
|
752 throw new OpenDataException(msg); |
|
753 } |
|
754 } |
|
755 final TabularType tabularType = (TabularType) getOpenType(); |
|
756 final TabularData table = new TabularDataSupport(tabularType); |
|
757 final CompositeType rowType = tabularType.getRowType(); |
|
758 for (Map.Entry entry : valueMap.entrySet()) { |
|
759 final Object openKey = |
|
760 keyConverter.toOpenValue(lookup, entry.getKey()); |
|
761 final Object openValue = |
|
762 valueConverter.toOpenValue(lookup, entry.getValue()); |
|
763 final CompositeData row; |
|
764 row = |
|
765 new CompositeDataSupport(rowType, keyValueArray, |
|
766 new Object[] {openKey, |
|
767 openValue}); |
|
768 table.put(row); |
|
769 } |
|
770 return table; |
|
771 } |
|
772 |
|
773 public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object openValue) |
|
774 throws InvalidObjectException { |
|
775 final TabularData table = (TabularData) openValue; |
|
776 final Collection<CompositeData> rows = Util.cast(table.values()); |
|
777 final Map<Object, Object> valueMap = |
|
778 sortedMap ? newSortedMap() : newMap(); |
|
779 for (CompositeData row : rows) { |
|
780 final Object key = |
|
781 keyConverter.fromOpenValue(lookup, row.get("key")); |
|
782 final Object value = |
|
783 valueConverter.fromOpenValue(lookup, row.get("value")); |
|
784 if (valueMap.put(key, value) != null) { |
|
785 final String msg = |
|
786 "Duplicate entry in TabularData: key=" + key; |
|
787 throw new InvalidObjectException(msg); |
|
788 } |
|
789 } |
|
790 return valueMap; |
|
791 } |
|
792 |
|
793 void checkReconstructible() throws InvalidObjectException { |
|
794 keyConverter.checkReconstructible(); |
|
795 valueConverter.checkReconstructible(); |
|
796 } |
|
797 |
|
798 private final boolean sortedMap; |
|
799 private final OpenConverter keyConverter; |
|
800 private final OpenConverter valueConverter; |
|
801 } |
|
802 |
|
803 private static final class CompositeConverter extends OpenConverter { |
|
804 CompositeConverter(Class targetClass, |
|
805 CompositeType compositeType, |
|
806 String[] itemNames, |
|
807 Method[] getters) throws OpenDataException { |
|
808 super(targetClass, compositeType, CompositeData.class); |
|
809 |
|
810 assert(itemNames.length == getters.length); |
|
811 |
|
812 this.itemNames = itemNames; |
|
813 this.getters = getters; |
|
814 this.getterConverters = new OpenConverter[getters.length]; |
|
815 for (int i = 0; i < getters.length; i++) { |
|
816 Type retType = getters[i].getGenericReturnType(); |
|
817 getterConverters[i] = OpenConverter.toConverter(retType); |
|
818 } |
|
819 } |
|
820 |
|
821 final Object toNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
822 throws OpenDataException { |
|
823 CompositeType ct = (CompositeType) getOpenType(); |
|
824 if (value instanceof CompositeDataView) |
|
825 return ((CompositeDataView) value).toCompositeData(ct); |
|
826 if (value == null) |
|
827 return null; |
|
828 |
|
829 Object[] values = new Object[getters.length]; |
|
830 for (int i = 0; i < getters.length; i++) { |
|
831 try { |
|
832 Object got = getters[i].invoke(value, (Object[]) null); |
|
833 values[i] = getterConverters[i].toOpenValue(lookup, got); |
|
834 } catch (Exception e) { |
|
835 throw openDataException("Error calling getter for " + |
|
836 itemNames[i] + ": " + e, e); |
|
837 } |
|
838 } |
|
839 return new CompositeDataSupport(ct, itemNames, values); |
|
840 } |
|
841 |
|
842 /** Determine how to convert back from the CompositeData into |
|
843 the original Java type. For a type that is not reconstructible, |
|
844 this method will fail every time, and will throw the right |
|
845 exception. */ |
|
846 private synchronized void makeCompositeBuilder() |
|
847 throws InvalidObjectException { |
|
848 if (compositeBuilder != null) |
|
849 return; |
|
850 |
|
851 Class targetClass = (Class<?>) getTargetType(); |
|
852 /* In this 2D array, each subarray is a set of builders where |
|
853 there is no point in consulting the ones after the first if |
|
854 the first refuses. */ |
|
855 CompositeBuilder[][] builders = { |
|
856 { |
|
857 new CompositeBuilderViaFrom(targetClass, itemNames), |
|
858 }, |
|
859 { |
|
860 new CompositeBuilderViaConstructor(targetClass, itemNames), |
|
861 }, |
|
862 { |
|
863 new CompositeBuilderCheckGetters(targetClass, itemNames, |
|
864 getterConverters), |
|
865 new CompositeBuilderViaSetters(targetClass, itemNames), |
|
866 new CompositeBuilderViaProxy(targetClass, itemNames), |
|
867 }, |
|
868 }; |
|
869 CompositeBuilder foundBuilder = null; |
|
870 /* We try to make a meaningful exception message by |
|
871 concatenating each Builder's explanation of why it |
|
872 isn't applicable. */ |
|
873 final StringBuilder whyNots = new StringBuilder(); |
|
874 find: |
|
875 for (CompositeBuilder[] relatedBuilders : builders) { |
|
876 for (int i = 0; i < relatedBuilders.length; i++) { |
|
877 CompositeBuilder builder = relatedBuilders[i]; |
|
878 String whyNot = builder.applicable(getters); |
|
879 if (whyNot == null) { |
|
880 foundBuilder = builder; |
|
881 break find; |
|
882 } |
|
883 if (whyNot.length() > 0) { |
|
884 if (whyNots.length() > 0) |
|
885 whyNots.append("; "); |
|
886 whyNots.append(whyNot); |
|
887 if (i == 0) |
|
888 break; // skip other builders in this group |
|
889 } |
|
890 } |
|
891 } |
|
892 if (foundBuilder == null) { |
|
893 final String msg = |
|
894 "Do not know how to make a " + targetClass.getName() + |
|
895 " from a CompositeData: " + whyNots; |
|
896 throw new InvalidObjectException(msg); |
|
897 } |
|
898 compositeBuilder = foundBuilder; |
|
899 } |
|
900 |
|
901 void checkReconstructible() throws InvalidObjectException { |
|
902 makeCompositeBuilder(); |
|
903 } |
|
904 |
|
905 public final Object fromNonNullOpenValue(MXBeanLookup lookup, Object value) |
|
906 throws InvalidObjectException { |
|
907 makeCompositeBuilder(); |
|
908 return compositeBuilder.fromCompositeData(lookup, |
|
909 (CompositeData) value, |
|
910 itemNames, |
|
911 getterConverters); |
|
912 } |
|
913 |
|
914 private final String[] itemNames; |
|
915 private final Method[] getters; |
|
916 private final OpenConverter[] getterConverters; |
|
917 private CompositeBuilder compositeBuilder; |
|
918 } |
|
919 |
|
920 /** Converts from a CompositeData to an instance of the targetClass. */ |
|
921 private static abstract class CompositeBuilder { |
|
922 CompositeBuilder(Class targetClass, String[] itemNames) { |
|
923 this.targetClass = targetClass; |
|
924 this.itemNames = itemNames; |
|
925 } |
|
926 |
|
927 Class<?> getTargetClass() { |
|
928 return targetClass; |
|
929 } |
|
930 |
|
931 String[] getItemNames() { |
|
932 return itemNames; |
|
933 } |
|
934 |
|
935 /** If the subclass is appropriate for targetClass, then the |
|
936 method returns null. If the subclass is not appropriate, |
|
937 then the method returns an explanation of why not. If the |
|
938 subclass should be appropriate but there is a problem, |
|
939 then the method throws InvalidObjectException. */ |
|
940 abstract String applicable(Method[] getters) |
|
941 throws InvalidObjectException; |
|
942 |
|
943 abstract Object fromCompositeData(MXBeanLookup lookup, CompositeData cd, |
|
944 String[] itemNames, |
|
945 OpenConverter[] converters) |
|
946 throws InvalidObjectException; |
|
947 |
|
948 private final Class<?> targetClass; |
|
949 private final String[] itemNames; |
|
950 } |
|
951 |
|
952 /** Builder for when the target class has a method "public static |
|
953 from(CompositeData)". */ |
|
954 private static final class CompositeBuilderViaFrom |
|
955 extends CompositeBuilder { |
|
956 |
|
957 CompositeBuilderViaFrom(Class targetClass, String[] itemNames) { |
|
958 super(targetClass, itemNames); |
|
959 } |
|
960 |
|
961 String applicable(Method[] getters) throws InvalidObjectException { |
|
962 // See if it has a method "T from(CompositeData)" |
|
963 // as is conventional for a CompositeDataView |
|
964 Class<?> targetClass = getTargetClass(); |
|
965 try { |
|
966 Method fromMethod = |
|
967 targetClass.getMethod("from", |
|
968 new Class[] {CompositeData.class}); |
|
969 |
|
970 if (!Modifier.isStatic(fromMethod.getModifiers())) { |
|
971 final String msg = |
|
972 "Method from(CompositeData) is not static"; |
|
973 throw new InvalidObjectException(msg); |
|
974 } |
|
975 |
|
976 if (fromMethod.getReturnType() != getTargetClass()) { |
|
977 final String msg = |
|
978 "Method from(CompositeData) returns " + |
|
979 fromMethod.getReturnType().getName() + |
|
980 " not " + targetClass.getName(); |
|
981 throw new InvalidObjectException(msg); |
|
982 } |
|
983 |
|
984 this.fromMethod = fromMethod; |
|
985 return null; // success! |
|
986 } catch (InvalidObjectException e) { |
|
987 throw e; |
|
988 } catch (Exception e) { |
|
989 // OK: it doesn't have the method |
|
990 return "no method from(CompositeData)"; |
|
991 } |
|
992 } |
|
993 |
|
994 final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd, |
|
995 String[] itemNames, |
|
996 OpenConverter[] converters) |
|
997 throws InvalidObjectException { |
|
998 try { |
|
999 return fromMethod.invoke(null, cd); |
|
1000 } catch (Exception e) { |
|
1001 final String msg = "Failed to invoke from(CompositeData)"; |
|
1002 throw invalidObjectException(msg, e); |
|
1003 } |
|
1004 } |
|
1005 |
|
1006 private Method fromMethod; |
|
1007 } |
|
1008 |
|
1009 /** This builder never actually returns success. It simply serves |
|
1010 to check whether the other builders in the same group have any |
|
1011 chance of success. If any getter in the targetClass returns |
|
1012 a type that we don't know how to reconstruct, then we will |
|
1013 not be able to make a builder, and there is no point in repeating |
|
1014 the error about the problematic getter as many times as there are |
|
1015 candidate builders. Instead, the "applicable" method will return |
|
1016 an explanatory string, and the other builders will be skipped. |
|
1017 If all the getters are OK, then the "applicable" method will return |
|
1018 an empty string and the other builders will be tried. */ |
|
1019 private static class CompositeBuilderCheckGetters extends CompositeBuilder { |
|
1020 CompositeBuilderCheckGetters(Class targetClass, String[] itemNames, |
|
1021 OpenConverter[] getterConverters) { |
|
1022 super(targetClass, itemNames); |
|
1023 this.getterConverters = getterConverters; |
|
1024 } |
|
1025 |
|
1026 String applicable(Method[] getters) { |
|
1027 for (int i = 0; i < getters.length; i++) { |
|
1028 try { |
|
1029 getterConverters[i].checkReconstructible(); |
|
1030 } catch (InvalidObjectException e) { |
|
1031 return "method " + getters[i].getName() + " returns type " + |
|
1032 "that cannot be mapped back from OpenData"; |
|
1033 } |
|
1034 } |
|
1035 return ""; |
|
1036 } |
|
1037 |
|
1038 final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd, |
|
1039 String[] itemNames, |
|
1040 OpenConverter[] converters) { |
|
1041 throw new Error(); |
|
1042 } |
|
1043 |
|
1044 private final OpenConverter[] getterConverters; |
|
1045 } |
|
1046 |
|
1047 /** Builder for when the target class has a setter for every getter. */ |
|
1048 private static class CompositeBuilderViaSetters extends CompositeBuilder { |
|
1049 |
|
1050 CompositeBuilderViaSetters(Class targetClass, String[] itemNames) { |
|
1051 super(targetClass, itemNames); |
|
1052 } |
|
1053 |
|
1054 String applicable(Method[] getters) { |
|
1055 try { |
|
1056 Constructor<?> c = getTargetClass().getConstructor((Class[]) null); |
|
1057 } catch (Exception e) { |
|
1058 return "does not have a public no-arg constructor"; |
|
1059 } |
|
1060 |
|
1061 Method[] setters = new Method[getters.length]; |
|
1062 for (int i = 0; i < getters.length; i++) { |
|
1063 Method getter = getters[i]; |
|
1064 Class returnType = getter.getReturnType(); |
|
1065 String name = propertyName(getter); |
|
1066 String setterName = "set" + name; |
|
1067 Method setter; |
|
1068 try { |
|
1069 setter = getTargetClass().getMethod(setterName, returnType); |
|
1070 if (setter.getReturnType() != void.class) |
|
1071 throw new Exception(); |
|
1072 } catch (Exception e) { |
|
1073 return "not all getters have corresponding setters " + |
|
1074 "(" + getter + ")"; |
|
1075 } |
|
1076 setters[i] = setter; |
|
1077 } |
|
1078 this.setters = setters; |
|
1079 return null; |
|
1080 } |
|
1081 |
|
1082 Object fromCompositeData(MXBeanLookup lookup, CompositeData cd, |
|
1083 String[] itemNames, |
|
1084 OpenConverter[] converters) |
|
1085 throws InvalidObjectException { |
|
1086 Object o; |
|
1087 try { |
|
1088 o = getTargetClass().newInstance(); |
|
1089 for (int i = 0; i < itemNames.length; i++) { |
|
1090 if (cd.containsKey(itemNames[i])) { |
|
1091 Object openItem = cd.get(itemNames[i]); |
|
1092 Object javaItem = |
|
1093 converters[i].fromOpenValue(lookup, openItem); |
|
1094 setters[i].invoke(o, javaItem); |
|
1095 } |
|
1096 } |
|
1097 } catch (Exception e) { |
|
1098 throw invalidObjectException(e); |
|
1099 } |
|
1100 return o; |
|
1101 } |
|
1102 |
|
1103 private Method[] setters; |
|
1104 } |
|
1105 |
|
1106 /** Builder for when the target class has a constructor that is |
|
1107 annotated with @ConstructorProperties so we can see the correspondence |
|
1108 to getters. */ |
|
1109 private static final class CompositeBuilderViaConstructor |
|
1110 extends CompositeBuilder { |
|
1111 |
|
1112 CompositeBuilderViaConstructor(Class targetClass, String[] itemNames) { |
|
1113 super(targetClass, itemNames); |
|
1114 } |
|
1115 |
|
1116 String applicable(Method[] getters) throws InvalidObjectException { |
|
1117 |
|
1118 final Class<ConstructorProperties> propertyNamesClass = ConstructorProperties.class; |
|
1119 |
|
1120 Class targetClass = getTargetClass(); |
|
1121 Constructor<?>[] constrs = targetClass.getConstructors(); |
|
1122 |
|
1123 // Applicable if and only if there are any annotated constructors |
|
1124 List<Constructor<?>> annotatedConstrList = newList(); |
|
1125 for (Constructor<?> constr : constrs) { |
|
1126 if (Modifier.isPublic(constr.getModifiers()) |
|
1127 && constr.getAnnotation(propertyNamesClass) != null) |
|
1128 annotatedConstrList.add(constr); |
|
1129 } |
|
1130 |
|
1131 if (annotatedConstrList.isEmpty()) |
|
1132 return "no constructor has @ConstructorProperties annotation"; |
|
1133 |
|
1134 annotatedConstructors = newList(); |
|
1135 |
|
1136 // Now check that all the annotated constructors are valid |
|
1137 // and throw an exception if not. |
|
1138 |
|
1139 // First link the itemNames to their getter indexes. |
|
1140 Map<String, Integer> getterMap = newMap(); |
|
1141 String[] itemNames = getItemNames(); |
|
1142 for (int i = 0; i < itemNames.length; i++) |
|
1143 getterMap.put(itemNames[i], i); |
|
1144 |
|
1145 // Run through the constructors making the checks in the spec. |
|
1146 // For each constructor, remember the correspondence between its |
|
1147 // parameters and the items. The int[] for a constructor says |
|
1148 // what parameter index should get what item. For example, |
|
1149 // if element 0 is 2 then that means that item 0 in the |
|
1150 // CompositeData goes to parameter 2 of the constructor. If an |
|
1151 // element is -1, that item isn't given to the constructor. |
|
1152 // Also remember the set of properties in that constructor |
|
1153 // so we can test unambiguity. |
|
1154 Set<BitSet> getterIndexSets = newSet(); |
|
1155 for (Constructor<?> constr : annotatedConstrList) { |
|
1156 String[] propertyNames = |
|
1157 constr.getAnnotation(propertyNamesClass).value(); |
|
1158 |
|
1159 Type[] paramTypes = constr.getGenericParameterTypes(); |
|
1160 if (paramTypes.length != propertyNames.length) { |
|
1161 final String msg = |
|
1162 "Number of constructor params does not match " + |
|
1163 "@ConstructorProperties annotation: " + constr; |
|
1164 throw new InvalidObjectException(msg); |
|
1165 } |
|
1166 |
|
1167 int[] paramIndexes = new int[getters.length]; |
|
1168 for (int i = 0; i < getters.length; i++) |
|
1169 paramIndexes[i] = -1; |
|
1170 BitSet present = new BitSet(); |
|
1171 |
|
1172 for (int i = 0; i < propertyNames.length; i++) { |
|
1173 String propertyName = propertyNames[i]; |
|
1174 if (!getterMap.containsKey(propertyName)) { |
|
1175 final String msg = |
|
1176 "@ConstructorProperties includes name " + propertyName + |
|
1177 " which does not correspond to a property: " + |
|
1178 constr; |
|
1179 throw new InvalidObjectException(msg); |
|
1180 } |
|
1181 int getterIndex = getterMap.get(propertyName); |
|
1182 paramIndexes[getterIndex] = i; |
|
1183 if (present.get(getterIndex)) { |
|
1184 final String msg = |
|
1185 "@ConstructorProperties contains property " + |
|
1186 propertyName + " more than once: " + constr; |
|
1187 throw new InvalidObjectException(msg); |
|
1188 } |
|
1189 present.set(getterIndex); |
|
1190 Method getter = getters[getterIndex]; |
|
1191 Type propertyType = getter.getGenericReturnType(); |
|
1192 if (!propertyType.equals(paramTypes[i])) { |
|
1193 final String msg = |
|
1194 "@ConstructorProperties gives property " + propertyName + |
|
1195 " of type " + propertyType + " for parameter " + |
|
1196 " of type " + paramTypes[i] + ": " + constr; |
|
1197 throw new InvalidObjectException(msg); |
|
1198 } |
|
1199 } |
|
1200 |
|
1201 if (!getterIndexSets.add(present)) { |
|
1202 final String msg = |
|
1203 "More than one constructor has a @ConstructorProperties " + |
|
1204 "annotation with this set of names: " + |
|
1205 Arrays.toString(propertyNames); |
|
1206 throw new InvalidObjectException(msg); |
|
1207 } |
|
1208 |
|
1209 Constr c = new Constr(constr, paramIndexes, present); |
|
1210 annotatedConstructors.add(c); |
|
1211 } |
|
1212 |
|
1213 /* Check that no possible set of items could lead to an ambiguous |
|
1214 * choice of constructor (spec requires this check). For any |
|
1215 * pair of constructors, their union would be the minimal |
|
1216 * ambiguous set. If this set itself corresponds to a constructor, |
|
1217 * there is no ambiguity for that pair. In the usual case, one |
|
1218 * of the constructors is a superset of the other so the union is |
|
1219 * just the bigger constuctor. |
|
1220 * |
|
1221 * The algorithm here is quadratic in the number of constructors |
|
1222 * with a @ConstructorProperties annotation. Typically this corresponds |
|
1223 * to the number of versions of the class there have been. Ten |
|
1224 * would already be a large number, so although it's probably |
|
1225 * possible to have an O(n lg n) algorithm it wouldn't be |
|
1226 * worth the complexity. |
|
1227 */ |
|
1228 for (BitSet a : getterIndexSets) { |
|
1229 boolean seen = false; |
|
1230 for (BitSet b : getterIndexSets) { |
|
1231 if (a == b) |
|
1232 seen = true; |
|
1233 else if (seen) { |
|
1234 BitSet u = new BitSet(); |
|
1235 u.or(a); u.or(b); |
|
1236 if (!getterIndexSets.contains(u)) { |
|
1237 Set<String> names = new TreeSet<String>(); |
|
1238 for (int i = u.nextSetBit(0); i >= 0; |
|
1239 i = u.nextSetBit(i+1)) |
|
1240 names.add(itemNames[i]); |
|
1241 final String msg = |
|
1242 "Constructors with @ConstructorProperties annotation " + |
|
1243 " would be ambiguous for these items: " + |
|
1244 names; |
|
1245 throw new InvalidObjectException(msg); |
|
1246 } |
|
1247 } |
|
1248 } |
|
1249 } |
|
1250 |
|
1251 return null; // success! |
|
1252 } |
|
1253 |
|
1254 Object fromCompositeData(MXBeanLookup lookup, CompositeData cd, |
|
1255 String[] itemNames, |
|
1256 OpenConverter[] converters) |
|
1257 throws InvalidObjectException { |
|
1258 // The CompositeData might come from an earlier version where |
|
1259 // not all the items were present. We look for a constructor |
|
1260 // that accepts just the items that are present. Because of |
|
1261 // the ambiguity check in applicable(), we know there must be |
|
1262 // at most one maximally applicable constructor. |
|
1263 CompositeType ct = cd.getCompositeType(); |
|
1264 BitSet present = new BitSet(); |
|
1265 for (int i = 0; i < itemNames.length; i++) { |
|
1266 if (ct.getType(itemNames[i]) != null) |
|
1267 present.set(i); |
|
1268 } |
|
1269 |
|
1270 Constr max = null; |
|
1271 for (Constr constr : annotatedConstructors) { |
|
1272 if (subset(constr.presentParams, present) && |
|
1273 (max == null || |
|
1274 subset(max.presentParams, constr.presentParams))) |
|
1275 max = constr; |
|
1276 } |
|
1277 |
|
1278 if (max == null) { |
|
1279 final String msg = |
|
1280 "No constructor has a @ConstructorProperties for this set of " + |
|
1281 "items: " + ct.keySet(); |
|
1282 throw new InvalidObjectException(msg); |
|
1283 } |
|
1284 |
|
1285 Object[] params = new Object[max.presentParams.cardinality()]; |
|
1286 for (int i = 0; i < itemNames.length; i++) { |
|
1287 if (!max.presentParams.get(i)) |
|
1288 continue; |
|
1289 Object openItem = cd.get(itemNames[i]); |
|
1290 Object javaItem = converters[i].fromOpenValue(lookup, openItem); |
|
1291 int index = max.paramIndexes[i]; |
|
1292 if (index >= 0) |
|
1293 params[index] = javaItem; |
|
1294 } |
|
1295 |
|
1296 try { |
|
1297 return max.constructor.newInstance(params); |
|
1298 } catch (Exception e) { |
|
1299 final String msg = |
|
1300 "Exception constructing " + getTargetClass().getName(); |
|
1301 throw invalidObjectException(msg, e); |
|
1302 } |
|
1303 } |
|
1304 |
|
1305 private static boolean subset(BitSet sub, BitSet sup) { |
|
1306 BitSet subcopy = (BitSet) sub.clone(); |
|
1307 subcopy.andNot(sup); |
|
1308 return subcopy.isEmpty(); |
|
1309 } |
|
1310 |
|
1311 private static class Constr { |
|
1312 final Constructor<?> constructor; |
|
1313 final int[] paramIndexes; |
|
1314 final BitSet presentParams; |
|
1315 Constr(Constructor<?> constructor, int[] paramIndexes, |
|
1316 BitSet presentParams) { |
|
1317 this.constructor = constructor; |
|
1318 this.paramIndexes = paramIndexes; |
|
1319 this.presentParams = presentParams; |
|
1320 } |
|
1321 } |
|
1322 |
|
1323 private List<Constr> annotatedConstructors; |
|
1324 } |
|
1325 |
|
1326 /** Builder for when the target class is an interface and contains |
|
1327 no methods other than getters. Then we can make an instance |
|
1328 using a dynamic proxy that forwards the getters to the source |
|
1329 CompositeData. */ |
|
1330 private static final class CompositeBuilderViaProxy |
|
1331 extends CompositeBuilder { |
|
1332 |
|
1333 CompositeBuilderViaProxy(Class targetClass, String[] itemNames) { |
|
1334 super(targetClass, itemNames); |
|
1335 } |
|
1336 |
|
1337 String applicable(Method[] getters) { |
|
1338 Class targetClass = getTargetClass(); |
|
1339 if (!targetClass.isInterface()) |
|
1340 return "not an interface"; |
|
1341 Set<Method> methods = |
|
1342 newSet(Arrays.asList(targetClass.getMethods())); |
|
1343 methods.removeAll(Arrays.asList(getters)); |
|
1344 /* If the interface has any methods left over, they better be |
|
1345 * public methods that are already present in java.lang.Object. |
|
1346 */ |
|
1347 String bad = null; |
|
1348 for (Method m : methods) { |
|
1349 String mname = m.getName(); |
|
1350 Class[] mparams = m.getParameterTypes(); |
|
1351 try { |
|
1352 Method om = Object.class.getMethod(mname, mparams); |
|
1353 if (!Modifier.isPublic(om.getModifiers())) |
|
1354 bad = mname; |
|
1355 } catch (NoSuchMethodException e) { |
|
1356 bad = mname; |
|
1357 } |
|
1358 /* We don't catch SecurityException since it shouldn't |
|
1359 * happen for a method in Object and if it does we would |
|
1360 * like to know about it rather than mysteriously complaining. |
|
1361 */ |
|
1362 } |
|
1363 if (bad != null) |
|
1364 return "contains methods other than getters (" + bad + ")"; |
|
1365 return null; // success! |
|
1366 } |
|
1367 |
|
1368 final Object fromCompositeData(MXBeanLookup lookup, CompositeData cd, |
|
1369 String[] itemNames, |
|
1370 OpenConverter[] converters) { |
|
1371 final Class targetClass = getTargetClass(); |
|
1372 return |
|
1373 Proxy.newProxyInstance(targetClass.getClassLoader(), |
|
1374 new Class[] {targetClass}, |
|
1375 new CompositeDataInvocationHandler(cd)); |
|
1376 } |
|
1377 } |
|
1378 |
|
1379 static InvalidObjectException invalidObjectException(String msg, |
|
1380 Throwable cause) { |
|
1381 return EnvHelp.initCause(new InvalidObjectException(msg), cause); |
|
1382 } |
|
1383 |
|
1384 static InvalidObjectException invalidObjectException(Throwable cause) { |
|
1385 return invalidObjectException(cause.getMessage(), cause); |
|
1386 } |
|
1387 |
|
1388 static OpenDataException openDataException(String msg, Throwable cause) { |
|
1389 return EnvHelp.initCause(new OpenDataException(msg), cause); |
|
1390 } |
|
1391 |
|
1392 static OpenDataException openDataException(Throwable cause) { |
|
1393 return openDataException(cause.getMessage(), cause); |
|
1394 } |
|
1395 |
|
1396 static void mustBeComparable(Class collection, Type element) |
|
1397 throws OpenDataException { |
|
1398 if (!(element instanceof Class) |
|
1399 || !Comparable.class.isAssignableFrom((Class<?>) element)) { |
|
1400 final String msg = |
|
1401 "Parameter class " + element + " of " + |
|
1402 collection.getName() + " does not implement " + |
|
1403 Comparable.class.getName(); |
|
1404 throw new OpenDataException(msg); |
|
1405 } |
|
1406 } |
|
1407 |
|
1408 /** |
|
1409 * Utility method to take a string and convert it to normal Java variable |
|
1410 * name capitalization. This normally means converting the first |
|
1411 * character from upper case to lower case, but in the (unusual) special |
|
1412 * case when there is more than one character and both the first and |
|
1413 * second characters are upper case, we leave it alone. |
|
1414 * <p> |
|
1415 * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays |
|
1416 * as "URL". |
|
1417 * |
|
1418 * @param name The string to be decapitalized. |
|
1419 * @return The decapitalized version of the string. |
|
1420 */ |
|
1421 public static String decapitalize(String name) { |
|
1422 if (name == null || name.length() == 0) { |
|
1423 return name; |
|
1424 } |
|
1425 int offset1 = Character.offsetByCodePoints(name, 0, 1); |
|
1426 // Should be name.offsetByCodePoints but 6242664 makes this fail |
|
1427 if (offset1 < name.length() && |
|
1428 Character.isUpperCase(name.codePointAt(offset1))) |
|
1429 return name; |
|
1430 return name.substring(0, offset1).toLowerCase() + |
|
1431 name.substring(offset1); |
|
1432 } |
|
1433 |
|
1434 /** |
|
1435 * Reverse operation for java.beans.Introspector.decapitalize. For any s, |
|
1436 * capitalize(decapitalize(s)).equals(s). The reverse is not true: |
|
1437 * e.g. capitalize("uRL") produces "URL" which is unchanged by |
|
1438 * decapitalize. |
|
1439 */ |
|
1440 static String capitalize(String name) { |
|
1441 if (name == null || name.length() == 0) |
|
1442 return name; |
|
1443 int offset1 = name.offsetByCodePoints(0, 1); |
|
1444 return name.substring(0, offset1).toUpperCase() + |
|
1445 name.substring(offset1); |
|
1446 } |
|
1447 |
|
1448 public static String propertyName(Method m) { |
|
1449 String rest = null; |
|
1450 String name = m.getName(); |
|
1451 if (name.startsWith("get")) |
|
1452 rest = name.substring(3); |
|
1453 else if (name.startsWith("is") && m.getReturnType() == boolean.class) |
|
1454 rest = name.substring(2); |
|
1455 if (rest == null || rest.length() == 0 |
|
1456 || m.getParameterTypes().length > 0 |
|
1457 || m.getReturnType() == void.class |
|
1458 || name.equals("getClass")) |
|
1459 return null; |
|
1460 return rest; |
|
1461 } |
|
1462 |
|
1463 private final static Map<Type, Type> inProgress = newIdentityHashMap(); |
|
1464 // really an IdentityHashSet but that doesn't exist |
|
1465 } |
|