/*
* 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()]);
}
}