play.classloading.BytecodeCache Maven / Gradle / Ivy
package play.classloading;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.MessageDigest;
import play.Logger;
import play.Play;
import play.PlayPlugin;
/**
* Used to speed up compilation time
*/
public class BytecodeCache {
/**
* Delete the bytecode
* @param name Cache name
*/
public static void deleteBytecode(String name) {
try {
if (!Play.initialized || Play.tmpDir == null || Play.readOnlyTmp || !Play.configuration.getProperty("play.bytecodeCache", "true").equals("true")) {
return;
}
File f = cacheFile(name.replace("/", "_").replace("{", "_").replace("}", "_").replace(":", "_"));
if (f.exists()) {
f.delete();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Retrieve the bytecode if source has not changed
* @param name The cache name
* @param source The source code
* @return The bytecode
*/
public static byte[] getBytecode(String name, String source) {
try {
if (!Play.initialized || Play.tmpDir == null || !Play.configuration.getProperty("play.bytecodeCache", "true").equals("true")) {
return null;
}
File f = cacheFile(name.replace("/", "_").replace("{", "_").replace("}", "_").replace(":", "_"));
if (f.exists()) {
FileInputStream fis = new FileInputStream(f);
// Read hash
int offset = 0;
int read = -1;
StringBuilder hash = new StringBuilder();
while ((read = fis.read()) != 0) {
hash.append((char) read);
offset++;
}
if (!hash(source).equals(hash.toString())) {
if (Logger.isTraceEnabled()) {
Logger.trace("Bytecode too old (%s != %s)", hash, hash(source));
}
return null;
}
byte[] byteCode = new byte[(int) f.length() - (offset + 1)];
fis.read(byteCode);
fis.close();
return byteCode;
}
if (Logger.isTraceEnabled()) {
Logger.trace("Cache MISS for %s", name);
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Cache the bytecode
* @param byteCode The bytecode
* @param name The cache name
* @param source The corresponding source
*/
public static void cacheBytecode(byte[] byteCode, String name, String source) {
try {
if (!Play.initialized || Play.tmpDir == null || Play.readOnlyTmp || !Play.configuration.getProperty("play.bytecodeCache", "true").equals("true")) {
return;
}
File f = cacheFile(name.replace("/", "_").replace("{", "_").replace("}", "_").replace(":", "_"));
FileOutputStream fos = new FileOutputStream(f);
fos.write(hash(source).getBytes("utf-8"));
fos.write(0);
fos.write(byteCode);
fos.close();
// emit bytecode to standard class layout as well
if(!name.contains("/") && !name.contains("{")) {
f = new File(Play.tmpDir, "classes/"+(name.replace(".", "/"))+".class");
f.getParentFile().mkdirs();
fos = new FileOutputStream(f);
fos.write(byteCode);
fos.close();
}
if (Logger.isTraceEnabled()) {
Logger.trace("%s cached", name);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Build a hash of the source code.
* To efficiently track source code modifications.
*/
static String hash(String text) {
try {
StringBuffer plugins = new StringBuffer();
for(PlayPlugin plugin : Play.pluginCollection.getEnabledPlugins()) {
plugins.append(plugin.getClass().getName());
}
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.reset();
messageDigest.update((Play.version + plugins.toString() + text).getBytes("utf-8"));
byte[] digest = messageDigest.digest();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < digest.length; ++i) {
int value = digest[i];
if (value < 0) {
value += 256;
}
builder.append(Integer.toHexString(value));
}
return builder.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Retrieve the real file that will be used as cache.
*/
static File cacheFile(String id) {
File dir = new File(Play.tmpDir, "bytecode/" + Play.mode.name());
if (!dir.exists() && Play.tmpDir != null && !Play.readOnlyTmp) {
dir.mkdirs();
}
return new File(dir, id);
}
}