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