001: /*
002: * @(#)UrlClassLoader.java
003: *
004: * Copyright (C) 2000,2002-2003 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.util.classes.v1.jdk0;
028:
029: import java.io.File;
030: import java.io.IOException;
031: import java.io.FileInputStream;
032: import java.io.ByteArrayInputStream;
033: import java.io.ByteArrayOutputStream;
034: import java.io.InputStream;
035:
036: import java.net.URL;
037:
038: import java.util.Hashtable;
039: import java.util.Enumeration;
040:
041: import java.util.zip.ZipInputStream;
042: import java.util.zip.ZipEntry;
043:
044: import net.sourceforge.groboutils.util.classes.v1.IUrlClassLoader;
045:
046: /**
047: * Class responsible for loading classes in a JDK 1.0+ version compatible
048: * way. Need to determine speed vs. size for caching Jar files that are
049: * loaded over the internet. For now, we cache stuff, in the hope that
050: * the flush() will be called.
051: * <P>
052: * If the URL is null, or if the bytecode for a class name is not found,
053: * then this will attempt to load the class from the system classloader.
054: * <P>
055: * NOTE: As of version 1.0.0, there is no longer a dependency upon
056: * <tt>net.sourceforge.groboutils.util.io.v1</tt>
057: *
058: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
059: * @version $Date: 2003/05/06 05:35:00 $
060: * @since November 17, 2000 (GroboUtils Alpha 0.9.0)
061: */
062: public class UrlClassLoader implements IUrlClassLoader, BytecodeSource {
063: //----------------------------
064: // Public data
065:
066: //----------------------------
067: // Private data
068:
069: private ArrayClassLoader m_acl = new ArrayClassLoader();
070: private Hashtable m_urlSources = new Hashtable();
071:
072: private static final String[] JAR_EXTENTIONS = { ".jar", ".zip",
073: ".war", ".sar", ".ear" };
074:
075: private abstract class BytecodeSourceCache implements
076: BytecodeSource {
077: private Hashtable m_classes = new Hashtable();
078:
079: public BytecodeSourceCache() {
080: // do nothing
081: }
082:
083: public byte[] getBytecode(String className) {
084: byte buff[] = (byte[]) this .m_classes.get(className);
085: if (buff == null) {
086: try {
087: buff = readBytecode(className);
088: if (buff != null) {
089: this .m_classes.put(className, buff);
090: }
091: } catch (IOException ioe) {
092: ioe.printStackTrace();
093: buff = null;
094: } catch (SecurityException se) {
095: se.printStackTrace();
096: buff = null;
097: }
098: }
099: return buff;
100: }
101:
102: protected abstract byte[] readBytecode(String className)
103: throws IOException;
104: }
105:
106: /**
107: * Loads classes from a given zip/jar file
108: */
109: private class JarBytecodeSource extends BytecodeSourceCache {
110: private byte[] m_zipfile;
111:
112: public JarBytecodeSource(String s_url) throws IOException {
113: // due to an error in the JDK 1.1 implementation of the
114: // file: protocol, we must verify the URL ourself
115: InputStream is = null;
116: try {
117: if (s_url.startsWith("file:")) {
118: // this is also a bit of an optimization
119: is = new FileInputStream(s_url.substring("file:"
120: .length()));
121: } else {
122: URL url = new URL(s_url);
123: is = url.openStream();
124: }
125: this .m_zipfile = readByteStream(is);
126: } finally {
127: if (is != null) {
128: is.close();
129: }
130: }
131: }
132:
133: protected byte[] readBytecode(String className)
134: throws IOException {
135: ByteArrayInputStream bais = new ByteArrayInputStream(
136: this .m_zipfile);
137: ZipInputStream zis = new ZipInputStream(bais);
138: String classFile = className2jarFileName(className);
139: byte buff[] = findZipEntry(classFile, zis);
140: zis.close();
141: bais.close();
142: return buff;
143: }
144: }
145:
146: private class UrlBytecodeSource extends BytecodeSourceCache {
147: private String m_url;
148:
149: public UrlBytecodeSource(String url) {
150: this .m_url = url;
151: }
152:
153: protected byte[] readBytecode(String className)
154: throws IOException {
155: URL url = new URL(joinClassToUrl(className, this .m_url));
156: byte[] buff = null;
157: IOException ex = null;
158: // there may be time-out issues
159: for (int i = 0; i < 3 && buff == null; ++i) {
160: InputStream is = null;
161: try {
162: is = url.openStream();
163: buff = readByteStream(is);
164: } catch (IOException ioe) {
165: ex = ioe;
166: } finally {
167: is.close();
168: }
169: }
170: if (buff == null) {
171: throw ex;
172: }
173: return buff;
174: }
175: }
176:
177: //----------------------------
178: // constructors
179:
180: /**
181: * Default constructor
182: */
183: public UrlClassLoader() {
184: this .m_acl.setBytecodeSource(this );
185: }
186:
187: //----------------------------
188: // Public methods
189:
190: /**
191: * Load the given class from the given URL. If the URL is <tt>null</tt>,
192: * then it loads the class from the default class loader.
193: *
194: * @param className the exact class name to load.
195: * @param url the URL from which the class is loaded. If this is
196: * <tt>null</tt>, then this loads the class from the default class
197: * loader.
198: * @return the loaded Class instance, or <tt>null</tt> if the class could
199: * not be found.
200: */
201: public Class loadClass(String className, String url) {
202: url = convertUrl(url);
203:
204: // check if this is a JAR type reference
205: BytecodeSource bs = getBytecodeSource(url);
206: if (bs != null) {
207: byte bytecode[] = bs.getBytecode(className);
208:
209: if (bytecode != null && bytecode.length > 0) {
210: // load the class from bytecode
211:
212: this .m_acl.addClass(className, bytecode);
213: }
214: // else - use the system class loader
215: // ***** SHOULD WE RETURN NULL???? ****
216: }
217: Class c = null;
218: try {
219: c = this .m_acl.loadClass(className);
220: } catch (ClassNotFoundException cnfe) {
221: c = null;
222: }
223:
224: return c;
225: }
226:
227: /**
228: * Call to flush any cache stored in the interface. This allows for
229: * a class loader to cache results, and free up memory when it is
230: * not needed.
231: */
232: public void flush() {
233: this .m_acl = new ArrayClassLoader();
234: this .m_acl.setBytecodeSource(this );
235: this .m_urlSources = new Hashtable();
236: }
237:
238: public byte[] getBytecode( String classname )
239: {
240: Enumeration enum = this .m_urlSources.elements();
241: while (enum.hasMoreElements())
242: {
243: BytecodeSource bs = (BytecodeSource)enum.nextElement();
244: byte buff[] = bs.getBytecode( classname );
245: if (buff != null)
246: {
247: return buff;
248: }
249: }
250: return null;
251: }
252:
253: //----------------------------
254: // Protected methods
255:
256: /**
257: *
258: */
259: protected BytecodeSource getBytecodeSource(String url) {
260: if (url == null) {
261: return null;
262: }
263: BytecodeSource bs = (BytecodeSource) this .m_urlSources.get(url);
264: if (bs == null) {
265: // there may be time-out issues
266: for (int i = 0; i < 3 && bs == null; ++i) {
267: if (isJarURL(url)) {
268: try {
269: bs = new JarBytecodeSource(url);
270: } catch (IOException ioe) {
271: ioe.printStackTrace();
272: }
273: } else {
274: bs = new UrlBytecodeSource(url);
275: }
276: }
277: if (bs != null) {
278: this .m_urlSources.put(url, bs);
279: }
280: }
281: return bs;
282: }
283:
284: /**
285: * Converts the given string to a fully qualified URL. If no
286: * scheme is given, then it is converted to a File scheme.
287: */
288: protected String convertUrl(String url) {
289: if (url == null) {
290: return null;
291: }
292: //System.out.println("Converting from URL "+url);
293: int pos = url.indexOf(':');
294: String scheme = null;
295: if (pos >= 0) {
296: scheme = url.substring(0, pos);
297: }
298: if (scheme == null || scheme.length() <= 0) {
299: url = "file:" + getAbsoluteFilename(new File(url));
300: } else {
301: // check if the url is an existing file, since
302: // a colon could be a drive or volume reference.
303: File f = new File(url);
304: if (f.exists()) {
305: // make this a file
306: url = "file:" + getAbsoluteFilename(f);
307: }
308: // else assume that the given url is correct
309: }
310:
311: //System.out.println(" - to URL "+url);
312: return url;
313: }
314:
315: /**
316: * @return <tt>true</tt> if the URL references a Jar-like file.
317: */
318: protected boolean isJarURL(String url) {
319: int pos = url.lastIndexOf('.');
320: if (pos < 0)
321: return false;
322: String ext = url.substring(pos);
323: for (int i = JAR_EXTENTIONS.length; --i >= 0;) {
324: if (JAR_EXTENTIONS[i].equalsIgnoreCase(ext)) {
325: return true;
326: }
327: }
328: return false;
329: }
330:
331: /**
332: * Finds the entry of the given filename in the given input stream,
333: * and returns the entry as a byte array. If the entry wasn't found,
334: * then <tt>null</tt> is returned.
335: */
336: protected byte[] findZipEntry(String filename, ZipInputStream zis) {
337: try {
338: ZipEntry ze = zis.getNextEntry();
339: while (ze != null) {
340: // case must match!
341: if (filename.equals(ze.getName())) {
342: // size may be huge, but actually class files
343: // have a maximum size
344: int size = (int) ze.getSize();
345: byte b[];
346: if (size < 0) {
347: b = readByteStream(zis);
348: } else {
349: b = new byte[size];
350: zis.read(b, 0, size);
351: }
352: return b;
353: }
354: ze = zis.getNextEntry();
355: }
356: // the entry wasn't found
357: return null;
358: } catch (IOException ioe) {
359: ioe.printStackTrace();
360: return null;
361: }
362: }
363:
364: /**
365: * Converts a class name to a file name.
366: */
367: protected String className2jarFileName(String className) {
368: return className.replace('.', '/') + ".class";
369: }
370:
371: /**
372: * Joins a classname to a URL.
373: */
374: protected String joinClassToUrl(String className, String url) {
375: StringBuffer sb = new StringBuffer(url);
376: sb.append('/');
377: sb.append(className2jarFileName(className));
378: return new String(sb);
379: }
380:
381: /**
382: *
383: */
384: protected String getAbsoluteFilename(File f) {
385: return f.getAbsolutePath().toString();
386: }
387:
388: /**
389: * A re-implementation of
390: * <tt>net.groboclown.util.io.v1.ReadByteStream</tt>, to reduce
391: * the amount of inter-package dependencies.
392: */
393: protected byte[] readByteStream(InputStream is) throws IOException {
394: int totSize = 0;
395: byte[] ret = new byte[0];
396: byte[] buff = new byte[4096];
397: int size = is.read(buff, 0, 4096);
398: while (size > 0) {
399: byte[] tmp = new byte[size + totSize];
400: System.arraycopy(ret, 0, tmp, 0, totSize);
401: System.arraycopy(buff, 0, tmp, totSize, size);
402: ret = tmp;
403: totSize += size;
404:
405: size = is.read(buff, 0, 4096);
406: }
407: return ret;
408: }
409: }
|