summaryrefslogtreecommitdiffstats
path: root/sca-java-2.x/tags/2.0.1-RC1/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/ClassLoadingUtil.java
blob: c65868c23b78fe41d79ff36c628f4d930ac1f0be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.    
 */

package org.apache.tuscany.sca.binding.ejb.corba;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * Utility class for loading classes by a variety of name variations.
 * <p/>
 * Supported names types are:
 * <p/>
 * 1)  Fully qualified class name (e.g., "java.lang.String", "org.apache.geronimo.kernel.ClassLoading"
 * 2)  Method signature encoding ("Ljava.lang.String;", "J", "I", etc.)
 * 3)  Primitive type names ("int", "boolean", etc.)
 * 4)  Method array signature strings ("[I", "[Ljava.lang.String")
 * 5)  Arrays using Java code format ("int[]", "java.lang.String[][]")
 * <p/>
 * The classes are loaded using the provided class loader.  For the basic types, the primitive
 * reflection types are returned.
 *
 * @version $Rev$ $Date$
 */
public class ClassLoadingUtil {

    /**
     * Table for mapping primitive class names/signatures to the implementing
     * class object
     */
    private static final HashMap PRIMITIVE_CLASS_MAP = new HashMap();

    /**
     * Table for mapping primitive classes back to their name signature type, which
     * allows a reverse mapping to be performed from a class object into a resolvable
     * signature.
     */
    private static final HashMap CLASS_TO_SIGNATURE_MAP = new HashMap();


    /**
     * Setup the primitives map.  We make any entry for each primitive class using both the
     * human readable name and the method signature shorthand type.
     */
    static {
        PRIMITIVE_CLASS_MAP.put("boolean", boolean.class);
        PRIMITIVE_CLASS_MAP.put("Z", boolean.class);
        PRIMITIVE_CLASS_MAP.put("byte", byte.class);
        PRIMITIVE_CLASS_MAP.put("B", byte.class);
        PRIMITIVE_CLASS_MAP.put("char", char.class);
        PRIMITIVE_CLASS_MAP.put("C", char.class);
        PRIMITIVE_CLASS_MAP.put("short", short.class);
        PRIMITIVE_CLASS_MAP.put("S", short.class);
        PRIMITIVE_CLASS_MAP.put("int", int.class);
        PRIMITIVE_CLASS_MAP.put("I", int.class);
        PRIMITIVE_CLASS_MAP.put("long", long.class);
        PRIMITIVE_CLASS_MAP.put("J", long.class);
        PRIMITIVE_CLASS_MAP.put("float", float.class);
        PRIMITIVE_CLASS_MAP.put("F", float.class);
        PRIMITIVE_CLASS_MAP.put("double", double.class);
        PRIMITIVE_CLASS_MAP.put("D", double.class);
        PRIMITIVE_CLASS_MAP.put("void", void.class);
        PRIMITIVE_CLASS_MAP.put("V", void.class);

        // Now build a reverse mapping table.  The table above has a many-to-one mapping for
        // class names.  To do the reverse, we need to pick just one.  As long as the
        // returned name supports "round tripping" of the requests, this will work fine.

        CLASS_TO_SIGNATURE_MAP.put(boolean.class, "Z");
        CLASS_TO_SIGNATURE_MAP.put(byte.class, "B");
        CLASS_TO_SIGNATURE_MAP.put(char.class, "C");
        CLASS_TO_SIGNATURE_MAP.put(short.class, "S");
        CLASS_TO_SIGNATURE_MAP.put(int.class, "I");
        CLASS_TO_SIGNATURE_MAP.put(long.class, "J");
        CLASS_TO_SIGNATURE_MAP.put(float.class, "F");
        CLASS_TO_SIGNATURE_MAP.put(double.class, "D");
        CLASS_TO_SIGNATURE_MAP.put(void.class, "V");
    }


