blob: b4a8a44f11532edd06781e242e0108694599ae5e [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package com.sun.jmx.mbeanserver;
27
28import static com.sun.jmx.mbeanserver.Util.*;
29
30import static javax.management.openmbean.SimpleType.*;
31
32import com.sun.jmx.remote.util.EnvHelp;
33
34import java.beans.ConstructorProperties;
35import java.io.InvalidObjectException;
36import java.lang.annotation.ElementType;
37import java.lang.ref.WeakReference;
38import java.lang.reflect.Array;
39import java.lang.reflect.Constructor;
40import java.lang.reflect.Field;
41import java.lang.reflect.GenericArrayType;
42import java.lang.reflect.Method;
43import java.lang.reflect.Modifier;
44import java.lang.reflect.ParameterizedType;
45import java.lang.reflect.Proxy;
46import java.lang.reflect.Type;
47import java.util.ArrayList;
48import java.util.Arrays;
49import java.util.BitSet;
50import java.util.Collection;
51import java.util.Comparator;
52import java.util.HashSet;
53import java.util.List;
54import java.util.Map;
55import java.util.Set;
56import java.util.SortedMap;
57import java.util.SortedSet;
58import java.util.TreeSet;
59import java.util.WeakHashMap;
60
61import javax.management.JMX;
62import javax.management.ObjectName;
63import javax.management.openmbean.ArrayType;
64import javax.management.openmbean.CompositeData;
65import javax.management.openmbean.CompositeDataInvocationHandler;
66import javax.management.openmbean.CompositeDataSupport;
67import javax.management.openmbean.CompositeDataView;
68import javax.management.openmbean.CompositeType;
69import javax.management.openmbean.OpenDataException;
70import javax.management.openmbean.OpenType;
71import javax.management.openmbean.SimpleType;
72import javax.management.openmbean.TabularData;
73import javax.management.openmbean.TabularDataSupport;
74import 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 */
117public 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(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}