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