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     */
017    package org.apache.xbean.classloader;
018    
019    import java.io.IOException;
020    import java.net.URL;
021    import java.net.URLStreamHandlerFactory;
022    import java.util.ArrayList;
023    import java.util.Arrays;
024    import java.util.Collection;
025    import java.util.Collections;
026    import java.util.Enumeration;
027    import 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     */
040    public 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    }