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