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