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