Skip to content

Commit

Permalink
[playframework#674] improves speed when looking for new / removed cla…
Browse files Browse the repository at this point in the history
…sses in test-mode by caching the regexp-result for each file until the file has changed on disk.
  • Loading branch information
mbknor committed Mar 23, 2011
1 parent 83fae71 commit 93e2a09
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 24 deletions.
30 changes: 6 additions & 24 deletions framework/src/play/classloading/ApplicationClassloader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,6 +39,9 @@
*/
public class ApplicationClassloader extends ClassLoader {


private final ClassStateHashCreator classStateHashCreator = new ClassStateHashCreator();

/**
* This protection domain applies to all loaded classes.
*/
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}

/**
Expand Down
99 changes: 99 additions & 0 deletions framework/src/play/classloading/hash/ClassStateHashCreator.java
Original file line number Diff line number Diff line change
@@ -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<File, FileWithClassDefs> classDefsInFileCache = new HashMap<File, FileWithClassDefs>();

public synchronized int computePathHash(List<VirtualFile> 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;
}


}

0 comments on commit 93e2a09

Please sign in to comment.