001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.geronimo.kernel;
017:
018: import java.lang.reflect.Array;
019: import java.util.HashMap;
020: import java.util.Set;
021: import java.util.LinkedHashSet;
022: import java.util.LinkedList;
023: import java.util.Arrays;
024: import java.util.List;
025: import java.util.ArrayList;
026:
027: import org.apache.geronimo.kernel.config.MultiParentClassLoader;
028:
029: /**
030: * Utility class for loading classes by a variety of name variations.
031: * <p/>
032: * Supported names types are:
033: * <p/>
034: * 1) Fully qualified class name (e.g., "java.lang.String", "org.apache.geronimo.kernel.ClassLoading"
035: * 2) Method signature encoding ("Ljava.lang.String;", "J", "I", etc.)
036: * 3) Primitive type names ("int", "boolean", etc.)
037: * 4) Method array signature strings ("[I", "[Ljava.lang.String")
038: * 5) Arrays using Java code format ("int[]", "java.lang.String[][]")
039: * <p/>
040: * The classes are loaded using the provided class loader. For the basic types, the primitive
041: * reflection types are returned.
042: *
043: * @version $Rev: 476049 $
044: */
045: public class ClassLoading {
046:
047: /**
048: * Table for mapping primitive class names/signatures to the implementing
049: * class object
050: */
051: private static final HashMap PRIMITIVE_CLASS_MAP = new HashMap();
052:
053: /**
054: * Table for mapping primitive classes back to their name signature type, which
055: * allows a reverse mapping to be performed from a class object into a resolvable
056: * signature.
057: */
058: private static final HashMap CLASS_TO_SIGNATURE_MAP = new HashMap();
059:
060: /**
061: * Setup the primitives map. We make any entry for each primitive class using both the
062: * human readable name and the method signature shorthand type.
063: */
064: static {
065: PRIMITIVE_CLASS_MAP.put("boolean", boolean.class);
066: PRIMITIVE_CLASS_MAP.put("Z", boolean.class);
067: PRIMITIVE_CLASS_MAP.put("byte", byte.class);
068: PRIMITIVE_CLASS_MAP.put("B", byte.class);
069: PRIMITIVE_CLASS_MAP.put("char", char.class);
070: PRIMITIVE_CLASS_MAP.put("C", char.class);
071: PRIMITIVE_CLASS_MAP.put("short", short.class);
072: PRIMITIVE_CLASS_MAP.put("S", short.class);
073: PRIMITIVE_CLASS_MAP.put("int", int.class);
074: PRIMITIVE_CLASS_MAP.put("I", int.class);
075: PRIMITIVE_CLASS_MAP.put("long", long.class);
076: PRIMITIVE_CLASS_MAP.put("J", long.class);
077: PRIMITIVE_CLASS_MAP.put("float", float.class);
078: PRIMITIVE_CLASS_MAP.put("F", float.class);
079: PRIMITIVE_CLASS_MAP.put("double", double.class);
080: PRIMITIVE_CLASS_MAP.put("D", double.class);
081: PRIMITIVE_CLASS_MAP.put("void", void.class);
082: PRIMITIVE_CLASS_MAP.put("V", void.class);
083:
084: // Now build a reverse mapping table. The table above has a many-to-one mapping for
085: // class names. To do the reverse, we need to pick just one. As long as the
086: // returned name supports "round tripping" of the requests, this will work fine.
087:
088: CLASS_TO_SIGNATURE_MAP.put(boolean.class, "Z");
089: CLASS_TO_SIGNATURE_MAP.put(byte.class, "B");
090: CLASS_TO_SIGNATURE_MAP.put(char.class, "C");
091: CLASS_TO_SIGNATURE_MAP.put(short.class, "S");
092: CLASS_TO_SIGNATURE_MAP.put(int.class, "I");
093: CLASS_TO_SIGNATURE_MAP.put(long.class, "J");
094: CLASS_TO_SIGNATURE_MAP.put(float.class, "F");
095: CLASS_TO_SIGNATURE_MAP.put(double.class, "D");
096: CLASS_TO_SIGNATURE_MAP.put(void.class, "V");
097: }
098:
099: /**
100: * Load a class that matches the requested name, using the provided class loader context.
101: * <p/>
102: * The class name may be a standard class name, the name of a primitive type Java
103: * reflection class (e.g., "boolean" or "int"), or a type in method type signature
104: * encoding. Array classes in either encoding form are also processed.
105: *
106: * @param className The name of the required class.
107: * @param classLoader The class loader used to resolve the class object.
108: * @return The Class object resolved from "className".
109: * @throws ClassNotFoundException When unable to resolve the class object.
110: * @throws IllegalArgumentException If either argument is null.
111: */
112: public static Class loadClass(String className,
113: ClassLoader classLoader) throws ClassNotFoundException {
114:
115: // the tests require IllegalArgumentExceptions for null values on either of these.
116: if (className == null) {
117: throw new IllegalArgumentException("className is null");
118: }
119:
120: if (classLoader == null) {
121: throw new IllegalArgumentException("classLoader is null");
122: }
123: // The easiest case is a proper class name. We just have the class loader resolve this.
124: // If the class loader throws a ClassNotFoundException, then we need to check each of the
125: // special name encodings we support.
126: try {
127: return classLoader.loadClass(className);
128: } catch (ClassNotFoundException ignore) {
129: // if not found, continue on to the other name forms.
130: }
131:
132: // The second easiest version to resolve is a direct map to a primitive type name
133: // or method signature. Check our name-to-class map for one of those.
134: Class resolvedClass = (Class) PRIMITIVE_CLASS_MAP
135: .get(className);
136: if (resolvedClass != null) {
137: return resolvedClass;
138: }
139:
140: // Class names in method signature have the format "Lfully.resolved.name;",
141: // so if it ends in a semicolon and begins with an "L", this must be in
142: // this format. Have the class loader try to load this. There are no other
143: // options if this fails, so just allow the class loader to throw the
144: // ClassNotFoundException.
145: if (className.endsWith(";") && className.startsWith("L")) {
146: // pick out the name portion
147: String typeName = className.substring(1,
148: className.length() - 1);
149: // and delegate the loading to the class loader.
150: return classLoader.loadClass(typeName);
151: }
152:
153: // All we have left now are the array types. Method signature array types
154: // have a series of leading "[" characters to specify the number of dimensions.
155: // The other array type we handle uses trailing "[]" for the dimensions, just
156: // like the Java language syntax.
157:
158: // first check for the signature form ([[[[type).
159: if (className.charAt(0) == '[') {
160: // we have at least one array marker, now count how many leading '['s we have
161: // to get the dimension count.
162: int count = 0;
163: int nameLen = className.length();
164:
165: while (count < nameLen && className.charAt(count) == '[') {
166: count++;
167: }
168:
169: // pull of the name subtype, which is everything after the last '['
170: String arrayTypeName = className.substring(count, className
171: .length());
172: // resolve the type using a recursive call, which will load any of the primitive signature
173: // types as well as class names.
174: Class arrayType = loadClass(arrayTypeName, classLoader);
175:
176: // Resolving array types require a little more work. The array classes are
177: // created dynamically when the first instance of a given dimension and type is
178: // created. We need to create one using reflection to do this.
179: return getArrayClass(arrayType, count);
180: }
181:
182: // ok, last chance. Now check for an array specification in Java language
183: // syntax. This will be a type name followed by pairs of "[]" to indicate
184: // the number of dimensions.
185: if (className.endsWith("[]")) {
186: // get the base component class name and the arrayDimensions
187: int count = 0;
188: int position = className.length();
189:
190: while (position > 1
191: && className.substring(position - 2, position)
192: .equals("[]")) {
193: // count this dimension
194: count++;
195: // and step back the probe position.
196: position -= 2;
197: }
198:
199: // position now points at the location of the last successful test. This makes it
200: // easy to pick off the class name.
201:
202: String typeName = className.substring(0, position);
203:
204: // load the base type, again, doing this recursively
205: Class arrayType = loadClass(typeName, classLoader);
206: // and turn this into the class object
207: return getArrayClass(arrayType, count);
208: }
209:
210: // We're out of options, just toss an exception over the wall.
211: if (classLoader instanceof MultiParentClassLoader) {
212: MultiParentClassLoader cl = (MultiParentClassLoader) classLoader;
213: throw new ClassNotFoundException("Could not load class "
214: + className + " from classloader: " + cl.getId()
215: + ", destroyed state: " + cl.isDestroyed());
216: }
217: throw new ClassNotFoundException("Could not load class "
218: + className + " from unknown classloader; "
219: + classLoader);
220: }
221:
222: /**
223: * Map a class object back to a class name. The returned class object
224: * must be "round trippable", which means
225: * <p/>
226: * type == ClassLoading.loadClass(ClassLoading.getClassName(type), classLoader)
227: * <p/>
228: * must be true. To ensure this, the class name is always returned in
229: * method signature format.
230: *
231: * @param type The class object we convert into name form.
232: * @return A string representation of the class name, in method signature
233: * format.
234: */
235: public static String getClassName(Class type) {
236: StringBuffer name = new StringBuffer();
237:
238: // we test these in reverse order from the resolution steps,
239: // first handling arrays, then primitive types, and finally
240: // "normal" class objects.
241:
242: // First handle arrays. If a class is an array, the type is
243: // element stored at that level. So, for a 2-dimensional array
244: // of ints, the top-level type will be "[I". We need to loop
245: // down the hierarchy until we hit a non-array type.
246: while (type.isArray()) {
247: // add another array indicator at the front of the name,
248: // and continue with the next type.
249: name.append('[');
250: type = type.getComponentType();
251: }
252:
253: // we're down to the base type. If this is a primitive, then
254: // we poke in the single-character type specifier.
255: if (type.isPrimitive()) {
256: name.append((String) CLASS_TO_SIGNATURE_MAP.get(type));
257: }
258: // a "normal" class. This gets expressing using the "Lmy.class.name;" syntax.
259: else {
260: name.append('L');
261: name.append(type.getName());
262: name.append(';');
263: }
264: return name.toString();
265: }
266:
267: private static Class getArrayClass(Class type, int dimension) {
268: // Array.newInstance() requires an array of the requested number of dimensions
269: // that gives the size for each dimension. We just request 0 in each of the
270: // dimentions, which is not unlike a black hole sigularity.
271: int dimensions[] = new int[dimension];
272: // create an instance and return the associated class object.
273: return Array.newInstance(type, dimensions).getClass();
274: }
275:
276: public static Set getAllTypes(Class type) {
277: Set allTypes = new LinkedHashSet();
278: allTypes.add(type);
279: allTypes.addAll(getAllSuperClasses(type));
280: allTypes.addAll(getAllInterfaces(type));
281: return allTypes;
282: }
283:
284: private static Set getAllSuperClasses(Class clazz) {
285: Set allSuperClasses = new LinkedHashSet();
286: for (Class super Class = clazz.getSuperclass(); super Class != null; super Class = super Class
287: .getSuperclass()) {
288: allSuperClasses.add(super Class);
289: }
290: return allSuperClasses;
291: }
292:
293: private static Set getAllInterfaces(Class clazz) {
294: Set allInterfaces = new LinkedHashSet();
295: LinkedList stack = new LinkedList();
296: stack.addAll(Arrays.asList(clazz.getInterfaces()));
297: while (!stack.isEmpty()) {
298: Class intf = (Class) stack.removeFirst();
299: if (!allInterfaces.contains(intf)) {
300: allInterfaces.add(intf);
301: stack.addAll(Arrays.asList(intf.getInterfaces()));
302: }
303: }
304: return allInterfaces;
305: }
306:
307: public static Set reduceInterfaces(Set source) {
308: Class[] classes = (Class[]) source.toArray(new Class[source
309: .size()]);
310: classes = reduceInterfaces(classes);
311: return new LinkedHashSet(Arrays.asList(classes));
312: }
313:
314: /**
315: * If there are multiple interfaces, and some of them extend each other,
316: * eliminate the superclass in favor of the subclasses that extend them.
317: *
318: * If one of the entries is a class (not an interface), make sure it's
319: * the first one in the array. If more than one of the entries is a
320: * class, throws an IllegalArgumentException
321: *
322: * @param source the original list of interfaces
323: * @return the equal or smaller list of interfaces
324: */
325: public static Class[] reduceInterfaces(Class[] source) {
326: // use a copy of the sorce array
327: source = (Class[]) source.clone();
328:
329: for (int leftIndex = 0; leftIndex < source.length - 1; leftIndex++) {
330: Class left = source[leftIndex];
331: if (left == null) {
332: continue;
333: }
334:
335: for (int rightIndex = leftIndex + 1; rightIndex < source.length; rightIndex++) {
336: Class right = source[rightIndex];
337: if (right == null) {
338: continue;
339: }
340:
341: if (left == right || right.isAssignableFrom(left)) {
342: // right is the same as class or a sub class of left
343: source[rightIndex] = null;
344: } else if (left.isAssignableFrom(right)) {
345: // left is the same as class or a sub class of right
346: source[leftIndex] = null;
347:
348: // the left has been eliminated; move on to the next left
349: break;
350: }
351: }
352: }
353:
354: Class clazz = null;
355: for (int i = 0; i < source.length; i++) {
356: if (source[i] != null && !source[i].isInterface()) {
357: if (clazz != null) {
358: throw new IllegalArgumentException(
359: "Source contains two classes which are not subclasses of each other: "
360: + clazz.getName() + ", "
361: + source[i].getName());
362: }
363: clazz = source[i];
364: source[i] = null;
365: }
366: }
367:
368: List list = new ArrayList(source.length);
369: if (clazz != null)
370: list.add(clazz);
371: for (int i = 0; i < source.length; i++) {
372: if (source[i] != null) {
373: list.add(source[i]);
374: }
375: }
376: return (Class[]) list.toArray(new Class[list.size()]);
377: }
378: }
|