001: /*
002: * Janino - An embedded Java[TM] compiler
003: *
004: * Copyright (c) 2001-2007, 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: * <p>
056: * <b>Notice:</b> You must NOT rely on that this class stores some particular data in some
057: * particular resources through the given <code>classFileCacheResourceFinder/Creator</code>!
058: * These serve only as a means for the {@link CachingJavaSourceClassLoader} to persistently
059: * cache some data between invocations. In other words: If you want to compile <code>.java</code>
060: * files into <code>.class</code> files, then don't use <i>this</i> class but {@link Compiler}
061: * instead!
062: */
063: public class CachingJavaSourceClassLoader extends JavaSourceClassLoader {
064: private final ResourceFinder classFileCacheResourceFinder;
065: private final ResourceCreator classFileCacheResourceCreator;
066: private final ResourceFinder sourceFinder;
067:
068: /**
069: * See {@link #CachingJavaSourceClassLoader(ClassLoader, ResourceFinder, String, ResourceFinder, ResourceCreator, EnumeratorSet)}.
070: *
071: * @param optionalSourcePath Directories to scan for source files
072: * @param cacheDirectory Directory to use for caching generated class files (see class description)
073: */
074: public CachingJavaSourceClassLoader(ClassLoader parentClassLoader,
075: File[] optionalSourcePath,
076: String optionalCharacterEncoding, File cacheDirectory,
077: EnumeratorSet debuggingInformation) {
078: this (
079: parentClassLoader, // parentClassLoader
080: ( // sourceFinder
081: optionalSourcePath == null ? (ResourceFinder) new DirectoryResourceFinder(
082: new File("."))
083: : (ResourceFinder) new PathResourceFinder(
084: optionalSourcePath)),
085: optionalCharacterEncoding, // optionalCharacterEncoding
086: new DirectoryResourceFinder(cacheDirectory), // classFileCacheResourceFinder
087: new DirectoryResourceCreator(cacheDirectory), // classFileCacheResourceCreator
088: debuggingInformation // debuggingInformation
089: );
090: }
091:
092: /**
093: * Notice that this class is thread-safe if and only if the
094: * <code>classFileCacheResourceCreator</code> stores its data atomically,
095: * i.e. the <code>classFileCacheResourceFinder</code> sees the resource
096: * written by the <code>classFileCacheResourceCreator</code> only after
097: * the {@link OutputStream} is closed.
098: * <p>
099: * In order to make the caching scheme work, both the
100: * <code>classFileCacheResourceFinder</code> and the
101: * <code>sourceFinder</code> must support the {@link org.codehaus.janino.util.resource.Resource#lastModified()}
102: * method, so that the modification time of the source and the class files
103: * can be compared.
104: *
105: * @param parentClassLoader Attempt to load classes through this one before looking for source files
106: * @param sourceFinder Finds Java<sup>TM</sup> source for class <code>pkg.Cls</code> in resource <code>pkg/Cls.java</code>
107: * @param optionalCharacterEncoding Encoding of Java<sup>TM</sup> source or <code>null</code> for platform default encoding
108: * @param classFileCacheResourceFinder Finds precompiled class <code>pkg.Cls</code> in resource <code>pkg/Cls.class</code> (see class description)
109: * @param classFileCacheResourceCreator Stores compiled class <code>pkg.Cls</code> in resource <code>pkg/Cls.class</code> (see class description)
110: * @param debuggingInformation What debugging information to include into the generated class files
111: */
112: public CachingJavaSourceClassLoader(ClassLoader parentClassLoader,
113: ResourceFinder sourceFinder,
114: String optionalCharacterEncoding,
115: ResourceFinder classFileCacheResourceFinder,
116: ResourceCreator classFileCacheResourceCreator,
117: EnumeratorSet debuggingInformation) {
118: super (parentClassLoader, sourceFinder,
119: optionalCharacterEncoding, debuggingInformation);
120: this .classFileCacheResourceFinder = classFileCacheResourceFinder;
121: this .classFileCacheResourceCreator = classFileCacheResourceCreator;
122: this .sourceFinder = sourceFinder;
123: }
124:
125: /**
126: * Override {@link JavaSourceClassLoader#generateBytecodes(String)} to implement
127: * class file caching.
128: *
129: * @return String name => byte[] bytecode, or <code>null</code> if no source code could be found
130: * @throws ClassNotFoundException on compilation problems or class file cache I/O problems
131: */
132: protected Map generateBytecodes(String className)
133: throws ClassNotFoundException {
134:
135: // Check whether a class file resource exists in the cache.
136: {
137: Resource classFileResource = this .classFileCacheResourceFinder
138: .findResource(ClassFile
139: .getClassFileResourceName(className));
140: if (classFileResource != null) {
141:
142: // Check whether a source file resource exists.
143: Resource sourceResource = this .sourceFinder
144: .findResource(ClassFile
145: .getSourceResourceName(className));
146: if (sourceResource == null)
147: return null;
148:
149: // Check whether the class file is up-to-date.
150: if (sourceResource.lastModified() < classFileResource
151: .lastModified()) {
152:
153: // Yes, it is... read the bytecode from the file and define the class.
154: byte[] bytecode;
155: try {
156: bytecode = CachingJavaSourceClassLoader
157: .readResource(classFileResource);
158: } catch (IOException ex) {
159: throw new ClassNotFoundException(
160: "Reading class file from \""
161: + classFileResource + "\"", ex);
162: }
163: Map m = new HashMap();
164: m.put(className, bytecode);
165: return m;
166: }
167: }
168: }
169:
170: // Cache miss... generate the bytecode from source.
171: Map bytecodes = super .generateBytecodes(className);
172: if (bytecodes == null)
173: return null;
174:
175: // Write the generated bytecodes to the class file cache.
176: for (Iterator it = bytecodes.entrySet().iterator(); it
177: .hasNext();) {
178: Map.Entry me = (Map.Entry) it.next();
179: String className2 = (String) me.getKey();
180: byte[] bytecode = (byte[]) me.getValue();
181:
182: try {
183: CachingJavaSourceClassLoader.writeResource(
184: this .classFileCacheResourceCreator, ClassFile
185: .getClassFileResourceName(className2),
186: bytecode);
187: } catch (IOException ex) {
188: throw new ClassNotFoundException(
189: "Writing class file to \""
190: + ClassFile
191: .getClassFileResourceName(className2)
192: + "\"", ex);
193: }
194: }
195:
196: return bytecodes;
197: }
198:
199: /**
200: * Read all bytes from the given resource.
201: */
202: private static byte[] readResource(Resource r) throws IOException {
203: ByteArrayOutputStream baos = new ByteArrayOutputStream();
204: byte[] buffer = new byte[4096];
205:
206: InputStream is = r.open();
207: try {
208: for (;;) {
209: int cnt = is.read(buffer);
210: if (cnt == -1)
211: break;
212: baos.write(buffer, 0, cnt);
213: }
214: } finally {
215: try {
216: is.close();
217: } catch (IOException ex) {
218: }
219: }
220:
221: return baos.toByteArray();
222: }
223:
224: /**
225: * Create a resource with the given name and store the data in it.
226: */
227: private static void writeResource(ResourceCreator resourceCreator,
228: String resourceName, byte[] data) throws IOException {
229: OutputStream os = resourceCreator.createResource(resourceName);
230: try {
231: os.write(data);
232: } finally {
233: try {
234: os.close();
235: } catch (IOException ex) {
236: }
237: }
238: }
239: }
|