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 }