001    /*
002     * This file is part of muCommander, http://www.mucommander.com
003     * Copyright (C) 2002-2008 Maxence Bernard
004     *
005     * muCommander is free software; you can redistribute it and/or modify
006     * it under the terms of the GNU General Public License as published by
007     * the Free Software Foundation; either version 3 of the License, or
008     * (at your option) any later version.
009     *
010     * muCommander is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    
019    package com.mucommander.file;
020    
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.net.URL;
024    import java.util.Enumeration;
025    import java.util.Iterator;
026    import java.util.Vector;
027    
028    /**
029     * <code>ClassLoader</code> implementation capable of loading classes from instances of {@link AbstractFile}.
030     * <p>
031     * It's possible to modify this loader's classpath at runtime through the {@link #addFile(AbstractFile)} method.
032     * </p>
033     * @author Nicolas Rinaudo
034     */
035    public class AbstractFileClassLoader extends ClassLoader {
036        // - Instance fields -------------------------------------------------------
037        // -------------------------------------------------------------------------
038        /** All abstract files in which to look for classes and resources. */
039        private Vector files;
040    
041    
042    
043        // - Initialisation -------------------------------------------------------
044        // ------------------------------------------------------------------------
045        /**
046         * Creates a new <code>AbstractFileClassLoader</code>.
047         * @param parent parent of the class loader.
048         */
049        public AbstractFileClassLoader(ClassLoader parent) {
050            super(parent);
051            files = new Vector();
052        }
053    
054        /**
055         * Creates a new <code>AbstractFileClassLoader</code> that uses the system classloader as a parent.
056         */
057        public AbstractFileClassLoader() {this(ClassLoader.getSystemClassLoader());}
058    
059    
060    
061        // - File list access ------------------------------------------------------
062        // -------------------------------------------------------------------------
063        /**
064         * Adds the specified <code>file</code> to the class loader's classpath.
065         * <p>
066         * Note that the file will <b>not</b> be added if it's already in the classpath.
067         * </p>
068         * @param  file                     file to add the class loader's classpath.
069         * @throws IllegalArgumentException if <code>file</code> is not browsable.
070         */
071        public void addFile(AbstractFile file) {
072            // Makes sure the specified file is browsable.
073            if(!file.isBrowsable())
074                throw new IllegalArgumentException();
075    
076            // Only adds the file if it's not already there.
077            if(!contains(file))
078                files.add(file);
079        }
080    
081        /**
082         * Returns an iterator on all files in this loader's classpath.
083         * @return an iterator on all files in this loader's classpath.
084         */
085        public Iterator files() {return files.iterator();}
086    
087        /**
088         * Returns <code>true</code> if this loader's classpath already contains the specified file.
089         * @param  file file to look for.
090         * @return      <code>true</code> if this loader's classpath already contains the specified file.
091         */
092        public boolean contains(AbstractFile file) {return files.contains(file);}
093    
094    
095    
096        // - Resource access -------------------------------------------------------
097        // -------------------------------------------------------------------------
098        /**
099         * Tries to locate the specified resource and returns an AbstractFile instance on it.
100         * @param  name name of the resource to locate.
101         * @return      an {@link AbstractFile} instance describing the requested resource if found, <code>null</code> otherwise.
102         */
103        private AbstractFile findResourceAsFile(String name) {
104            Iterator     iterator; // Iterator on all classpath elements.
105            AbstractFile file;     // Current file.
106    
107            iterator = files.iterator();
108            while(iterator.hasNext()) {
109                try {
110                    // If the requested resource could be found, returns it.
111                    if((file = ((AbstractFile)iterator.next()).getChild(name)).exists())
112                        return file;
113                }
114                // Treats error as a simple 'resource not found' case and keeps looking for
115                // one with the correct name that will load.
116                catch(IOException e) {}
117            }
118    
119            // The requested resource wasn't found.
120            return null;
121        }
122    
123        /**
124         * Returns an input stream on the requested resource.
125         * @param  name name of the resource to open.
126         * @return      an input stream on the requested resource, <code>null</code> if not found.
127         */
128        public InputStream getResourceAsStream(String name) {
129            AbstractFile file; // File representing the resource.
130            InputStream  in;   // Input stream on the resource.
131    
132            // Tries the parent first, to respect the delegation model.
133            if((in = getParent().getResourceAsStream(name)) != null)
134                return in;
135    
136            // Tries to locate the resource in the extended classpath if it wasn't found
137            // in the parent.
138            if((file = findResourceAsFile(name)) != null) {
139                try {return file.getInputStream();}
140                catch(Exception e) {}
141            }
142    
143            // Couldn't find the resource.
144            return null;
145        }
146    
147        /**
148         * Tries to find the requested resource.
149         * @param  name name of the resource to locate.
150         * @return      the URL of the requested resource if found, <code>null</code> otherwise.
151         */
152        protected URL findResource(String name) {
153            AbstractFile file; // Path to the requested resource.
154    
155            // Tries to find the resource.
156            if((file = findResourceAsFile(name)) == null)
157                return null;
158    
159            // Tries to retrieve an URL on the resource.
160            try {return file.getJavaNetURL();}
161            catch(Exception e) {return null;}
162        }
163    
164        /**
165         * Tries to find all the resources with the specified name.
166         * @param  name of the resources to find.
167         * @return      an enumeration containing the URLs of all the resources that match <code>name</code>.
168         */
169        protected Enumeration findResources(String name) {
170            Iterator     iterator;   // Iterator on all available JAR files.
171            AbstractFile file;       // AbstractFile describing each match.
172            Vector       resources;  // All resources that match 'name'.
173    
174            // Initialisation.
175            iterator  = files.iterator();
176            resources = new Vector();
177    
178            // Goes through all files in the classpath to find the resource.
179            while(iterator.hasNext()) {
180                try {
181                    if((file = ((AbstractFile)iterator.next()).getChild(name)).exists())
182                        resources.add(file.getJavaNetURL());
183                }
184                catch(IOException e) {}
185            }
186            return resources.elements();
187        }
188    
189        /**
190         * Returns the absolute path of the requested library.
191         * @param name name of the library to load.
192         * @return the absolute path of the requested library if found, <code>null</code> otheriwse.
193         */
194        protected String findLibrary(String name) {
195            AbstractFile file; // Path of the requested library.
196    
197            // Tries to find the requested library.
198            if((file = findResourceAsFile(name)) == null)
199                return null;
200    
201            // Retrieves its absolute path.
202            return file.getAbsolutePath();
203        }
204    
205    
206    
207        // - Class loading ---------------------------------------------------------
208        // -------------------------------------------------------------------------
209        /**
210         * Loads the class defined by the specified name and path.
211         * @param  name        name of the class to load.
212         * @param  file        file containing the class' bytecode.
213         * @throws IOException if an error occurs.
214         */
215        private Class loadClass(String name, AbstractFile file) throws IOException {
216            byte[]      buffer; // Buffer for the class' bytecode.
217            int         offset; // Current offset in buffer.
218            InputStream in;     // Stream on the class' bytecode.
219    
220            // Initialisation.
221            buffer = new byte[(int)file.getSize()];
222            offset = 0;
223            in     = null;
224    
225            try {
226                // Loads the content of file in buffer.
227                in = file.getInputStream();
228                while(offset != buffer.length)
229                    offset += in.read(buffer, offset, buffer.length - offset);
230    
231                // Loads the class.
232                return defineClass(name, buffer, 0, buffer.length);
233            }
234    
235            // Frees resources.
236            finally {
237                if(in != null)
238                    in.close();
239            }                
240        }
241    
242        /**
243         * Tries to find and load the specified class.
244         * @param name                    fully qualified name of the class to load.
245         * @return                        the requested <code>Class</code> if found, <code>null</code> otherwise.
246         * @throws ClassNotFoundException if the requested class was not found.
247         */
248        protected synchronized Class findClass(String name) throws ClassNotFoundException {
249            AbstractFile file; // File containing the class' bytecode.
250    
251            // Tries to locate the specified class and, if found, load it.
252            if((file = findResourceAsFile(name.replace('.', '/') + ".class")) != null) {
253                try {return loadClass(name, file);}
254                catch(Exception e) {}
255            }
256            throw new ClassNotFoundException(name);
257        }
258    }