001    /*
002    $Id: RootLoader.java,v 1.6 2005/09/06 08:19:32 hmeling Exp $
003    
004    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006    Redistribution and use of this software and associated documentation
007    ("Software"), with or without modification, are permitted provided
008    that the following conditions are met:
009    
010    1. Redistributions of source code must retain copyright
011       statements and notices.  Redistributions must also contain a
012       copy of this document.
013    
014    2. Redistributions in binary form must reproduce the
015       above copyright notice, this list of conditions and the
016       following disclaimer in the documentation and/or other
017       materials provided with the distribution.
018    
019    3. The name "groovy" must not be used to endorse or promote
020       products derived from this Software without prior written
021       permission of The Codehaus.  For written permission,
022       please contact info@codehaus.org.
023    
024    4. Products derived from this Software may not be called "groovy"
025       nor may "groovy" appear in their names without prior written
026       permission of The Codehaus. "groovy" is a registered
027       trademark of The Codehaus.
028    
029    5. Due credit should be given to The Codehaus -
030       http://groovy.codehaus.org/
031    
032    THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033    ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034    NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035    FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036    THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043    OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045    */
046    package org.codehaus.groovy.tools;
047    
048    import java.io.IOException;
049    import java.net.URL;
050    import java.net.URLClassLoader;
051    import java.util.Enumeration;
052    
053    /**
054     * This ClassLoader should be used as root of class loaders. Any
055     * RootLoader does have it's own classpath. When searching for a 
056     * class or resource this classpath will be used. Parent 
057     * Classloaders are ignored first. If a class or resource 
058     * can't be found in the classpath of the RootLoader, then parent is
059     * checked.
060     * 
061     * <b>Note:</b> this is very against the normal behavior of 
062     * classloaders. Normal is to frist check parent and then look in
063     * the ressources you gave this classloader.
064     * 
065     * It's possible to add urls to the classpath at runtime through
066     * @see #addURL(URL)
067     * 
068     * <b>Why using RootLoader?</b>
069     * If you have to load classes with multiple classloaders and a
070     * classloader does know a class which depends on a class only 
071     * a child of this loader does know, then you won't be able to 
072     * load the class. To load the class the child is not allowed 
073     * to redirect it's search for the class to the parent first.
074     * That way the child can load the class. If the child does not
075     * have all classes to do this, this fails of course.
076     *  
077     * For example:
078     *  
079     *  <pre>
080     *  parentLoader   (has classpath: a.jar;c.jar)
081     *      |
082     *      |
083     *  childLoader    (has classpath: a.jar;b.jar;c.jar)
084     *  </pre>
085     *  
086     *  class C (from c.jar) extends B (from b.jar)
087     *  
088     *  childLoader.find("C")
089     *  --> parentLoader does know C.class, try to load it
090     *  --> to load C.class it has to load B.class
091     *  --> parentLoader is unable to find B.class in a.jar or c.jar
092     *  --> NoClassDefFoundException!
093     *  
094     *  if childLoader had tried to load the class by itself, there
095     *  would be no problem. Changing childLoader to be a RootLoader 
096     *  instance will solve that problem.
097     *   
098     * @author Jochen Theodorou
099     */
100    public class RootLoader extends ClassLoader {
101    
102        private ClassLoader parent; 
103        private InnerLoader inner;
104        
105        private class InnerLoader extends URLClassLoader {
106            public InnerLoader(URL[] urls) {
107                super(urls,null);
108            }        
109            public void addPathEntry(URL url) {
110                addURL(url);
111            }
112            protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
113                try {
114                    return super.loadClass(name, resolve);
115                } catch (ClassNotFoundException cnfe) {
116                    return RootLoader.this.loadClassByName(name,true,resolve);
117                }
118            }
119        }
120        
121        /**
122         * constructs a new RootLoader without classpath
123         * @param parent the parent Loader
124         */   
125        private RootLoader(ClassLoader parent) {
126            super(parent);
127        }
128        
129        /**
130         * constructs a new RootLoader with a parent loader and an
131         * array of URLs as classpath
132         */
133        public RootLoader(URL[] urls, ClassLoader parent) {
134            this(parent);
135            inner = new InnerLoader(urls);
136        }
137        
138        /**
139         * constructs a new RootLoader with a @see LoaderConfiguration
140         * object which holds the classpath
141         */
142        public RootLoader(LoaderConfiguration lc) {
143            this(RootLoader.class.getClassLoader());
144            Thread.currentThread().setContextClassLoader(this);
145            inner = new InnerLoader(lc.getClassPathUrls());
146        }
147    
148        /**
149         * loads a class using the name of the class
150         */
151        protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
152            return loadClassByName(name,false,resolve);
153        }
154        
155        /**
156         * method to avoid endless loops
157         */
158        private Class loadClassByName(String name, boolean ignoreInner, boolean resolve) throws ClassNotFoundException {
159            // if the searched class can't be found in inner, then try the 
160            // old behavior which searches in parent first
161            if (!ignoreInner) {
162                try {
163                    return inner.loadClass(name);
164                } catch (ClassNotFoundException cnfe) {
165                    // fall through
166                }
167            }
168            return super.loadClass(name,true);
169        }
170        
171        /**
172         * returns the URL of a resource, or null if it is not found
173         */
174        public URL getResource(String name) {
175            URL url = inner.getResource(name);
176            url = super.getResource(name);
177            return url;
178        }    
179        
180        /**
181         * returns an Enumeration of all found ressources. Resources found
182         * in the classpath of this loader are at the beginning of the
183         * returned enumeration
184         */
185        protected Enumeration findResources(String name) throws IOException {
186            final Enumeration enum1 = inner.findResources(name);
187            final Enumeration enum2 = super.findResources(name);
188            return new Enumeration() {
189                public boolean hasMoreElements() {
190                    return enum1.hasMoreElements() || enum2.hasMoreElements();
191                }
192                public Object nextElement() {
193                    if (enum1.hasMoreElements()) return enum1.nextElement();
194                    if (enum2.hasMoreElements()) return enum2.nextElement();
195                    return null;
196                }
197            };
198        }
199     
200        /**
201         * adds an url to the classpath of this classloader
202         */
203        public void addURL(URL url) {
204            inner.addPathEntry(url);
205        }
206        
207        /**
208         * returns all classpath entries of this classloader
209         */
210        public URL[] getURLs() {
211            return inner.getURLs();
212        }
213    }