001: /*
002: * Janino - An embedded Java[TM] compiler
003: *
004: * Copyright (c) 2006, Arno Unkrig
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * 2. Redistributions in binary form must reproduce the above
014: * copyright notice, this list of conditions and the following
015: * disclaimer in the documentation and/or other materials
016: * provided with the distribution.
017: * 3. The name of the author may not be used to endorse or promote
018: * products derived from this software without specific prior
019: * written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
022: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
024: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
025: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
027: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
028: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
029: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
030: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
031: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
032: */
033:
034: package org.codehaus.janino;
035:
036: import java.io.*;
037: import java.util.*;
038:
039: import org.codehaus.janino.util.*;
040: import org.codehaus.janino.util.enumerator.*;
041: import org.codehaus.janino.util.resource.*;
042:
043: /**
044: * A {@link org.codehaus.janino.JavaSourceClassLoader} that uses a
045: * resource storage provided by the application to cache compiled
046: * classes and thus saving unnecessary recompilations.
047: * <p>
048: * The application provides access to the resource storeage through a pair of
049: * a {@link org.codehaus.janino.util.resource.ResourceFinder} and a
050: * {@link org.codehaus.janino.util.resource.ResourceCreator} (see
051: * {@link #CachingJavaSourceClassLoader(ClassLoader, ResourceFinder, String, ResourceFinder, ResourceCreator, EnumeratorSet)}.
052: * <p>
053: * See {@link org.codehaus.janino.JavaSourceClassLoader#main(String[])} for
054: * an example how to use this class.
055: */
056: public class CachingJavaSourceClassLoader extends JavaSourceClassLoader {
057: private final ResourceFinder classFileCacheResourceFinder;
058: private final ResourceCreator classFileCacheResourceCreator;
059: private final ResourceFinder sourceFinder;
060:
061: /**
062: * See {@link #CachingJavaSourceClassLoader(ClassLoader, ResourceFinder, String, ResourceFinder, ResourceCreator, EnumeratorSet)}.
063: *
064: * @param optionalSourcePath Directories to scan for source files
065: * @param cacheDirectory Directory to use for caching generated class files
066: */
067: public CachingJavaSourceClassLoader(ClassLoader parentClassLoader,
068: File[] optionalSourcePath,
069: String optionalCharacterEncoding, File cacheDirectory,
070: EnumeratorSet debuggingInformation) {
071: this (
072: parentClassLoader, // parentClassLoader
073: ( // sourceFinder
074: optionalSourcePath == null ? (ResourceFinder) new DirectoryResourceFinder(
075: new File("."))
076: : (ResourceFinder) new PathResourceFinder(
077: optionalSourcePath)),
078: optionalCharacterEncoding, // optionalCharacterEncoding
079: new DirectoryResourceFinder(cacheDirectory), // classFileCacheResourceFinder
080: new DirectoryResourceCreator(cacheDirectory), // classFileCacheResourceCreator
081: debuggingInformation // debuggingInformation
082: );
083: }
084:
085: /**
086: * Notice that this class is thread-safe if and only if the
087: * <code>classFileCacheResourceCreator</code> stores its data atomically,
088: * i.e. the <code>classFileCacheResourceFinder</code> sees the resource
089: * written by the <code>classFileCacheResourceCreator</code> only after
090: * the {@link OutputStream} is closed.
091: * <p>
092: * In order to make the caching scheme work, both the
093: * <code>classFileCacheResourceFinder</code> and the
094: * <code>sourceFinder</code> must support the {@link org.codehaus.janino.util.resource.Resource#lastModified()}
095: * method, so that the modification time of the source and the class files
096: * can be compared.
097: *
098: * @param parentClassLoader Attempt to load classes through this one before looking for source files
099: * @param sourceFinder Finds Java<sup>TM</sup> source for class <code>pkg.Cls</code> in resource <code>pkg/Cls.java</code>
100: * @param optionalCharacterEncoding Encoding of Java<sup>TM</sup> source or <code>null</code> for platform default encoding
101: * @param classFileCacheResourceFinder Finds precompiled class <code>pkg.Cls</code> in resource <code>pkg/Cls.class</code>
102: * @param classFileCacheResourceCreator Stores compiled class <code>pkg.Cls</code> in resource <code>pkg/Cls.class</code>
103: * @param debuggingInformation What debugging information to include into the generated class files
104: */
105: public CachingJavaSourceClassLoader(ClassLoader parentClassLoader,
106: ResourceFinder sourceFinder,
107: String optionalCharacterEncoding,
108: ResourceFinder classFileCacheResourceFinder,
109: ResourceCreator classFileCacheResourceCreator,
110: EnumeratorSet debuggingInformation) {
111: super (parentClassLoader, sourceFinder,
112: optionalCharacterEncoding, debuggingInformation);
113: this .classFileCacheResourceFinder = classFileCacheResourceFinder;
114: this .classFileCacheResourceCreator = classFileCacheResourceCreator;
115: this .sourceFinder = sourceFinder;
116: }
117:
118: /**
119: * Override {@link JavaSourceClassLoader#findClass(String)} to implement
120: * class file caching.
121: */
122: protected Class findClass(String className)
123: throws ClassNotFoundException {
124:
125: // Check whether a class file resource exists in the cache.
126: {
127: Resource classFileResource = this .classFileCacheResourceFinder
128: .findResource(ClassFile
129: .getClassFileResourceName(className));
130: if (classFileResource != null) {
131:
132: // Check whether a source file resource exists.
133: Resource sourceResource = this .sourceFinder
134: .findResource(ClassFile
135: .getSourceResourceName(className));
136: if (sourceResource == null)
137: throw new ClassNotFoundException(className);
138:
139: // Check whether the class file is up-to-date.
140: if (sourceResource.lastModified() < classFileResource
141: .lastModified()) {
142:
143: // Yes, it is... read the bytecode from the file and define the class.
144: byte[] bytecode;
145: InputStream is = null;
146: try {
147: is = classFileResource.open();
148: bytecode = CachingJavaSourceClassLoader
149: .readInputStream(is);
150: } catch (IOException ex) {
151: throw new ClassNotFoundException(
152: "Reading class file from \""
153: + classFileResource + "\"", ex);
154: } finally {
155: try {
156: is.close();
157: } catch (IOException ex) {
158: }
159: }
160: return this .defineBytecode(className, bytecode);
161: }
162: }
163: }
164:
165: // Cache miss... generate the bytecode from source.
166: Map bytecodes = this .generateBytecodes(className);
167: if (bytecodes == null)
168: throw new ClassNotFoundException(className);
169:
170: // Write the generated bytecodes to the class file cache.
171: for (Iterator it = bytecodes.entrySet().iterator(); it
172: .hasNext();) {
173: Map.Entry me = (Map.Entry) it.next();
174: String className2 = (String) me.getKey();
175: byte[] bytecode = (byte[]) me.getValue();
176:
177: OutputStream os = null;
178: try {
179: os = this .classFileCacheResourceCreator
180: .createResource(ClassFile
181: .getClassFileResourceName(className2));
182: os.write(bytecode);
183: } catch (IOException ex) {
184: throw new ClassNotFoundException(
185: "Writing class file to \""
186: + ClassFile
187: .getClassFileResourceName(className2)
188: + "\"", ex);
189: } finally {
190: if (os != null)
191: try {
192: os.close();
193: } catch (IOException ex) {
194: }
195: }
196: }
197:
198: Class clazz = this .defineBytecodes(className, bytecodes);
199: if (clazz == null)
200: throw new RuntimeException(
201: "Scanning, parsing and compiling class \""
202: + className
203: + "\" did not create a class file!?");
204: return clazz;
205: }
206:
207: /**
208: * Read data from <code>is</code> until EOF into a byte array.
209: */
210: private static byte[] readInputStream(InputStream is)
211: throws IOException {
212: ByteArrayOutputStream baos = new ByteArrayOutputStream();
213: CachingJavaSourceClassLoader.copy(is, baos);
214: return baos.toByteArray();
215: }
216:
217: /**
218: * Copy data from <code>is</code> until EOF to <code>os</code>.
219: */
220: private static void copy(InputStream is, OutputStream os)
221: throws IOException {
222: byte[] buffer = new byte[4096];
223: for (;;) {
224: int cnt = is.read(buffer);
225: if (cnt == -1)
226: return;
227: os.write(buffer, 0, cnt);
228: }
229: }
230: }
|