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