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 }