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 */
017package org.apache.xbean.classloader;
018
019import java.io.IOException;
020import java.net.URL;
021import java.net.URLStreamHandlerFactory;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.Enumeration;
027import java.util.List;
028
029/**
030 * A MultiParentClassLoader is a simple extension of the URLClassLoader that simply changes the single parent class
031 * loader model to support a list of parent class loaders.  Each operation that accesses a parent, has been replaced
032 * with a operation that checks each parent in order.  This getParent method of this class will always return null,
033 * which may be interperated by the calling code to mean that this class loader is a direct child of the system class
034 * loader.
035 *
036 * @author Dain Sundstrom
037 * @version $Id: MultiParentClassLoader.java 725706 2008-12-11 14:58:11Z gnodet $
038 * @since 2.0
039 */
040public class MultiParentClassLoader extends NamedClassLoader {
041    private final ClassLoader[] parents;
042    private final boolean inverseClassLoading;
043    private final String[] hiddenClasses;
044    private final String[] nonOverridableClasses;
045    private final String[] hiddenResources;
046    private final String[] nonOverridableResources;
047
048    /**
049     * Creates a named class loader with no parents.
050     * @param name the name of this class loader
051     * @param urls the urls from which this class loader will classes and resources
052     */
053    public MultiParentClassLoader(String name, URL[] urls) {
054        this(name, urls, ClassLoader.getSystemClassLoader());
055    }
056
057    /**
058     * Creates a named class loader as a child of the specified parent.
059     * @param name the name of this class loader
060     * @param urls the urls from which this class loader will classes and resources
061     * @param parent the parent of this class loader
062     */
063    public MultiParentClassLoader(String name, URL[] urls, ClassLoader parent) {
064        this(name, urls, new ClassLoader[] {parent});
065    }
066
067    /**
068     * Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory
069     * for accessing the urls..
070     * @param name the name of this class loader
071     * @param urls the urls from which this class loader will classes and resources
072     * @param parent the parent of this class loader
073     * @param factory the URLStreamHandlerFactory used to access the urls
074     */
075    public MultiParentClassLoader(String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
076        this(name, urls, new ClassLoader[] {parent}, factory);
077    }
078
079    /**
080     * Creates a named class loader as a child of the specified parents.
081     * @param name the name of this class loader
082     * @param urls the urls from which this class loader will classes and resources
083     * @param parents the parents of this class loader
084     */
085    public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents) {
086        this(name, urls, parents, false, new String[0], new String[0]);
087    }
088
089    public MultiParentClassLoader(String name, URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) {
090        this(name, urls, new ClassLoader[]{parent}, inverseClassLoading, hiddenClasses, nonOverridableClasses);
091    }
092    
093    /**
094     * Creates a named class loader as a child of the specified parents and using the specified URLStreamHandlerFactory
095     * for accessing the urls..
096     * @param name the name of this class loader
097     * @param urls the urls from which this class loader will classes and resources
098     * @param parents the parents of this class loader
099     * @param factory the URLStreamHandlerFactory used to access the urls
100     */
101    public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents, URLStreamHandlerFactory factory) {
102        super(name, urls, null, factory);
103        this.parents = copyParents(parents);
104        this.inverseClassLoading = false;
105        this.hiddenClasses = new String[0];
106        this.nonOverridableClasses = new String[0];
107        this.hiddenResources = new String[0];
108        this.nonOverridableResources = new String[0];
109    }
110
111    public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, Collection hiddenClasses, Collection nonOverridableClasses) {
112        this(name, urls, parents, inverseClassLoading, (String[]) hiddenClasses.toArray(new String[hiddenClasses.size()]), (String[]) nonOverridableClasses.toArray(new String[nonOverridableClasses.size()]));
113    }
114
115    public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) {
116        super(name, urls);
117        this.parents = copyParents(parents);
118        this.inverseClassLoading = inverseClassLoading;
119        this.hiddenClasses = hiddenClasses;
120        this.nonOverridableClasses = nonOverridableClasses;
121        hiddenResources = toResources(hiddenClasses);
122        nonOverridableResources = toResources(nonOverridableClasses);
123    }
124
125    private static String[] toResources(String[] classes) {
126        String[] resources = new String[classes.length];
127        for (int i = 0; i < classes.length; i++) {
128            String className = classes[i];
129            resources[i] = className.replace('.', '/');
130        }
131        return resources;
132    }
133    
134    private static ClassLoader[] copyParents(ClassLoader[] parents) {
135        ClassLoader[] newParentsArray = new ClassLoader[parents.length];
136        for (int i = 0; i < parents.length; i++) {
137            ClassLoader parent = parents[i];
138            if (parent == null) {
139                throw new NullPointerException("parent[" + i + "] is null");
140            }
141            newParentsArray[i] = parent;
142        }
143        return newParentsArray;
144    }
145
146    /**
147     * Gets the parents of this class loader.
148     * @return the parents of this class loader
149     */
150    public ClassLoader[] getParents() {
151        return parents;
152    }
153
154    /**
155     * {@inheritDoc}
156     */
157    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
158        //
159        // Check if class is in the loaded classes cache
160        //
161        Class cachedClass = findLoadedClass(name);
162        if (cachedClass != null) {
163            return resolveClass(cachedClass, resolve);
164        }
165        
166        //
167        // if we are using inverse class loading, check local urls first
168        //
169        if (inverseClassLoading && !isDestroyed() && !isNonOverridableClass(name)) {
170            try {
171                Class clazz = findClass(name);
172                return resolveClass(clazz, resolve);
173            } catch (ClassNotFoundException ignored) {
174            }
175        }
176
177        //
178        // Check parent class loaders
179        //
180        if (!isHiddenClass(name)) {
181            for (int i = 0; i < parents.length; i++) {
182                ClassLoader parent = parents[i];
183                try {
184                    Class clazz = parent.loadClass(name);
185                    return resolveClass(clazz, resolve);
186                } catch (ClassNotFoundException ignored) {
187                    // this parent didn't have the class; try the next one
188                }
189            }
190        }
191
192        //
193        // if we are not using inverse class loading, check local urls now
194        //
195        // don't worry about excluding non-overridable classes here... we
196        // have alredy checked he parent and the parent didn't have the
197        // class, so we can override now
198        if (!isDestroyed()) {
199            try {
200                Class clazz = findClass(name);
201                return resolveClass(clazz, resolve);
202            } catch (ClassNotFoundException ignored) {
203            }
204        }
205
206        throw new ClassNotFoundException(name + " in classloader " + getName());
207    }
208
209    private boolean isNonOverridableClass(String name) {
210        for (int i = 0; i < nonOverridableClasses.length; i++) {
211            if (name.startsWith(nonOverridableClasses[i])) {
212                return true;
213            }
214        }
215        return false;
216    }
217
218    private boolean isHiddenClass(String name) {
219        for (int i = 0; i < hiddenClasses.length; i++) {
220            if (name.startsWith(hiddenClasses[i])) {
221                return true;
222            }
223        }
224        return false;
225    }
226
227    private Class resolveClass(Class clazz, boolean resolve) {
228        if (resolve) {
229            resolveClass(clazz);
230        }
231        return clazz;
232    }
233
234    /**
235     * {@inheritDoc}
236     */
237    public URL getResource(String name) {
238        if (isDestroyed()) {
239            return null;
240        }
241
242        //
243        // if we are using inverse class loading, check local urls first
244        //
245        if (inverseClassLoading && !isDestroyed() && !isNonOverridableResource(name)) {
246            URL url = findResource(name);
247            if (url != null) {
248                return url;
249            }
250        }
251
252        //
253        // Check parent class loaders
254        //
255        if (!isHiddenResource(name)) {
256            for (int i = 0; i < parents.length; i++) {
257                ClassLoader parent = parents[i];
258                URL url = parent.getResource(name);
259                if (url != null) {
260                    return url;
261                }
262            }
263        }
264
265        //
266        // if we are not using inverse class loading, check local urls now
267        //
268        // don't worry about excluding non-overridable resources here... we
269        // have alredy checked he parent and the parent didn't have the
270        // resource, so we can override now
271        if (!isDestroyed()) {
272            // parents didn't have the resource; attempt to load it from my urls
273            return findResource(name);
274        }
275
276        return null;
277    }
278
279    /**
280     * {@inheritDoc}
281     */
282    public Enumeration findResources(String name) throws IOException {
283        if (isDestroyed()) {
284            return Collections.enumeration(Collections.EMPTY_SET);
285        }
286
287        List resources = new ArrayList();
288
289        //
290        // if we are using inverse class loading, add the resources from local urls first
291        //
292        if (inverseClassLoading && !isDestroyed()) {
293            List myResources = Collections.list(super.findResources(name));
294            resources.addAll(myResources);
295        }
296
297        //
298        // Add parent resources
299        //
300        for (int i = 0; i < parents.length; i++) {
301            ClassLoader parent = parents[i];
302            List parentResources = Collections.list(parent.getResources(name));
303            resources.addAll(parentResources);
304        }
305
306        //
307        // if we are not using inverse class loading, add the resources from local urls now
308        //
309        if (!inverseClassLoading && !isDestroyed()) {
310            List myResources = Collections.list(super.findResources(name));
311            resources.addAll(myResources);
312        }
313
314        return Collections.enumeration(resources);
315    }
316
317    private boolean isNonOverridableResource(String name) {
318        for (int i = 0; i < nonOverridableResources.length; i++) {
319            if (name.startsWith(nonOverridableResources[i])) {
320                return true;
321            }
322        }
323        return false;
324    }
325
326    private boolean isHiddenResource(String name) {
327        for (int i = 0; i < hiddenResources.length; i++) {
328            if (name.startsWith(hiddenResources[i])) {
329                return true;
330            }
331        }
332        return false;
333    }
334
335    /**
336     * {@inheritDoc}
337     */
338    public String toString() {
339        return "[" + getClass().getName() + ":" +
340                " name=" + getName() +
341                " urls=" + Arrays.asList(getURLs()) +
342                " parents=" + Arrays.asList(parents) +
343                "]";
344    }
345}