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    }