From 93e2a090d9b9130e7c3e4161bdc94974da376d0a Mon Sep 17 00:00:00 2001 From: Morten Kjetland Date: Wed, 23 Mar 2011 08:24:02 +0100 Subject: [PATCH] [#674] improves speed when looking for new / removed classes in test-mode by caching the regexp-result for each file until the file has changed on disk. --- .../classloading/ApplicationClassloader.java | 30 ++---- .../hash/ClassStateHashCreator.java | 99 +++++++++++++++++++ 2 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 framework/src/play/classloading/hash/ClassStateHashCreator.java diff --git a/framework/src/play/classloading/ApplicationClassloader.java b/framework/src/play/classloading/ApplicationClassloader.java index c074c18f34..8360082d7e 100644 --- a/framework/src/play/classloading/ApplicationClassloader.java +++ b/framework/src/play/classloading/ApplicationClassloader.java @@ -26,6 +26,7 @@ import play.Logger; import play.Play; +import play.classloading.hash.ClassStateHashCreator; import play.vfs.VirtualFile; import play.cache.Cache; import play.classloading.ApplicationClasses.ApplicationClass; @@ -38,6 +39,9 @@ */ public class ApplicationClassloader extends ClassLoader { + + private final ClassStateHashCreator classStateHashCreator = new ClassStateHashCreator(); + /** * This protection domain applies to all loaded classes. */ @@ -319,6 +323,7 @@ public void detectChanges() { if (dirtySig) { throw new RuntimeException("Signature change !"); } + // Now check if there is new classes or removed classes int hash = computePathHash(); if (hash != this.pathHash) { @@ -347,30 +352,7 @@ public void detectChanges() { int pathHash = 0; int computePathHash() { - StringBuffer buf = new StringBuffer(); - for (VirtualFile virtualFile : Play.javaPath) { - scan(buf, virtualFile); - } - return buf.toString().hashCode(); - } - - void scan(StringBuffer buf, VirtualFile current) { - if (!current.isDirectory()) { - if (current.getName().endsWith(".java")) { - Matcher matcher = Pattern.compile("\\s+class\\s([a-zA-Z0-9_]+)\\s+").matcher(current.contentAsString()); - buf.append(current.getName()); - buf.append("("); - while (matcher.find()) { - buf.append(matcher.group(1)); - buf.append(","); - } - buf.append(")"); - } - } else if (!current.getName().startsWith(".")) { - for (VirtualFile virtualFile : current.list()) { - scan(buf, virtualFile); - } - } + return classStateHashCreator.computePathHash(Play.javaPath); } /** diff --git a/framework/src/play/classloading/hash/ClassStateHashCreator.java b/framework/src/play/classloading/hash/ClassStateHashCreator.java new file mode 100644 index 0000000000..8c1eb1882c --- /dev/null +++ b/framework/src/play/classloading/hash/ClassStateHashCreator.java @@ -0,0 +1,99 @@ +package play.classloading.hash; + +import play.vfs.VirtualFile; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ClassStateHashCreator { + + private final Pattern classDefFinderPattern = Pattern.compile("\\s+class\\s([a-zA-Z0-9_]+)\\s+"); + + private static class FileWithClassDefs { + private final File file; + private final long size; + private final long lastModified; + private final String classDefs; + + private FileWithClassDefs(File file, String classDefs) { + this.file = file; + this.classDefs = classDefs; + + // store size and time for this file.. + size = file.length(); + lastModified = file.lastModified(); + } + + /** + * @return true if file has changed on disk + */ + public boolean fileNotChanges( ) { + return size == file.length() && lastModified == file.lastModified(); + } + + public String getClassDefs() { + return classDefs; + } + } + + private final Map classDefsInFileCache = new HashMap(); + + public synchronized int computePathHash(List paths) { + StringBuffer buf = new StringBuffer(); + for (VirtualFile virtualFile : paths) { + scan(buf, virtualFile); + } + // TODO: should use better hashing-algorithm.. MD5? SHA1? + // I think hashCode() has too many collisions.. + return buf.toString().hashCode(); + } + + private void scan(StringBuffer buf, VirtualFile current) { + if (!current.isDirectory()) { + if (current.getName().endsWith(".java")) { + buf.append( getClassDefsForFile(current)); + } + } else if (!current.getName().startsWith(".")) { + // TODO: we could later optimizie it further if we check if the entire folder is unchanged + for (VirtualFile virtualFile : current.list()) { + scan(buf, virtualFile); + } + } + } + + private String getClassDefsForFile( VirtualFile current ) { + + File realFile = current.getRealFile(); + // first we look in cache + + FileWithClassDefs fileWithClassDefs = classDefsInFileCache.get( realFile ); + if( fileWithClassDefs != null && fileWithClassDefs.fileNotChanges() ) { + // found the file in cache and it has not changed on disk + return fileWithClassDefs.getClassDefs(); + } + + // didn't find it or it has changed on disk + // we must re-parse it + + StringBuilder buf = new StringBuilder(); + Matcher matcher = classDefFinderPattern.matcher(current.contentAsString()); + buf.append(current.getName()); + buf.append("("); + while (matcher.find()) { + buf.append(matcher.group(1)); + buf.append(","); + } + buf.append(")"); + String classDefs = buf.toString(); + + // store it in cache + classDefsInFileCache.put( realFile, new FileWithClassDefs(realFile, classDefs)); + return classDefs; + } + + +}