    /**
     * Load a class that matches the requested name, using the provided class loader context.
     * <p/>
     * The class name may be a standard class name, the name of a primitive type Java
     * reflection class (e.g., "boolean" or "int"), or a type in method type signature
     * encoding.  Array classes in either encoding form are also processed.
     *
     * @param className The name of the required class.
     * @param classLoader The class loader used to resolve the class object.
     * @return The Class object resolved from "className".
     * @throws ClassNotFoundException When unable to resolve the class object.
     * @throws IllegalArgumentException If either argument is null.
     */
    public static Class loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {

        // the tests require IllegalArgumentExceptions for null values on either of these.
        if (className == null) {
            throw new IllegalArgumentException("className is null");
        }

        if (classLoader == null) {
            throw new IllegalArgumentException("classLoader is null");
        }
        // The easiest case is a proper class name.  We just have the class loader resolve this.
        // If the class loader throws a ClassNotFoundException, then we need to check each of the
        // special name encodings we support.
        try {
            return classLoader.loadClass(className);
        } catch (ClassNotFoundException ignore) {
            // if not found, continue on to the other name forms.
        }


        // The second easiest version to resolve is a direct map to a primitive type name
        // or method signature.  Check our name-to-class map for one of those.
        Class resolvedClass = (Class) PRIMITIVE_CLASS_MAP.get(className);
        if (resolvedClass != null) {
            return resolvedClass;
        }

        // Class names in method signature have the format "Lfully.resolved.name;",
        // so if it ends in a semicolon and begins with an "L", this must be in
        // this format.  Have the class loader try to load this.  There are no other
        // options if this fails, so just allow the class loader to throw the
        // ClassNotFoundException.
        if (className.endsWith(";") && className.startsWith("L")) {
            // pick out the name portion
            String typeName = className.substring(1, className.length() - 1);
            // and delegate the loading to the class loader.
            return classLoader.loadClass(typeName);
        }

        // All we have left now are the array types.  Method signature array types
        // have a series of leading "[" characters to specify the number of dimensions.
        // The other array type we handle uses trailing "[]" for the dimensions, just
        // like the Java language syntax.

        // first check for the signature form ([[[[type).
        if (className.charAt(0) == '[') {
            // we have at least one array marker, now count how many leading '['s we have
            // to get the dimension count.
            int count = 0;
            int nameLen = className.length();

            while (count < nameLen && className.charAt(count) == '[') {
                count++;
            }

            // pull of the name subtype, which is everything after the last '['
            String arrayTypeName = className.substring(count, className.length());
            // resolve the type using a recursive call, which will load any of the primitive signature
            // types as well as class names.
            Class arrayType = loadClass(arrayTypeName, classLoader);

            // Resolving array types require a little more work.  The array classes are
            // created dynamically when the first instance of a given dimension and type is
            // created.  We need to create one using reflection to do this.
            return getArrayClass(arrayType, count);
        }


        // ok, last chance.  Now check for an array specification in Java language
        // syntax.  This will be a type name followed by pairs of "[]" to indicate
        // the number of dimensions.
        if (className.endsWith("[]")) {
            // get the base component class name and the arrayDimensions
            int count = 0;
            int position = className.length();

            while (position > 1 && className.substring(position - 2, position).equals("[]")) {
                // count this dimension
                count++;
                // and step back the probe position.
                position -= 2;
            }

            // position now points at the location of the last successful test.  This makes it
            // easy to pick off the class name.

            String typeName = className.substring(0, position);

            // load the base type, again, doing this recursively
            Class arrayType = loadClass(typeName, classLoader);
            // and turn this into the class object
            return getArrayClass(arrayType, count);
        }

        throw new ClassNotFoundException("Could not load class " + className + " from unknown classloader; " + classLoader);
    }


    /**
     * Map a class object back to a class name.  The returned class object
     * must be "round trippable", which means
     * <p/>
     * type == ClassLoading.loadClass(ClassLoading.getClassName(type), classLoader)
     * <p/>
     * must be true.  To ensure this, the class name is always returned in
     * method signature format.
     *
     * @param type The class object we convert into name form.
     * @return A string representation of the class name, in method signature
     *         format.
     */
    public static String getClassName(Class type) {
        StringBuffer name = new StringBuffer();

        // we test these in reverse order from the resolution steps,
        // first handling arrays, then primitive types, and finally
        // "normal" class objects.

        // First handle arrays.  If a class is an array, the type is
        // element stored at that level.  So, for a 2-dimensional array
        // of ints, the top-level type will be "[I".  We need to loop
        // down the hierarchy until we hit a non-array type.
        while (type.isArray()) {
            // add another array indicator at the front of the name,
            // and continue with the next type.
            name.append('[');
            type = type.getComponentType();
        }

        // we're down to the base type.  If this is a primitive, then
        // we poke in the single-character type specifier.
        if (type.isPrimitive()) {
            name.append((String) CLASS_TO_SIGNATURE_MAP.get(type));
        }
        // a "normal" class.  This gets expressing using the "Lmy.class.name;" syntax.
        else {
            name.append('L');
            name.append(type.getName());
            name.append(';');
        }
        return name.toString();
    }

