001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.loader;
031:
032: import com.caucho.log.Log;
033: import com.caucho.make.DependencyContainer;
034: import com.caucho.util.ByteBuffer;
035: import com.caucho.util.QDate;
036: import com.caucho.vfs.Depend;
037: import com.caucho.vfs.Dependency;
038: import com.caucho.vfs.JarPath;
039: import com.caucho.vfs.Path;
040: import com.caucho.vfs.PersistentDependency;
041: import com.caucho.vfs.ReadStream;
042:
043: import java.io.IOException;
044: import java.lang.ref.WeakReference;
045: import java.security.CodeSource;
046: import java.util.logging.Level;
047: import java.util.logging.Logger;
048:
049: /**
050: * Describes a cached loaded class entry.
051: */
052: public class ClassEntry implements Dependency {
053: private static final Logger log = Log.open(ClassEntry.class);
054:
055: private static boolean _hasJNIReload;
056: private static boolean _hasAnnotations;
057:
058: private DynamicClassLoader _loader;
059: private String _name;
060:
061: private Path _classPath;
062:
063: private PersistentDependency _depend;
064:
065: private boolean _classIsModified;
066:
067: private Path _sourcePath;
068: private long _sourceLastModified;
069: private long _sourceLength;
070:
071: private ClassPackage _classPackage;
072:
073: private CodeSource _codeSource;
074:
075: private ClassNotFoundException _exn;
076:
077: private WeakReference<Class> _clRef;
078:
079: /**
080: * Create a loaded class entry
081: *
082: * @param name the classname
083: * @param sourcePath path to the source Java file
084: * @param classPath path to the compiled class file
085: */
086: public ClassEntry(DynamicClassLoader loader, String name,
087: Path sourcePath, Path classPath, CodeSource codeSource) {
088: _loader = loader;
089: _name = name;
090:
091: _classPath = classPath;
092:
093: setDependPath(classPath);
094:
095: if (sourcePath != null && !sourcePath.equals(classPath)) {
096: _sourcePath = sourcePath;
097:
098: _sourceLastModified = sourcePath.getLastModified();
099: _sourceLength = sourcePath.getLength();
100: }
101:
102: _codeSource = codeSource;
103:
104: /*
105: if (codePath == null)
106: codePath = classPath;
107:
108: if (_loader.getSecurityManager() != null) {
109: try {
110: _codeSource = new CodeSource(new URL(codePath.getURL()), null);
111: } catch (Exception e) {
112: e.printStackTrace();
113: }
114: }
115: */
116: }
117:
118: /**
119: * Create a loaded class entry
120: *
121: * @param name the classname
122: * @param sourcePath path to the source Java file
123: * @param classPath path to the compiled class file
124: */
125: public ClassEntry(Loader loader, String name, Path sourcePath,
126: Path classPath) {
127: this (loader.getLoader(), name, sourcePath, classPath, loader
128: .getCodeSource(classPath));
129: }
130:
131: public String getName() {
132: return _name;
133: }
134:
135: /**
136: * returns the class loader.
137: */
138: public DynamicClassLoader getClassLoader() {
139: return _loader;
140: }
141:
142: public CodeSource getCodeSource() {
143: return _codeSource;
144: }
145:
146: public Path getSourcePath() {
147: return _sourcePath;
148: }
149:
150: /**
151: * Sets the depend path.
152: */
153: protected void setDependPath(Path dependPath) {
154: if (dependPath instanceof JarPath)
155: _depend = ((JarPath) dependPath).getDepend();
156: else
157: _depend = new Depend(dependPath);
158: }
159:
160: /**
161: * Adds the dependencies, returning true if it's adding itself.
162: */
163: protected boolean addDependencies(DependencyContainer container) {
164: if (_classPath instanceof JarPath) {
165: container.add(_depend);
166: return false;
167: } else if (_hasJNIReload) {
168: container.add(this );
169: return true;
170: } else if (_sourcePath == null) {
171: container.add(_depend);
172: return false;
173: } else {
174: container.add(this );
175: return true;
176: }
177: }
178:
179: public void setSourceLength(long length) {
180: _sourceLength = length;
181: }
182:
183: public void setSourceLastModified(long lastModified) {
184: _sourceLastModified = lastModified;
185: }
186:
187: public ClassPackage getClassPackage() {
188: return _classPackage;
189: }
190:
191: public void setClassPackage(ClassPackage pkg) {
192: _classPackage = pkg;
193: }
194:
195: /**
196: * Returns true if the source file has been modified.
197: */
198: public boolean isModified() {
199: if (_depend.isModified()) {
200: if (log.isLoggable(Level.FINE))
201: log.fine("class modified: " + _depend);
202:
203: return reloadIsModified();
204: } else if (_sourcePath == null)
205: return false;
206:
207: else if (_sourcePath.getLastModified() != _sourceLastModified) {
208: if (log.isLoggable(Level.FINE))
209: log.fine("source modified time: "
210: + _sourcePath
211: + " old:"
212: + QDate.formatLocal(_sourceLastModified)
213: + " new:"
214: + QDate.formatLocal(_sourcePath
215: .getLastModified()));
216:
217: if (!compileIsModified()) {
218: return false;
219: }
220:
221: boolean isModified = reloadIsModified();
222:
223: return isModified;
224: } else if (_sourcePath.getLength() != _sourceLength) {
225: if (log.isLoggable(Level.FINE))
226: log.fine("source modified length: " + _sourcePath
227: + " old:" + _sourceLength + " new:"
228: + _sourcePath.getLength());
229:
230: if (!compileIsModified())
231: return false;
232:
233: return reloadIsModified();
234: } else {
235: return false;
236: }
237: }
238:
239: /**
240: * Returns true if the source file has been modified.
241: */
242: public boolean logModified(Logger log) {
243: if (_depend.logModified(log)) {
244: return true;
245: } else if (_sourcePath == null)
246: return false;
247:
248: else if (_sourcePath.getLastModified() != _sourceLastModified) {
249: log.info("source modified time: " + _sourcePath + " old:"
250: + QDate.formatLocal(_sourceLastModified) + " new:"
251: + QDate.formatLocal(_sourcePath.getLastModified()));
252:
253: return true;
254: } else if (_sourcePath.getLength() != _sourceLength) {
255: log
256: .info("source modified length: " + _sourcePath
257: + " old:" + _sourceLength + " new:"
258: + _sourcePath.getLength());
259:
260: return true;
261: } else {
262: return false;
263: }
264: }
265:
266: /**
267: * Returns true if the compile doesn't avoid the dependency.
268: */
269: public boolean compileIsModified() {
270: return true;
271: }
272:
273: /**
274: * Returns true if the reload doesn't avoid the dependency.
275: */
276: public boolean reloadIsModified() {
277: if (_classIsModified) {
278: return true;
279: }
280:
281: if (!_hasJNIReload || !_classPath.canRead()) {
282: return true;
283: }
284:
285: try {
286: long length = _classPath.getLength();
287:
288: Class cl = _clRef != null ? _clRef.get() : null;
289:
290: if (cl == null) {
291: return false;
292: }
293:
294: if (_hasAnnotations && requireReload(cl))
295: return true;
296:
297: ReadStream is = _classPath.openRead();
298:
299: byte[] bytecode = new byte[(int) length];
300:
301: try {
302: is.readAll(bytecode, 0, bytecode.length);
303: } finally {
304: is.close();
305: }
306:
307: int result = reloadNative(cl, bytecode, 0, bytecode.length);
308:
309: if (result != 0) {
310: _classIsModified = true;
311: return true;
312: }
313:
314: setDependPath(_classPath);
315:
316: if (_sourcePath != null) {
317: _sourceLastModified = _sourcePath.getLastModified();
318: _sourceLength = _sourcePath.getLength();
319: }
320:
321: log.info("Reloading " + cl.getName());
322:
323: return false;
324: } catch (Exception e) {
325: log.log(Level.WARNING, e.toString(), e);
326: _classIsModified = true;
327:
328: return true;
329: }
330: }
331:
332: /**
333: * Returns the path to the class file.
334: */
335: public Path getClassPath() {
336: return _classPath;
337: }
338:
339: public Class getEntryClass() {
340: return _clRef != null ? _clRef.get() : null;
341: }
342:
343: public void setEntryClass(Class cl) {
344: _clRef = new WeakReference<Class>(cl);
345: }
346:
347: /**
348: * preload actions.
349: */
350: public void preLoad() throws ClassNotFoundException {
351: }
352:
353: /**
354: * Loads the contents of the class file into the buffer.
355: */
356: public void load(ByteBuffer buffer) throws IOException {
357: synchronized (this ) {
358: Path classPath = getClassPath();
359:
360: buffer.clear();
361: long length = classPath.getLength();
362: if (length < 0)
363: throw new IOException("missing class: " + classPath);
364: ReadStream is = classPath.openRead();
365: try {
366: buffer.setLength((int) length);
367: if (is.readAll(buffer.getBuffer(), 0, (int) length) != length)
368: throw new IOException("class file length mismatch");
369: } finally {
370: is.close();
371: }
372: }
373: }
374:
375: /**
376: * post-load actions.
377: */
378: public boolean postLoad() {
379: return false;
380: }
381:
382: public static boolean hasJNIReload() {
383: return _hasJNIReload;
384: }
385:
386: private static boolean requireReload(Class cl) {
387: return cl.isAnnotationPresent(RequireReload.class);
388: }
389:
390: public String toString() {
391: if (_sourcePath == null)
392: return "ClassEntry[" + _classPath + "]";
393: else
394: return "ClassEntry[" + _classPath + ", src=" + _sourcePath
395: + "]";
396: }
397:
398: public static native boolean canReloadNative();
399:
400: public static native int reloadNative(Class cl, byte[] bytes,
401: int offset, int length);
402:
403: class ReloadThread implements Runnable {
404: private volatile boolean _isDone;
405:
406: public boolean isDone() {
407: return _isDone;
408: }
409:
410: public void run() {
411: }
412: }
413:
414: static {
415: try {
416: System.loadLibrary("resin_os");
417: _hasJNIReload = canReloadNative();
418: if (_hasJNIReload)
419: log
420: .config("In-place class redefinition (HotSwap) is available.");
421: else
422: log
423: .config("In-place class redefinition (HotSwap) is not available. In-place class reloading during development requires a compatible JDK and -Xdebug.");
424: } catch (Throwable e) {
425: log.fine(e.toString());
426:
427: log.log(Level.FINEST, e.toString(), e);
428: // log.config("In-place class redefinition (HotSwap) is not available.\n" + e);
429: }
430:
431: try {
432: Class reloadClass = Class
433: .forName("com.caucho.loader.RequireReload");
434:
435: Object.class.getAnnotation(reloadClass);
436:
437: _hasAnnotations = true;
438: } catch (Throwable e) {
439: }
440: }
441: }
|