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