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