blob: d065ebc0c69539bff46554e2fa46b205f2e8de98 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
* is available at http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* VMware Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.util.internal;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.osgi.framework.Bundle;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
* Class utility used internally. Contains mainly class inheritance mechanisms used when creating OSGi service proxies.
*
* @author Costin Leau
*
*/
public abstract class ClassUtils {
private static class ReadOnlySetFromMap<E> implements Set<E> {
private final Set<E> keys;
public ReadOnlySetFromMap(Map<E, ?> lookupMap) {
keys = lookupMap.keySet();
}
public boolean add(Object o) {
throw new UnsupportedOperationException();
}
public boolean addAll(Collection<? extends E> c) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
public boolean contains(Object o) {
return keys.contains(o);
}
public boolean containsAll(Collection<?> c) {
return keys.containsAll(c);
}
public boolean isEmpty() {
return keys.isEmpty();
}
public Iterator<E> iterator() {
return keys.iterator();
}
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
public int size() {
return keys.size();
}
public Object[] toArray() {
return keys.toArray();
}
public <T> T[] toArray(T[] array) {
return keys.toArray(array);
}
public String toString() {
return keys.toString();
}
public int hashCode() {
return keys.hashCode();
}
public boolean equals(Object o) {
return o == this || keys.equals(o);
}
}
public enum ClassSet {
/** Include only the interfaces inherited from superclasses or implemented by the current class */
INTERFACES,
/** Include only the class hierarchy (interfaces are excluded) */
CLASS_HIERARCHY,
/** Include all inherited classes (classes or interfaces) */
ALL_CLASSES
}
/**
* List of special class loaders, outside OSGi, that might be used by the user through boot delegation. read-only.
*/
public static final List<ClassLoader> knownNonOsgiLoaders;
/**
* Set of special class loaders, outside OSGi, that might be used by the user through boot delegation. read-only.
*/
public static final Set<ClassLoader> knownNonOsgiLoadersSet;
// add the known, non-OSGi class loaders
// note that the order is important
static {
// start with the framework class loader
// then get all its parents (normally the this should be fwk -> (*) -> app -> ext -> boot)
// where (*) represents some optional loaders for cases where the framework is embedded
final Map<ClassLoader, Boolean> lookupMap = new ConcurrentHashMap<ClassLoader, Boolean>(8);
final List<ClassLoader> lookupList = Collections.synchronizedList(new ArrayList<ClassLoader>());
final ClassLoader classLoader = getFwkClassLoader();
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
addNonOsgiClassLoader(classLoader, lookupList, lookupMap);
// get the system class loader
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
addNonOsgiClassLoader(sysLoader, lookupList, lookupMap);
return null;
}
});
} else {
addNonOsgiClassLoader(classLoader, lookupList, lookupMap);
// get the system class loader
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
addNonOsgiClassLoader(sysLoader, lookupList, lookupMap);
}
// wrap the fields as read-only collections
knownNonOsgiLoaders = Collections.unmodifiableList(lookupList);
knownNonOsgiLoadersSet = new ReadOnlySetFromMap<ClassLoader>(lookupMap);
}
public static ClassLoader getFwkClassLoader() {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return Bundle.class.getClassLoader();
}
});
} else {
return Bundle.class.getClassLoader();
}
}
/**
* Special static method used during the class initialization.
*
* @param classLoader non OSGi class loader
*/
private static void addNonOsgiClassLoader(ClassLoader classLoader, List<ClassLoader> list,
Map<ClassLoader, Boolean> map) {
while (classLoader != null) {
synchronized (list) {
if (!map.containsKey(classLoader)) {
list.add(classLoader);
map.put(classLoader, Boolean.TRUE);
}
}
classLoader = classLoader.getParent();
}
}
/**
* Simple class loading abstraction working on both ClassLoader and Bundle classes.
*
* @author Costin Leau
*
*/
private static class ClassLoaderBridge {
private final Bundle bundle;
private final ClassLoader classLoader;
public ClassLoaderBridge(Bundle bundle) {
Assert.notNull(bundle);
this.bundle = bundle;
this.classLoader = null;
}
public ClassLoaderBridge(ClassLoader classLoader) {
Assert.notNull(classLoader);
this.classLoader = classLoader;
this.bundle = null;
}
public Class<?> loadClass(String className) throws ClassNotFoundException {
return (bundle == null ? classLoader.loadClass(className) : bundle.loadClass(className));
}
public boolean canSee(String className) {
return (bundle == null ? org.springframework.util.ClassUtils.isPresent(className, classLoader) : isPresent(
className, bundle));
}
}
/**
* Returns an array of parent classes for the given class. The mode paramater indicates whether only interfaces
* should be included, classes or both.
*
* This method is normally used for publishing services and determing the
* {@link org.osgi.framework.Constants#OBJECTCLASS} property.
*
* <p/> Note: this method does class expansion returning parent as well as children classes.
*
* </p>
*
* @see #INCLUDE_ALL_CLASSES
* @see #INCLUDE_CLASS_HIERARCHY
* @see #INCLUDE_INTERFACES
*
* @param clazz
* @param mode
*
* @return array of classes extended or implemented by the given class
*/
public static Class<?>[] getClassHierarchy(Class<?> clazz, ClassSet inclusion) {
Class<?>[] classes = null;
if (clazz != null) {
Set<Class<?>> composingClasses = new LinkedHashSet<Class<?>>();
boolean includeClasses =
(inclusion.equals(ClassSet.CLASS_HIERARCHY) || inclusion.equals(ClassSet.ALL_CLASSES));
boolean includeInterfaces =
(inclusion.equals(ClassSet.INTERFACES) || inclusion.equals(ClassSet.ALL_CLASSES));
Class<?> clz = clazz;
do {
if (includeClasses) {
composingClasses.add(clz);
}
if (includeInterfaces) {
CollectionUtils.mergeArrayIntoCollection(getAllInterfaces(clz), composingClasses);
}
clz = clz.getSuperclass();
} while (clz != null && clz != Object.class);
classes = (Class[]) composingClasses.toArray(new Class[composingClasses.size()]);
}
return (classes == null ? new Class[0] : classes);
}
/**
* Sugar method that determines the class hierarchy of a given class and then filtering based on the given
* classloader. If a null classloader, the one of the given class will be used.
*
* @param clazz
* @param mode
* @param loader
* @return
*/
public static Class<?>[] getVisibleClassHierarchy(Class<?> clazz, ClassSet inclusion, ClassLoader loader) {
if (clazz == null)
return new Class[0];
return getVisibleClasses(getClassHierarchy(clazz, inclusion), getClassLoader(clazz));
}
/**
* 'Sugar' method that determines the class hierarchy of the given class, returning only the classes visible to the
* given bundle.
*
* @param clazz the class for which the hierarchy has to be determined
* @param mode discovery mode
* @param bundle bundle used for class visibility
* @return array of visible classes part of the hierarchy
*/
public static Class<?>[] getVisibleClassHierarchy(Class<?> clazz, ClassSet inclusion, Bundle bundle) {
return getVisibleClasses(getClassHierarchy(clazz, inclusion), bundle);
}
/**
* Given an array of classes, eliminates the ones that cannot be loaded by the given classloader.
*
* @return
*/
public static Class<?>[] getVisibleClasses(Class<?>[] classes, ClassLoader classLoader) {
return getVisibleClasses(classes, new ClassLoaderBridge(classLoader));
}
/**
* Given an array of classes, eliminates the ones that cannot be loaded by the given bundle.
*
* @return
*/
public static Class<?>[] getVisibleClasses(Class<?>[] classes, Bundle bundle) {
return getVisibleClasses(classes, new ClassLoaderBridge(bundle));
}
private static Class<?>[] getVisibleClasses(Class<?>[] classes, ClassLoaderBridge loader) {
if (ObjectUtils.isEmpty(classes))
return classes;
Set<Class<?>> classSet = new LinkedHashSet<Class<?>>(classes.length);
CollectionUtils.mergeArrayIntoCollection(classes, classSet);
// filter class collection based on visibility
for (Iterator<Class<?>> iter = classSet.iterator(); iter.hasNext();) {
Class<?> clzz = iter.next();
if (!loader.canSee(clzz.getName())) {
iter.remove();
}
}
return (Class[]) classSet.toArray(new Class[classSet.size()]);
}
/**
* Gets all interfaces implemented by the given class. This method returns both parent and children interfaces (i.e.
* Map and SortedMap).
*
* @param clazz
* @return all interfaces implemented by the given class.
*/
public static Class<?>[] getAllInterfaces(Class<?> clazz) {
Assert.notNull(clazz);
return getAllInterfaces(clazz, new LinkedHashSet<Class<?>>(8));
}
/**
* Gets all the interfaces recursively.
*
* @param clazz
* @param interfaces
* @return
*/
private static Class<?>[] getAllInterfaces(Class<?> clazz, Set<Class<?>> interfaces) {
Class<?>[] intfs = clazz.getInterfaces();
CollectionUtils.mergeArrayIntoCollection(intfs, interfaces);
for (int i = 0; i < intfs.length; i++) {
getAllInterfaces(intfs[i], interfaces);
}
return (Class[]) interfaces.toArray(new Class[interfaces.size()]);
}
/**
* Checks the present of a class inside a bundle. This method returns true if the given bundle can load the given
* class or false otherwise.
*
* @param className
* @param bundle
* @return
*/
public static boolean isPresent(String className, Bundle bundle) {
Assert.hasText(className);
Assert.notNull(bundle);
try {
bundle.loadClass(className);
return true;
} catch (Exception cnfe) {
return false;
}
}
/**
* Returns the classloader for the given class. This method deals with JDK classes which return by default, a null
* classloader.
*
* @param clazz
* @return
*/
public static ClassLoader getClassLoader(Class<?> clazz) {
Assert.notNull(clazz);
ClassLoader loader = clazz.getClassLoader();
return (loader == null ? ClassLoader.getSystemClassLoader() : loader);
}
/**
* Returns an array of class string names for the given classes.
*
* @param array
* @return
*/
public static String[] toStringArray(Class<?>[] array) {
if (ObjectUtils.isEmpty(array))
return new String[0];
String[] strings = new String[array.length];
for (int i = 0; i < array.length; i++) {
strings[i] = array[i].getName();
}
return strings;
}
/**
* Determines if multiple classes(not interfaces) are specified, without any relation to each other. Interfaces will
* simply be ignored.
*
* @param classes an array of classes
* @return true if at least two classes unrelated to each other are found, false otherwise
*/
public static boolean containsUnrelatedClasses(Class<?>[] classes) {
if (ObjectUtils.isEmpty(classes))
return false;
Class<?> clazz = null;
// check if is more then one class specified
for (int i = 0; i < classes.length; i++) {
if (!classes[i].isInterface()) {
if (clazz == null)
clazz = classes[i];
// check relationship
else {
if (clazz.isAssignableFrom(classes[i]))
// clazz is a parent, switch with the child
clazz = classes[i];
else if (!classes[i].isAssignableFrom(clazz))
return true;
}
}
}
// everything is in order
return false;
}
/**
* Parses the given class array and eliminate parents of existing classes. Useful when creating proxies to minimize
* the number of implemented interfaces and redundant class information.
*
* @see #containsUnrelatedClasses(Class[])
* @see #configureFactoryForClass(ProxyFactory, Class[])
*
* @param classes array of classes
* @return a new array without superclasses
*/
public static Class<?>[] removeParents(Class<?>[] classes) {
if (ObjectUtils.isEmpty(classes))
return new Class[0];
List<Class<?>> clazz = new ArrayList<Class<?>>(classes.length);
for (int i = 0; i < classes.length; i++) {
clazz.add(classes[i]);
}
// remove null elements
while (clazz.remove(null)) {
}
// only one class is allowed
// there can be multiple interfaces
// parents of classes inside the array are removed
boolean dirty;
do {
dirty = false;
for (int i = 0; i < clazz.size(); i++) {
Class<?> currentClass = clazz.get(i);
for (int j = 0; j < clazz.size(); j++) {
if (i != j) {
if (currentClass.isAssignableFrom(clazz.get(j))) {
clazz.remove(i);
i--;
dirty = true;
break;
}
}
}
}
} while (dirty);
return (Class[]) clazz.toArray(new Class[clazz.size()]);
}
/**
* Based on the given class, properly instructs the ProxyFactory proxies. For additional sanity checks on the passed
* classes, check the methods below.
*
* @see #containsUnrelatedClasses(Class[])
* @see #removeParents(Class[])
*
* @param factory
* @param classes
*/
public static void configureFactoryForClass(ProxyFactory factory, Class<?>[] classes) {
if (ObjectUtils.isEmpty(classes))
return;
for (int i = 0; i < classes.length; i++) {
Class<?> clazz = classes[i];
if (clazz.isInterface()) {
factory.addInterface(clazz);
} else {
factory.setTargetClass(clazz);
factory.setProxyTargetClass(true);
}
}
}
/**
* Loads classes with the given name, using the given classloader. {@link ClassNotFoundException} exceptions are
* being ignored. The return class array will not contain duplicates.
*
* @param classNames array of classnames to load (in FQN format)
* @param classLoader classloader used for loading the classes
* @return an array of classes (can be smaller then the array of class names) w/o duplicates
*/
public static Class<?>[] loadClassesIfPossible(String[] classNames, ClassLoader classLoader) {
if (ObjectUtils.isEmpty(classNames))
return new Class[0];
Assert.notNull(classLoader, "classLoader is required");
Set<Class<?>> classes = new LinkedHashSet<Class<?>>(classNames.length);
for (int i = 0; i < classNames.length; i++) {
try {
classes.add(classLoader.loadClass(classNames[i]));
} catch (ClassNotFoundException ex) {
// ignore
}
}
return (Class[]) classes.toArray(new Class[classes.size()]);
}
/**
* Loads and returns the classes given as names, using the given class loader. Throws IllegalArgument exception if
* the classes cannot be loaded.
*
* @param classNames array of class names
* @param classLoader class loader for loading the classes
* @return the loaded classes
*/
public static Class<?>[] loadClasses(String[] classNames, ClassLoader classLoader) {
if (ObjectUtils.isEmpty(classNames))
return new Class[0];
Assert.notNull(classLoader, "classLoader is required");
Set<Class<?>> classes = new LinkedHashSet<Class<?>>(classNames.length);
for (int i = 0; i < classNames.length; i++) {
classes.add(org.springframework.util.ClassUtils.resolveClassName(classNames[i], classLoader));
}
return (Class[]) classes.toArray(new Class[classes.size()]);
}
/**
* Excludes classes from the given array, which match the given modifier.
*
* @see Modifier
*
* @param classes array of classes (can be null)
* @param modifier class modifier
* @return array of classes (w/o duplicates) which does not have the given modifier
*/
public static Class<?>[] excludeClassesWithModifier(Class<?>[] classes, int modifier) {
if (ObjectUtils.isEmpty(classes))
return new Class[0];
Set<Class<?>> clazzes = new LinkedHashSet<Class<?>>(classes.length);
for (int i = 0; i < classes.length; i++) {
if ((modifier & classes[i].getModifiers()) == 0)
clazzes.add(classes[i]);
}
return (Class[]) clazzes.toArray(new Class[clazzes.size()]);
}
/**
* Returns the first matching class from the given array, that doens't belong to common libraries such as the JDK or
* OSGi API. Useful for filtering OSGi services by type to prevent class cast problems.
*
* <p/> No sanity checks are done on the given array class.
*
* @param classes array of classes
* @return a 'particular' (non JDK/OSGi) class if one is found. Else the first available entry is returned.
*/
public static Class<?> getParticularClass(Class<?>[] classes) {
boolean hasSecurity = (System.getSecurityManager() != null);
for (int i = 0; i < classes.length; i++) {
final Class<?> clazz = classes[i];
ClassLoader loader = null;
if (hasSecurity) {
loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return clazz.getClassLoader();
}
});
} else {
loader = clazz.getClassLoader();
}
// quick boot/system check
if (loader != null) {
// consider known loaders
if (!knownNonOsgiLoadersSet.contains(loader)) {
return clazz;
}
}
}
return (ObjectUtils.isEmpty(classes) ? null : classes[0]);
}
}