1/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
2 *
3 * This program and the accompanying materials are made available under
4 * the terms of the Common Public License v1.0 which accompanies this distribution,
5 * and is available at http://www.eclipse.org/legal/cpl-v10.html
6 *
7 * $Id: ClassLoaderResolver.java,v 1.1.1.1.2.2 2004/07/10 03:34:53 vlad_r Exp $
8 */
9package com.vladium.util;
10
11// ----------------------------------------------------------------------------
12/**
13 * This non-instantiable non-subclassable class acts as the global point for
14 * choosing a ClassLoader for dynamic class/resource loading at any point
15 * in an application.
16 *
17 * @see ResourceLoader
18 * @see IClassLoadStrategy
19 * @see DefaultClassLoadStrategy
20 *
21 * @author Vlad Roubtsov, (C) 2003
22 */
23public
24abstract class ClassLoaderResolver
25{
26    // public: ................................................................
27
28    // NOTE: don't use Logger in this class to avoid infinite recursion
29
30    /**
31     * This method selects the "best" classloader instance to be used for
32     * class/resource loading by whoever calls this method. The decision
33     * typically involves choosing between the caller's current, thread context,
34     * system, and other classloaders in the JVM and is made by the {@link IClassLoadStrategy}
35     * instance established by the last call to {@link #setStrategy}.<P>
36     *
37     * This method does not throw.
38     *
39     * @param caller [null input eliminates the caller's current classloader
40     * from consideration]
41     *
42     * @return classloader to be used by the caller ['null' indicates the
43     * primordial loader]
44     */
45    public static synchronized ClassLoader getClassLoader (final Class caller)
46    {
47        final ClassLoadContext ctx = new ClassLoadContext (caller);
48
49        return s_strategy.getClassLoader (ctx);
50    }
51
52    /**
53     * This method selects the "best" classloader instance to be used for
54     * class/resource loading by whoever calls this method. The decision
55     * typically involves choosing between the caller's current, thread context,
56     * system, and other classloaders in the JVM and is made by the {@link IClassLoadStrategy}
57     * instance established by the last call to {@link #setStrategy}.<P>
58     *
59     * This method uses its own caller to set the call context. To be able to
60     * override this decision explicitly, use {@link #getClassLoader(Class)}.<P>
61     *
62     * This method does not throw.
63     *
64     * @return classloader to be used by the caller ['null' indicates the
65     * primordial loader]
66     */
67    public static synchronized ClassLoader getClassLoader ()
68    {
69        final Class caller = getCallerClass (1); // 'caller' can be set to null
70        final ClassLoadContext ctx = new ClassLoadContext (caller);
71
72        return s_strategy.getClassLoader (ctx);
73    }
74
75    /*
76     * Indexes into the current method call context with a given offset. Offset 0
77     * corresponds to the immediate caller, offset 1 corresponds to its caller,
78     * etc.<P>
79     *
80     * Invariant: getCallerClass(0) == class containing code that performs this call
81     */
82    public static Class getCallerClass (final int callerOffset)
83    {
84        if (CALLER_RESOLVER == null) return null; // only happens if <clinit> failed
85
86        return CALLER_RESOLVER.getClassContext () [CALL_CONTEXT_OFFSET + callerOffset];
87    }
88
89    /**
90     * Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
91     * 'loader1'=='loader2']. Of course, this works only for classloaders that
92     * set their parent pointers correctly. 'null' is interpreted as the
93     * primordial loader [i.e., everybody's parent].
94     */
95    public static boolean isChild (final ClassLoader loader1, ClassLoader loader2)
96    {
97        if (loader1 == loader2) return true;
98        if (loader2 == null) return false;
99        if (loader1 == null) return true;
100
101        for ( ; loader2 != null; loader2 = loader2.getParent ())
102        {
103            if (loader2 == loader1) return true;
104        }
105
106        return false;
107    }
108
109    /**
110     * Gets the current classloader selection strategy setting.
111     */
112    public static synchronized IClassLoadStrategy getStrategy ()
113    {
114        return s_strategy;
115    }
116
117    /**
118     * Sets the classloader selection strategy to be used by subsequent calls
119     * to {@link #getClassLoader()}. An instance of {@link ClassLoaderResolver.DefaultClassLoadStrategy}
120     * is in effect if this method is never called.
121     *
122     * @param strategy new strategy [may not be null]
123     * @return previous setting
124     */
125    public static synchronized IClassLoadStrategy setStrategy (final IClassLoadStrategy strategy)
126    {
127        if (strategy == null) throw new IllegalArgumentException ("null input: strategy");
128
129        final IClassLoadStrategy old = s_strategy;
130        s_strategy = strategy;
131
132        return old;
133    }
134
135    // protected: .............................................................
136
137    // package: ...............................................................
138
139    // private: ...............................................................
140
141
142    private static final class DefaultClassLoadStrategy implements IClassLoadStrategy
143    {
144        public ClassLoader getClassLoader (final ClassLoadContext ctx)
145        {
146            if (ctx == null) throw new IllegalArgumentException ("null input: ctx");
147
148            final Class caller = ctx.getCallerClass ();
149            final ClassLoader contextLoader = Thread.currentThread ().getContextClassLoader ();
150
151            ClassLoader result;
152
153            if (caller == null)
154                result = contextLoader;
155            else
156            {
157                final ClassLoader callerLoader = caller.getClassLoader ();
158
159                // if 'callerLoader' and 'contextLoader' are in a parent-child
160                // relationship, always choose the child:
161
162                // SF BUG 975080: change the sibling case to use 'callerLoader'
163                // to work around ANT 1.6.x incorrect classloading model:
164
165                if (isChild (callerLoader, contextLoader))
166                    result = contextLoader;
167                else
168                    result = callerLoader;
169            }
170
171            final ClassLoader systemLoader = ClassLoader.getSystemClassLoader ();
172
173            // precaution for when deployed as a bootstrap or extension class:
174            if (isChild (result, systemLoader))
175                result = systemLoader;
176
177            return result;
178        }
179
180    } // end of nested class
181
182
183    /**
184     * A helper class to get the call context. It subclasses SecurityManager
185     * to make getClassContext() accessible. An instance of CallerResolver
186     * only needs to be created, not installed as an actual security
187     * manager.
188     */
189    private static final class CallerResolver extends SecurityManager
190    {
191        protected Class [] getClassContext ()
192        {
193            return super.getClassContext ();
194        }
195
196    } // end of nested class
197
198
199    private ClassLoaderResolver () {} // prevent subclassing
200
201
202    private static IClassLoadStrategy s_strategy; // initialized in <clinit>
203
204    private static final int CALL_CONTEXT_OFFSET = 2; // may need to change if this class is redesigned
205    private static final CallerResolver CALLER_RESOLVER; // set in <clinit>
206    //private static Throwable CLINIT_FAILURE;
207
208    static
209    {
210        CallerResolver temp = null;
211        try
212        {
213            // this can fail if the current SecurityManager does not allow
214            // RuntimePermission ("createSecurityManager"):
215
216            temp = new CallerResolver ();
217        }
218        catch (Throwable t)
219        {
220            //CLINIT_FAILURE = t;
221        }
222        CALLER_RESOLVER = temp;
223
224        s_strategy = new DefaultClassLoadStrategy ();
225    }
226
227} // end of class
228// ----------------------------------------------------------------------------