001 /** 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one or more 004 * contributor license agreements. See the NOTICE file distributed with 005 * this work for additional information regarding copyright ownership. 006 * The ASF licenses this file to You under the Apache License, Version 2.0 007 * (the "License"); you may not use this file except in compliance with 008 * the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.geronimo.corba.util; 020 021 import java.lang.reflect.Array; 022 import java.util.HashMap; 023 024 /** 025 * Utility class for loading classes by a variety of name variations. 026 * <p/> 027 * Supported names types are: 028 * <p/> 029 * 1) Fully qualified class name (e.g., "java.lang.String", "org.apache.geronimo.corba.util.ClassLoading" 030 * 2) Method signature encoding ("Ljava.lang.String;", "J", "I", etc.) 031 * 3) Primitive type names ("int", "boolean", etc.) 032 * 4) Method array signature strings ("[I", "[Ljava.lang.String") 033 * 5) Arrays using Java code format ("int[]", "java.lang.String[][]") 034 * <p/> 035 * The classes are loaded using the provided class loader. For the basic types, the primitive 036 * reflection types are returned. 037 * 038 * @version $Rev: 706640 $ 039 */ 040 public class ClassLoading { 041 042 /** 043 * Table for mapping primitive class names/signatures to the implementing 044 * class object 045 */ 046 private static final HashMap PRIMITIVE_CLASS_MAP = new HashMap(); 047 048 /** 049 * Table for mapping primitive classes back to their name signature type, which 050 * allows a reverse mapping to be performed from a class object into a resolvable 051 * signature. 052 */ 053 private static final HashMap CLASS_TO_SIGNATURE_MAP = new HashMap(); 054 055 056 /** 057 * Setup the primitives map. We make any entry for each primitive class using both the 058 * human readable name and the method signature shorthand type. 059 */ 060 static { 061 PRIMITIVE_CLASS_MAP.put("boolean", boolean.class); 062 PRIMITIVE_CLASS_MAP.put("Z", boolean.class); 063 PRIMITIVE_CLASS_MAP.put("byte", byte.class); 064 PRIMITIVE_CLASS_MAP.put("B", byte.class); 065 PRIMITIVE_CLASS_MAP.put("char", char.class); 066 PRIMITIVE_CLASS_MAP.put("C", char.class); 067 PRIMITIVE_CLASS_MAP.put("short", short.class); 068 PRIMITIVE_CLASS_MAP.put("S", short.class); 069 PRIMITIVE_CLASS_MAP.put("int", int.class); 070 PRIMITIVE_CLASS_MAP.put("I", int.class); 071 PRIMITIVE_CLASS_MAP.put("long", long.class); 072 PRIMITIVE_CLASS_MAP.put("J", long.class); 073 PRIMITIVE_CLASS_MAP.put("float", float.class); 074 PRIMITIVE_CLASS_MAP.put("F", float.class); 075 PRIMITIVE_CLASS_MAP.put("double", double.class); 076 PRIMITIVE_CLASS_MAP.put("D", double.class); 077 PRIMITIVE_CLASS_MAP.put("void", void.class); 078 PRIMITIVE_CLASS_MAP.put("V", void.class); 079 080 // Now build a reverse mapping table. The table above has a many-to-one mapping for 081 // class names. To do the reverse, we need to pick just one. As long as the 082 // returned name supports "round tripping" of the requests, this will work fine. 083 084 CLASS_TO_SIGNATURE_MAP.put(boolean.class, "Z"); 085 CLASS_TO_SIGNATURE_MAP.put(byte.class, "B"); 086 CLASS_TO_SIGNATURE_MAP.put(char.class, "C"); 087 CLASS_TO_SIGNATURE_MAP.put(short.class, "S"); 088 CLASS_TO_SIGNATURE_MAP.put(int.class, "I"); 089 CLASS_TO_SIGNATURE_MAP.put(long.class, "J"); 090 CLASS_TO_SIGNATURE_MAP.put(float.class, "F"); 091 CLASS_TO_SIGNATURE_MAP.put(double.class, "D"); 092 CLASS_TO_SIGNATURE_MAP.put(void.class, "V"); 093 } 094 095 096 /** 097 * Load a class that matches the requested name, using the provided class loader context. 098 * <p/> 099 * The class name may be a standard class name, the name of a primitive type Java 100 * reflection class (e.g., "boolean" or "int"), or a type in method type signature 101 * encoding. Array classes in either encoding form are also processed. 102 * 103 * @param className The name of the required class. 104 * @param classLoader The class loader used to resolve the class object. 105 * @return The Class object resolved from "className". 106 * @throws ClassNotFoundException When unable to resolve the class object. 107 * @throws IllegalArgumentException If either argument is null. 108 */ 109 public static Class loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException { 110 111 // the tests require IllegalArgumentExceptions for null values on either of these. 112 if (className == null) { 113 throw new IllegalArgumentException("className is null"); 114 } 115 116 if (classLoader == null) { 117 throw new IllegalArgumentException("classLoader is null"); 118 } 119 // The easiest case is a proper class name. We just have the class loader resolve this. 120 // If the class loader throws a ClassNotFoundException, then we need to check each of the 121 // special name encodings we support. 122 try { 123 return classLoader.loadClass(className); 124 } catch (ClassNotFoundException ignore) { 125 // if not found, continue on to the other name forms. 126 } 127 128 129 // The second easiest version to resolve is a direct map to a primitive type name 130 // or method signature. Check our name-to-class map for one of those. 131 Class resolvedClass = (Class) PRIMITIVE_CLASS_MAP.get(className); 132 if (resolvedClass != null) { 133 return resolvedClass; 134 } 135 136 // Class names in method signature have the format "Lfully.resolved.name;", 137 // so if it ends in a semicolon and begins with an "L", this must be in 138 // this format. Have the class loader try to load this. There are no other 139 // options if this fails, so just allow the class loader to throw the 140 // ClassNotFoundException. 141 if (className.endsWith(";") && className.startsWith("L")) { 142 // pick out the name portion 143 String typeName = className.substring(1, className.length() - 1); 144 // and delegate the loading to the class loader. 145 return classLoader.loadClass(typeName); 146 } 147 148 // All we have left now are the array types. Method signature array types 149 // have a series of leading "[" characters to specify the number of dimensions. 150 // The other array type we handle uses trailing "[]" for the dimensions, just 151 // like the Java language syntax. 152 153 // first check for the signature form ([[[[type). 154 if (className.charAt(0) == '[') { 155 // we have at least one array marker, now count how many leading '['s we have 156 // to get the dimension count. 157 int count = 0; 158 int nameLen = className.length(); 159 160 while (count < nameLen && className.charAt(count) == '[') { 161 count++; 162 } 163 164 // pull of the name subtype, which is everything after the last '[' 165 String arrayTypeName = className.substring(count, className.length()); 166 // resolve the type using a recursive call, which will load any of the primitive signature 167 // types as well as class names. 168 Class arrayType = loadClass(arrayTypeName, classLoader); 169 170 // Resolving array types require a little more work. The array classes are 171 // created dynamically when the first instance of a given dimension and type is 172 // created. We need to create one using reflection to do this. 173 return getArrayClass(arrayType, count); 174 } 175 176 177 // ok, last chance. Now check for an array specification in Java language 178 // syntax. This will be a type name followed by pairs of "[]" to indicate 179 // the number of dimensions. 180 if (className.endsWith("[]")) { 181 // get the base component class name and the arrayDimensions 182 int count = 0; 183 int position = className.length(); 184 185 while (position > 1 && className.substring(position - 2, position).equals("[]")) { 186 // count this dimension 187 count++; 188 // and step back the probe position. 189 position -= 2; 190 } 191 192 // position now points at the location of the last successful test. This makes it 193 // easy to pick off the class name. 194 195 String typeName = className.substring(0, position); 196 197 // load the base type, again, doing this recursively 198 Class arrayType = loadClass(typeName, classLoader); 199 // and turn this into the class object 200 return getArrayClass(arrayType, count); 201 } 202 203 // We're out of options, just toss an exception over the wall. 204 throw new ClassNotFoundException(className); 205 } 206 207 208 /** 209 * Map a class object back to a class name. The returned class object 210 * must be "round trippable", which means 211 * <p/> 212 * type == ClassLoading.loadClass(ClassLoading.getClassName(type), classLoader) 213 * <p/> 214 * must be true. To ensure this, the class name is always returned in 215 * method signature format. 216 * 217 * @param type The class object we convert into name form. 218 * @return A string representation of the class name, in method signature 219 * format. 220 */ 221 public static String getClassName(Class type) { 222 StringBuffer name = new StringBuffer(); 223 224 // we test these in reverse order from the resolution steps, 225 // first handling arrays, then primitive types, and finally 226 // "normal" class objects. 227 228 // First handle arrays. If a class is an array, the type is 229 // element stored at that level. So, for a 2-dimensional array 230 // of ints, the top-level type will be "[I". We need to loop 231 // down the hierarchy until we hit a non-array type. 232 while (type.isArray()) { 233 // add another array indicator at the front of the name, 234 // and continue with the next type. 235 name.append('['); 236 type = type.getComponentType(); 237 } 238 239 // we're down to the base type. If this is a primitive, then 240 // we poke in the single-character type specifier. 241 if (type.isPrimitive()) { 242 name.append((String) CLASS_TO_SIGNATURE_MAP.get(type)); 243 } 244 // a "normal" class. This gets expressing using the "Lmy.class.name;" syntax. 245 else { 246 name.append('L'); 247 name.append(type.getName()); 248 name.append(';'); 249 } 250 return name.toString(); 251 } 252 253 private static Class getArrayClass(Class type, int dimension) { 254 // Array.newInstance() requires an array of the requested number of dimensions 255 // that gives the size for each dimension. We just request 0 in each of the 256 // dimentions, which is not unlike a black hole sigularity. 257 int dimensions[] = new int[dimension]; 258 // create an instance and return the associated class object. 259 return Array.newInstance(type, dimensions).getClass(); 260 } 261 }