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.commons.discovery.resource;
018
019import java.util.LinkedList;
020import java.util.List;
021
022import org.apache.commons.discovery.jdk.JDKHooks;
023
024/**
025 * There are many different contexts in which
026 * loaders can be used.  This provides a holder
027 * for a set of class loaders, so that they
028 * don't have to be build back up everytime...
029 */
030public class ClassLoaders {
031
032    protected List<ClassLoader> classLoaders = new LinkedList<ClassLoader>();
033
034    /**
035     * Construct a new class loader set.
036     */
037    public ClassLoaders() {
038    }
039
040    /**
041     * Returns the size of class loaders set.
042     *
043     * @return The size of class loaders set
044     */
045    public int size() {
046        return classLoaders.size();
047    }
048
049    /**
050     * Returns the class loader positioned at the given index.
051     *
052     * @param idx The index the class loader has to be retrieved from
053     * @return The class loader positioned at the given index
054     */
055    public ClassLoader get(int idx) {
056        return classLoaders.get(idx);
057    }
058
059    /**
060     * Specify a new class loader to be used in searching.
061     *
062     * The order of loaders determines the order of the result.
063     * It is recommended to add the most specific loaders first;
064     * {@code null} class loaders are discarded.
065     *
066     * @param classLoader The class loader has to added in the set
067     */
068    public void put(ClassLoader classLoader) {
069        if (classLoader != null) {
070            classLoaders.add(classLoader);
071        }
072    }
073
074    /**
075     * Specify a new class loader to be used in searching.
076     * The order of loaders determines the order of the result.
077     * It is recommended to add the most specific loaders first;
078     * {@code null} class loaders are discarded.
079     *
080     * @param classLoader  The class loader has to added in the set
081     * @param prune if true, verify that the class loader is
082     *              not an Ancestor (@see isAncestor) before
083     *              adding it to our list.
084     */
085    public void put(ClassLoader classLoader, boolean prune) {
086        if (classLoader != null  &&  !(prune && isAncestor(classLoader))) {
087            classLoaders.add(classLoader);
088        }
089    }
090
091    /**
092     * Check to see if <code>classLoader</code> is an
093     * ancestor of any contained class loader.
094     *
095     * This can be used to eliminate redundant class loaders
096     * IF all class loaders defer to parent class loaders
097     * before resolving a class.
098     *
099     * It may be that this is not always true.  Therefore,
100     * this check is not done internally to eliminate
101     * redundant class loaders, but left to the discretion
102     * of the user.
103     *
104     * @param classLoader The class loader under test
105     * @return true, if the class loader under test is an ancestor
106     *         of any contained class loader, false otherwise
107     */
108    public boolean isAncestor(final ClassLoader classLoader) {
109        /* bootstrap classloader, at root of all trees! */
110        if (classLoader == null) {
111            return true;
112        }
113
114        for (int idx = 0; idx < size(); idx++) {
115            for (ClassLoader walker = get(idx);
116                    walker != null;
117                    walker = walker.getParent()) {
118                if (walker == classLoader) {
119                    return true;
120                }
121            }
122        }
123        return false;
124    }
125
126    /**
127     * Utility method.  Returns a preloaded ClassLoaders instance
128     * containing the following class loaders, in order:
129     *
130     * <ul>
131     *   <li>spi.getClassLoader</li>
132     *   <li>seeker.getClassLoader</li>
133     *   <li>System Class Loader</li>
134     * </ul>
135     *
136     * Note that the thread context class loader is NOT present.
137     * This is a reasonable set of loaders to try if the resource to be found
138     * should be restricted to a libraries containing the SPI and Factory.
139     *
140     * @param spi WHAT is being looked for (an implementation of this class,
141     *            a default property file related to this class).
142     * @param factory WHO is performing the lookup.
143     * @param prune Determines if ancestors are allowed to be loaded or not.
144     * @return The class loaders holder
145     */    
146    public static ClassLoaders getLibLoaders(Class<?> spi, Class<?> factory, boolean prune) {
147        ClassLoaders loaders = new ClassLoaders();
148
149        if (spi != null) {
150            loaders.put(spi.getClassLoader());
151        }
152        if (factory != null) {
153            loaders.put(factory.getClassLoader(), prune);
154        }
155        loaders.put(JDKHooks.getJDKHooks().getSystemClassLoader(), prune);
156
157        return loaders;
158    }
159
160    /**
161     * Utility method.  Returns a preloaded ClassLoaders instance
162     * containing the following class loaders, in order:
163     *
164     * <ul>
165     *   <li>Thread Context Class Loader</li>
166     *   <li>spi.getClassLoader</li>
167     *   <li>seeker.getClassLoader</li>
168     *   <li>System Class Loader</li>
169     * </ul>
170     *
171     * Note that the thread context class loader IS  present.
172     * This is a reasonable set of loaders to try if the resource to be found
173     * may be provided by an application.
174     *
175     * @param spi WHAT is being looked for (an implementation of this class,
176     *            a default property file related to this class).
177     * @param factory WHO is performing the lookup (factory).
178     * @param prune Determines if ancestors are allowed to be loaded or not.
179     * @return The class loaders holder
180     */
181    public static ClassLoaders getAppLoaders(Class<?> spi, Class<?> factory, boolean prune) {
182        ClassLoaders loaders = new ClassLoaders();
183
184        loaders.put(JDKHooks.getJDKHooks().getThreadContextClassLoader());
185        if (spi != null) {
186            loaders.put(spi.getClassLoader(), prune);
187        }
188        if (factory != null) {
189            loaders.put(factory.getClassLoader(), prune);
190        }
191        loaders.put(JDKHooks.getJDKHooks().getSystemClassLoader(), prune);
192
193        return loaders;
194    }
195
196}