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