    private static Class getArrayClass(Class type, int dimension) {
        // Array.newInstance() requires an array of the requested number of dimensions
        // that gives the size for each dimension.  We just request 0 in each of the
        // dimensions, which is not unlike a black hole singularity.
        int[] dimensions = new int[dimension];
        // create an instance and return the associated class object.
        return Array.newInstance(type, dimensions).getClass();
    }

    public static Set getAllTypes(Class type) {
        Set allTypes = new LinkedHashSet();
        allTypes.add(type);
        allTypes.addAll(getAllSuperClasses(type));
        allTypes.addAll(getAllInterfaces(type));
        return allTypes;
    }

    private static Set getAllSuperClasses(Class clazz) {
        Set allSuperClasses = new LinkedHashSet();
        for (Class superClass = clazz.getSuperclass(); superClass != null; superClass = superClass.getSuperclass()) {
            allSuperClasses.add(superClass);
        }
        return allSuperClasses;
    }

    private static Set getAllInterfaces(Class clazz) {
        Set allInterfaces = new LinkedHashSet();
        LinkedList stack = new LinkedList();
        stack.addAll(Arrays.asList(clazz.getInterfaces()));
        while (!stack.isEmpty()) {
            Class intf = (Class) stack.removeFirst();
            if (!allInterfaces.contains(intf)) {
                allInterfaces.add(intf);
                stack.addAll(Arrays.asList(intf.getInterfaces()));
            }
        }
        return allInterfaces;
    }

    public static Set reduceInterfaces(Set source) {
        Class[] classes = (Class[]) source.toArray(new Class[source.size()]);
        classes = reduceInterfaces(classes);
        return new LinkedHashSet(Arrays.asList(classes));
    }

    /**
     * If there are multiple interfaces, and some of them extend each other,
     * eliminate the superclass in favor of the subclasses that extend them.
     *
     * If one of the entries is a class (not an interface), make sure it's
     * the first one in the array.  If more than one of the entries is a
     * class, throws an IllegalArgumentException
     *
     * @param source the original list of interfaces
     * @return the equal or smaller list of interfaces
     */
    public static Class[] reduceInterfaces(Class[] source) {
        // use a copy of the source array
        source = (Class[]) source.clone();

        for (int leftIndex = 0; leftIndex < source.length-1; leftIndex++) {
            Class left = source[leftIndex];
            if(left == null) {
                continue;
            }

            for (int rightIndex = leftIndex +1; rightIndex < source.length; rightIndex++) {
                Class right = source[rightIndex];
                if(right == null) {
                    continue;
                }

                if(left == right || right.isAssignableFrom(left)) {
                    // right is the same as class or a sub class of left
                    source[rightIndex] = null;
                } else if(left.isAssignableFrom(right)) {
                    // left is the same as class or a sub class of right
                    source[leftIndex] = null;

                    // the left has been eliminated; move on to the next left
                    break;
                }
            }
        }

        Class clazz = null;
        for (int i = 0; i < source.length; i++) {
            if (source[i] != null && !source[i].isInterface()) {
                if (clazz != null) {
                    throw new IllegalArgumentException("Source contains two classes which are not subclasses of each other: " + clazz.getName() + ", " + source[i].getName());
                }
                clazz = source[i];
                source[i] = null;
            }
        }

        List list = new ArrayList(source.length);
        if (clazz != null) list.add(clazz);
        for (int i = 0; i < source.length; i++) {
            if(source[i] != null) {
                list.add(source[i]);
            }
        }
        return (Class[]) list.toArray(new Class[list.size()]);
    }
}