001: /* Copyright (C) 2004 - 2007 db4objects Inc. http://www.db4o.com
002:
003: This file is part of the db4o open source object database.
004:
005: db4o is free software; you can redistribute it and/or modify it under
006: the terms of version 2 of the GNU General Public License as published
007: by the Free Software Foundation and as clarified by db4objects' GPL
008: interpretation policy, available at
009: http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
010: Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
011: Suite 350, San Mateo, CA 94403, USA.
012:
013: db4o is distributed in the hope that it will be useful, but WITHOUT ANY
014: WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: for more details.
017:
018: You should have received a copy of the GNU General Public License along
019: with this program; if not, write to the Free Software Foundation, Inc.,
020: 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
021: package EDU.purdue.cs.bloat.file;
022:
023: import java.io.*;
024: import java.util.*;
025: import java.util.zip.*;
026: import java.net.URL;
027:
028: import EDU.purdue.cs.bloat.reflect.*;
029:
030: /**
031: * ClassFileLoder provides an interface for loading classes from files. The
032: * actual loading is done by the ClassFile itself.
033: * <p>
034: * Classes may be specified by their full package name (<tt>java.lang.String</tt>),
035: * or by the name of their class file (<tt>myclasses/Test.class</tt>). The
036: * class path may contain directories or Zip or Jar files. Any classes that are
037: * written back to disk ("committed") are placed in the output directory.
038: *
039: * @author Nate Nystrom (<a
040: * href="mailto:nystrom@cs.purdue.edu">nystrom@cs.purdue.edu</a>)
041: */
042: public class ClassFileLoader implements ClassInfoLoader {
043: public static boolean DEBUG = false;
044:
045: public static boolean USE_SYSTEM_CLASSES = true;
046:
047: private File outputDir; // Directory in which to write committed class files
048:
049: private String classpath; // Path to search for classes
050:
051: private Map openZipFiles; // zip files to search for class files
052:
053: private LinkedList cache; // We keep a cache of CACHE_LIMIT class files
054:
055: private boolean verbose;
056:
057: private static final int CACHE_LIMIT = 10;
058:
059: private ClassSource _classSource;
060:
061: public ClassFileLoader(ClassSource classSource) {
062: outputDir = new File(".");
063: classpath = System.getProperty("java.class.path");
064: classpath += File.pathSeparator
065: + System.getProperty("sun.boot.class.path");
066: if (ClassFileLoader.USE_SYSTEM_CLASSES) {
067: classpath += File.pathSeparator
068: + System.getProperty("java.sys.class.path");
069: }
070: openZipFiles = new HashMap();
071: cache = new LinkedList();
072: verbose = false;
073: _classSource = classSource;
074: }
075:
076: /**
077: * Constructor. The classpath initially consists of the contents of the
078: * <tt>java.class.path</tt> and <tt>sun.boot.class.path</tt> system
079: * properties.
080: */
081: public ClassFileLoader() {
082: this (new DefaultClassSource());
083: }
084:
085: public void setVerbose(final boolean verbose) {
086: this .verbose = verbose;
087: }
088:
089: /**
090: * Sets the classpath.
091: */
092: public void setClassPath(final String classpath) {
093: this .classpath = classpath;
094: }
095:
096: /**
097: * Adds to the classpath (CLASSPATH = CLASSPATH + morePath).
098: */
099: public void appendClassPath(final String morePath) {
100: this .classpath += File.pathSeparator + morePath;
101: }
102:
103: /**
104: * Adds to the classpath (CLASSPATH = morePath + CLASSPATH).
105: */
106: public void prependClassPath(final String morePath) {
107: this .classpath = morePath + File.pathSeparator + this .classpath;
108: }
109:
110: /**
111: * Returns the path used to search for class files.
112: */
113: public String getClassPath() {
114: return (this .classpath);
115: }
116:
117: /**
118: * Load the class from a stream.
119: *
120: * @param inputFile
121: * The file from which to load the class.
122: * @param stream
123: * The stream from which to load the class.
124: * @return A ClassInfo for the class.
125: * @exception ClassNotFoundException
126: * The class cannot be found in the class path.
127: */
128: private ClassInfo loadClassFromStream(final File inputFile,
129: final InputStream stream) throws ClassNotFoundException {
130:
131: final DataInputStream in = new DataInputStream(stream);
132: final ClassFile file = new ClassFile(inputFile, this , in);
133:
134: return file;
135: }
136:
137: /**
138: * Load the class from the file.
139: *
140: * @param file
141: * The File from which to load a class.
142: * @return A ClassInfo for the class.
143: * @exception ClassNotFoundException
144: * The class cannot be found in the class path.
145: */
146: private ClassInfo loadClassFromFile(final File file)
147: throws ClassNotFoundException {
148: try {
149: final InputStream in = new FileInputStream(file);
150:
151: final ClassInfo info = loadClassFromStream(file, in);
152:
153: if (verbose) {
154: System.out.println("[Loaded " + info.name() + " from "
155: + file.getPath() + "]");
156: }
157:
158: try {
159: in.close();
160: } catch (final IOException ex) {
161: }
162:
163: return info;
164: } catch (final FileNotFoundException e) {
165: throw new ClassNotFoundException(file.getPath());
166: }
167: }
168:
169: /**
170: * Loads all of the classes that are contained in a zip (or jar) file.
171: * Returns an array of the <tt>ClassInfo</tt>s for the classes in the zip
172: * file.
173: */
174: public ClassInfo[] loadClassesFromZipFile(final ZipFile zipFile)
175: throws ClassNotFoundException {
176: final ClassInfo[] infos = new ClassInfo[zipFile.size()];
177:
178: // Examine each entry in the zip file
179: final Enumeration entries = zipFile.entries();
180: for (int i = 0; entries.hasMoreElements(); i++) {
181: final ZipEntry entry = (ZipEntry) entries.nextElement();
182: if (entry.isDirectory()
183: || !entry.getName().endsWith(".class")) {
184: continue;
185: }
186:
187: try {
188: final InputStream stream = zipFile
189: .getInputStream(entry);
190: final File file = new File(entry.getName());
191:
192: infos[i] = loadClassFromStream(file, stream);
193:
194: } catch (final IOException ex) {
195: System.err.println("IOException: " + ex);
196: }
197: }
198:
199: return (infos);
200: }
201:
202: public ClassInfo newClass(final int modifiers,
203: final int classIndex, final int super ClassIndex,
204: final int[] interfaceIndexes, final List constants) {
205: return new ClassFile(modifiers, classIndex, super ClassIndex,
206: interfaceIndexes, constants, this );
207: }
208:
209: /**
210: * Thhis method tries to load a Class by its ressource.
211: * @param name the Name of the Class
212: * @return the ClassInfo
213: */
214: private ClassInfo loadClassFromRessource(String name) {
215: name = name.replace('/', '.');
216: try {
217: Class clazz = _classSource.loadClass(name);
218: int i = name.lastIndexOf('.');
219: if (i >= 0 && i < name.length()) {
220: name = name.substring(i + 1);
221: }
222: URL url = clazz.getResource(name + ".class");
223: if (url != null) {
224: return loadClassFromStream(new File(url.getFile()), url
225: .openStream());
226: }
227: } catch (Exception e) {
228: }
229: return null;
230: }
231:
232: /**
233: * Loads the class with the given name. Searches the class path, including
234: * zip files, for the class and then returns a data stream for the class
235: * file.
236: *
237: * @param name
238: * The name of the class to load, including the package name.
239: * @return A ClassInfo for the class.
240: * @exception ClassNotFoundException
241: * The class cannot be found in the class path.
242: */
243: public ClassInfo loadClass(String name)
244: throws ClassNotFoundException {
245: ClassInfo file = null;
246:
247: // Check to see if name ends with ".class". If so, load the class from
248: // that file. Note that this is okay because we can never have a class
249: // named "class" (i.e. a class named "class" with a lower-case 'c' can
250: // never be specified in a fully-specified java class name) because
251: // "class" is a reserved word.
252:
253: if (name.endsWith(".class")) {
254: final File nameFile = new File(name);
255:
256: if (!nameFile.exists()) {
257: throw new ClassNotFoundException(name);
258:
259: } else {
260: return (loadClassFromFile(nameFile));
261: }
262: }
263:
264: if ((file = loadClassFromRessource(name)) != null) {
265: addToCache(file);
266: return file;
267: }
268:
269: // Otherwise, we have a (possibly fully-specified) class name.
270: name = name.replace('.', '/');
271:
272: // Check the cache for the class file.
273: if (ClassFileLoader.DEBUG) {
274: System.out.println(" Looking for " + name + " in cache = "
275: + cache);
276: }
277:
278: final Iterator iter = cache.iterator();
279:
280: while (iter.hasNext()) {
281: file = (ClassFile) iter.next();
282:
283: if (name.equals(file.name())) {
284: if (ClassFileLoader.DEBUG) {
285: System.out.println(" Found " + file.name()
286: + " in cache");
287: }
288:
289: // Move to the front of the cache.
290: iter.remove();
291: cache.addFirst(file);
292:
293: return file;
294: }
295: }
296:
297: file = null;
298:
299: final String classFile = name.replace('/', File.separatorChar)
300: + ".class";
301:
302: // For each entry in the class path, search zip files and directories
303: // for classFile. When found, open an InputStream and break
304: // out of the loop to read the class file.
305: final String path = classpath + File.pathSeparator;
306:
307: if (ClassFileLoader.DEBUG) {
308: System.out.println("CLASSPATH = " + path);
309: }
310:
311: int index = 0;
312: int end = path.indexOf(File.pathSeparator, index);
313:
314: SEARCH: while (end >= 0) {
315: final String dir = path.substring(index, end);
316:
317: File f = new File(dir);
318:
319: if (f.isDirectory()) {
320: // The directory is really a directory. If the class file
321: // exists, open a stream and return.
322: f = new File(dir, classFile);
323:
324: if (f.exists()) {
325: try {
326: final InputStream in = new FileInputStream(f);
327:
328: if (verbose) {
329: System.out.println(" [Loaded " + name
330: + " from " + f.getPath() + "]");
331: }
332:
333: file = loadClassFromStream(f, in);
334:
335: try {
336: in.close();
337:
338: } catch (final IOException ex) {
339: }
340:
341: break SEARCH;
342:
343: } catch (final FileNotFoundException ex) {
344: }
345: }
346:
347: } else if (dir.endsWith(".zip") || dir.endsWith(".jar")) {
348: // Maybe a zip file?
349: try {
350: ZipFile zip = (ZipFile) openZipFiles.get(dir);
351:
352: if (zip == null) {
353: zip = new ZipFile(f);
354: openZipFiles.put(dir, zip);
355: }
356:
357: final String zipEntry = classFile.replace(
358: File.separatorChar, '/');
359:
360: final ZipEntry entry = zip.getEntry(zipEntry);
361:
362: if (entry != null) {
363: // Found the class file in the zip file.
364: // Open a stream and return.
365: if (verbose) {
366: System.out.println(" [Loaded " + name
367: + " from " + f.getPath() + "]");
368: }
369:
370: final InputStream in = zip
371: .getInputStream(entry);
372: file = loadClassFromStream(f, in);
373:
374: try {
375: in.close();
376:
377: } catch (final IOException ex) {
378: }
379: break SEARCH;
380: }
381: } catch (final ZipException ex) {
382: } catch (final IOException ex) {
383: }
384: }
385:
386: index = end + 1;
387: end = path.indexOf(File.pathSeparator, index);
388: }
389:
390: if (file == null) {
391: // The class file wasn't in the class path. Try the currnet
392: // directory. If not there, give up.
393: final File f = new File(classFile);
394:
395: if (!f.exists()) {
396: throw new ClassNotFoundException(name);
397: }
398:
399: if (verbose) {
400: System.out.println(" [Loaded " + name + " from "
401: + f.getPath() + "]");
402: }
403:
404: try {
405: final InputStream in = new FileInputStream(f);
406: file = loadClassFromStream(f, in);
407:
408: try {
409: in.close();
410: } catch (final IOException ex) {
411: }
412: } catch (final FileNotFoundException ex) {
413: throw new ClassNotFoundException(name);
414: }
415: }
416:
417: if (file == null) {
418: throw new ClassNotFoundException(name);
419: }
420:
421: addToCache(file);
422:
423: return file;
424: }
425:
426: private void addToCache(ClassInfo file) {
427: // If we've reached the cache size limit, remove the oldest file
428: // in the cache. Then add the new file.
429: if (cache.size() == ClassFileLoader.CACHE_LIMIT) {
430: cache.removeLast();
431: }
432:
433: cache.addFirst(file);
434: }
435:
436: /**
437: * Set the directory into which commited class files should be written.
438: *
439: * @param dir
440: * The directory.
441: */
442: public void setOutputDir(final File dir) {
443: outputDir = dir;
444: }
445:
446: /**
447: * Get the directory into which commited class files should be written.
448: */
449: public File outputDir() {
450: return outputDir;
451: }
452:
453: /**
454: * Writes a bunch of <code>byte</code>s to an output entry with the given
455: * name.
456: */
457: public void writeEntry(final byte[] bytes, final String name)
458: throws IOException {
459: final OutputStream os = outputStreamFor(name);
460: os.write(bytes);
461: os.flush();
462: os.close();
463: }
464:
465: /**
466: * Returns an <tt>OutputStream</tt> to which a class file should be
467: * written.
468: */
469: public OutputStream outputStreamFor(final ClassInfo info)
470: throws IOException {
471: // Format the name of the output file
472: final String name = info.name()
473: .replace('/', File.separatorChar)
474: + ".class";
475: return outputStreamFor(name);
476: }
477:
478: /**
479: * Returns an <code>OutputStream</code> to which somed named entity is
480: * written. Any forward slashes in the name are replaced by
481: * <code>File.separatorChar</code>.
482: */
483: protected OutputStream outputStreamFor(String name)
484: throws IOException {
485:
486: name = name.replace('/', File.separatorChar);
487:
488: final File f = new File(outputDir, name);
489:
490: if (f.exists()) {
491: f.delete();
492: }
493:
494: final File dir = new File(f.getParent());
495: dir.mkdirs();
496:
497: if (!dir.exists()) {
498: throw new RuntimeException("Couldn't create directory: "
499: + dir);
500: }
501:
502: return (new FileOutputStream(f));
503: }
504:
505: /**
506: * Signifies that we are done with this <code>ClassFileLoader</code>
507: */
508: public void done() throws IOException {
509: // Nothing for this guy
510: }
511: }
|