DefaultMXBeanMappingFactory.java revision 381
0N/A * Copyright 2005-2008 Sun Microsystems, Inc. All Rights Reserved. 0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 0N/A * This code is free software; you can redistribute it and/or modify it 2362N/A * under the terms of the GNU General Public License version 2 only, as 0N/A * published by the Free Software Foundation. Sun designates this 2362N/A * particular file as subject to the "Classpath" exception as provided 0N/A * by Sun in the LICENSE file that accompanied this code. 0N/A * This code is distributed in the hope that it will be useful, but WITHOUT 0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 0N/A * version 2 for more details (a copy is included in the LICENSE file that 0N/A * accompanied this code). 0N/A * You should have received a copy of the GNU General Public License version 0N/A * 2 along with this work; if not, write to the Free Software Foundation, 0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2362N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 2362N/A * CA 95054 USA or visit www.sun.com if you need additional information or 0N/A * have any questions. 0N/A * <p>A converter between Java types and the limited set of classes 0N/A * defined by Open MBeans.</p> 0N/A * <p>A Java type is an instance of java.lang.reflect.Type. For our 0N/A * purposes, it is either a Class, such as String.class or int.class; 0N/A * or a ParameterizedType, such as List<String> or Map<Integer, 0N/A * String[]>. On J2SE 1.4 and earlier, it can only be a Class.</p> 0N/A * <p>Each Type is associated with an DefaultMXBeanMappingFactory. The 0N/A * DefaultMXBeanMappingFactory defines an OpenType corresponding to the Type, plus a 0N/A * Java class corresponding to the OpenType. For example:</p> 0N/A * Type Open class OpenType 0N/A * ---- ---------- -------- 5891N/A * Integer Integer SimpleType.INTEGER 5891N/A * int int SimpleType.INTEGER 5891N/A * Integer[] Integer[] ArrayType(1, SimpleType.INTEGER) 5891N/A * int[] Integer[] ArrayType(SimpleType.INTEGER, true) 5891N/A * String[][] String[][] ArrayType(2, SimpleType.STRING) 5891N/A * List<String> String[] ArrayType(1, SimpleType.STRING) 5891N/A * ThreadState (an Enum) String SimpleType.STRING 5891N/A * Map<Integer, String[]> TabularData TabularType( 0N/A * {"key", SimpleType.INTEGER}, * <p>Apart from simple types, arrays, and collections, Java types are * converted through introspection into CompositeType. The Java type * must have at least one getter (method such as "int getSize()" or * "boolean isBig()"), and we must be able to deduce how to * reconstruct an instance of the Java class from the values of the * getters using one of various heuristics.</p> * <p>True if and only if this MXBeanMapping's toOpenValue and * fromOpenValue methods are the identity function.</p> /* Set up the mappings for Java types that map to SimpleType. */ // the classes that these predefined types declare must exist! // OK: must not be a primitive wrapper /** Get the converter for the given Java type, creating it if necessary. */ /* It's not yet worth formalizing these tests by having for example an array of factory classes, each of which says whether it recognizes the Type (Chain of Responsibility pattern). */ // Huge hack to avoid compiler warnings here. The ElementType // parameter is ignored but allows us to obtain a type variable // T that matches <T extends Enum<T>>. " must name a class with a public constructor that has a " +
"Could not construct a " + c.
getName() +
" for @" +
if (t
instanceof Class<?>)
/* Other cases: GenericArrayType, TypeVariable, WildcardType. * Returning the erasure of GenericArrayType is not necessary because * anyway we will be recursing on the element type, and we'll erase * then. Returning the erasure of the other two would mean returning * the type bound (e.g. Foo in <T extends Foo> or <? extends Foo>) * and since we don't treat this as Foo elsewhere we shouldn't here. /* Make the converter for an array type, or a collection such as * List<String> or Set<Integer>. We never see one-dimensional * primitive arrays (e.g. int[]) here because they use the identity * converter and are registered as such in the static initializer. /* We know how to translate List<E>, Set<E>, SortedSet<E>, Map<K,V>, SortedMap<K,V>, and that's it. We don't accept subtypes of those because we wouldn't know how to deserialize them. We don't accept Queue<E> because it is unlikely people would use that as a parameter or return type in an MBean. */ // For historical reasons GcInfo implements CompositeData but we // shouldn't count its CompositeData.getCompositeType() field as // an item in the computed CompositeType. /* Select public methods that look like "T getX()" or "boolean isX()", where T is not void and X is not the empty string. Exclude "Class getClass()" inherited from Object. */ "Class " + c.
getName() +
" has method name clash: " +
" to an open data type");
/* Converter for classes where the open data is identical to the original data. This is true for any of the SimpleType types, and for an any-dimension array of those. It is also true for primitive types as of JMX 1.3, since an int[] can be directly represented by an ArrayType, and an int needs no mapping because reflection takes care of it. */ for (
int i =
0; i <
len; i++)
* DefaultMXBeanMappingFactory for the elements of this array. If this is an * array of arrays, the converter converts the second-level arrays, * not the deepest elements. /* Determine the concrete class to be used when converting back to this Java type. We convert all Lists to ArrayList and all Sets to TreeSet. (TreeSet because it is a SortedSet, so works for both Set and SortedSet.) */ "Cannot convert SortedSet with non-null comparator: " +
"Could not add " + o +
" to " +
" (duplicate set element?)";
"No MXBean for name: " +
name;
"Cannot convert MXBean interface in this context";
"Cannot convert SortedMap with non-null comparator: " +
"Duplicate entry in TabularData: key=" +
key;
/** Determine how to convert back from the CompositeData into the original Java type. For a type that is not reconstructible, this method will fail every time, and will throw the right /* In this 2D array, each subarray is a set of builders where there is no point in consulting the ones after the first if /* We try to make a meaningful exception message by concatenating each Builder's explanation of why it break;
// skip other builders in this group " from a CompositeData: " +
whyNots;
/** Converts from a CompositeData to an instance of the targetClass. */ /** If the subclass is appropriate for targetClass, then the method returns null. If the subclass is not appropriate, then the method returns an explanation of why not. If the subclass should be appropriate but there is a problem, then the method throws InvalidObjectException. */ /** Builder for when the target class has a method "public static // See if it has a method "T from(CompositeData)" // as is conventional for a CompositeDataView "Method from(CompositeData) is not static";
"Method from(CompositeData) returns " +
// OK: it doesn't have the method return "no method from(CompositeData)";
final String msg =
"Failed to invoke from(CompositeData)";
/** This builder never actually returns success. It simply serves to check whether the other builders in the same group have any chance of success. If any getter in the targetClass returns a type that we don't know how to reconstruct, then we will not be able to make a builder, and there is no point in repeating the error about the problematic getter as many times as there are candidate builders. Instead, the "applicable" method will return an explanatory string, and the other builders will be skipped. If all the getters are OK, then the "applicable" method will return an empty string and the other builders will be tried. */ "that cannot be mapped back from OpenData";
/** Builder for when the target class has a setter for every getter. */ return "does not have a public no-arg constructor";
return "not all getters have corresponding setters " +
/** Builder for when the target class has a constructor that is annotated with @ConstructorProperties so we can see the correspondence // Applicable if and only if there are any annotated constructors return "no constructor has @ConstructorProperties annotation";
// Now check that all the annotated constructors are valid // and throw an exception if not. // First link the itemNames to their getter indexes. // Run through the constructors making the checks in the spec. // For each constructor, remember the correspondence between its // parameters and the items. The int[] for a constructor says // what parameter index should get what item. For example, // if element 0 is 2 then that means that item 0 in the // CompositeData goes to parameter 2 of the constructor. If an // element is -1, that item isn't given to the constructor. // Also remember the set of properties in that constructor // so we can test unambiguity. "Number of constructor params does not match " +
"@ConstructorProperties annotation: " +
constr;
" which does not correspond to a property: " +
"@ConstructorProperties contains property " +
"More than one constructor has a @ConstructorProperties " +
"annotation with this set of names: " +
/* Check that no possible set of items could lead to an ambiguous * choice of constructor (spec requires this check). For any * pair of constructors, their union would be the minimal * ambiguous set. If this set itself corresponds to a constructor, * there is no ambiguity for that pair. In the usual case, one * of the constructors is a superset of the other so the union is * just the bigger constuctor. * The algorithm here is quadratic in the number of constructors * with a @ConstructorProperties annotation. Typically this corresponds * to the number of versions of the class there have been. Ten * would already be a large number, so although it's probably * possible to have an O(n lg n) algorithm it wouldn't be "Constructors with @ConstructorProperties annotation " +
" would be ambiguous for these items: " +
// The CompositeData might come from an earlier version where // not all the items were present. We look for a constructor // that accepts just the items that are present. Because of // the ambiguity check in applicable(), we know there must be // at most one maximally applicable constructor. "No constructor has a @ConstructorProperties for this set of " +
/** Builder for when the target class is an interface and contains no methods other than getters. Then we can make an instance using a dynamic proxy that forwards the getters to the source return "not an interface";
/* If the interface has any methods left over, they better be * public methods that are already present in java.lang.Object. /* We don't catch SecurityException since it shouldn't * happen for a method in Object and if it does we would * like to know about it rather than mysteriously complaining. return "contains methods other than getters (" +
bad +
")";
"Parameter class " +
element +
" of " +
* Utility method to take a string and convert it to normal Java variable * name capitalization. This normally means converting the first * character from upper case to lower case, but in the (unusual) special * case when there is more than one character and both the first and * second characters are upper case, we leave it alone. * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays * @param name The string to be decapitalized. * @return The decapitalized version of the string. // Should be name.offsetByCodePoints but 6242664 makes this fail * Reverse operation for java.beans.Introspector.decapitalize. For any s, * capitalize(decapitalize(s)).equals(s). The reverse is not true: * e.g. capitalize("uRL") produces "URL" which is unchanged by // really an IdentityHashSet but that doesn't exist