package org.enumerable.lambda.weaving;
import org.enumerable.lambda.weaving.tree.LambdaTreeTransformer;
import java.io.*;
import java.util.Enumeration;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import static java.lang.System.*;
import static org.enumerable.lambda.exception.UncheckedException.uncheck;
import static org.enumerable.lambda.weaving.ClassFilter.createClassFilter;
import static org.enumerable.lambda.weaving.Debug.debug;
import static org.enumerable.lambda.weaving.Version.getVersionString;
/**
* AOT-compiler for lambdas, to side-step the need for the agent. Takes a list
* of class directories/jars and compiles them in place, the originals will be
* overwritten.
* <p>
* The classpath must contain all dependencies for the directories/jars when
* compiling.
* <p>
* The enumerable-agent-<version>.jar will still be as a normal runtime
* dependency after compilation.
*/
@SuppressWarnings({"ResultOfMethodCallIgnored"})
public class LambdaCompiler {
static final String AOT_COMPILED_MARKER = "META-INF/lambda.aot.compiled";
public static void main(String[] args) throws Exception {
out.println("[compiler] " + getVersionString());
if (args.length == 0) {
out.println("Usage: <jar>|<dir>...");
return;
}
for (String name : args) {
File file = new File(name);
if (!file.isDirectory() && !file.getName().endsWith(".jar")) {
out.println(file.getAbsolutePath() + " is neither a jar or a directory, exiting");
exit(1);
}
}
new LambdaCompiler().compile(args);
}
LambdaTreeTransformer transformer = new LambdaTreeTransformer();
byte[] buffer = new byte[8 * 1024];
void compile(String[] args) throws Exception {
for (String name : args) {
File file = new File(name);
debug("compiling " + file.getPath());
if (file.isDirectory()) {
compileClassesDirectory(file);
} else if (file.getName().endsWith(".jar"))
compilieJar(file);
}
}
private void compileClassesDirectory(File file) throws Exception {
File aotCompiledMarker = new File(file, AOT_COMPILED_MARKER);
if (aotCompiledMarker.exists()) {
out.println(file + " is already compiled, skipping.");
return;
}
aotCompiledMarker.getParentFile().mkdir();
aotCompiledMarker.createNewFile();
ensureCreated(aotCompiledMarker);
compileDirectory(file);
debug("writing generated lambdas in " + file);
writeGeneratedLambdas(file);
}
void compileDirectory(File dir) throws Exception {
for (File file : dir.listFiles())
if (file.getName().endsWith(".class"))
compileFile(file);
else if (file.isDirectory())
compileDirectory(file);
}
void compileFile(File file) throws Exception {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream(file);
byte[] bs = transformer.transform(ClassLoader.getSystemClassLoader(), createClassFilter(), null, in);
in.close();
if (bs != null) {
out = new FileOutputStream(file);
out.write(bs);
}
} finally {
if (in != null)
try {
in.close();
} catch (IOException silent) {
}
if (out != null)
try {
out.close();
} catch (IOException silent) {
}
}
}
void compilieJar(File jar) throws Exception {
try {
JarFile jarFile = new JarFile(jar);
if (jarFile.getEntry(AOT_COMPILED_MARKER) != null) {
out.println(jar + " is already compiled, skipping.");
return;
}
File tempDir = unjar(jarFile);
File aotCompiledMarker = new File(tempDir, AOT_COMPILED_MARKER);
aotCompiledMarker.getParentFile().mkdir();
aotCompiledMarker.createNewFile();
ensureCreated(aotCompiledMarker);
compileDirectory(tempDir);
debug("writing generated lambdas in " + jar);
writeGeneratedLambdas(tempDir);
File newJar = jar(tempDir);
File bak = new File(jar.getAbsolutePath() + ".bak");
rename(jar, bak);
ensureCreated(bak);
rename(newJar, jar);
ensureCreated(jar);
delete(bak);
delete(tempDir);
} catch (Exception e) {
throw uncheck(e);
}
}
private void rename(File from, File to) {
if (!from.renameTo(to)) throw new IllegalStateException("Could not rename " + from + " to " + to);
}
void delete(File file) {
if (file.isDirectory())
for (File child : file.listFiles())
delete(child);
file.delete();
}
void ensureCreated(File file) {
if (!file.isFile()) throw new IllegalStateException("Could not create " + file);
}
void ensureDirCreated(File dir) throws IOException {
if (!dir.mkdir()) throw new IOException("Could not create directory " + dir);
}
void writeGeneratedLambdas(File tempDir) throws IOException {
for (Map.Entry<String, byte[]> lambdaClass : transformer.getLambdasByClassName().entrySet()) {
File lambdaFile = new File(tempDir, lambdaClass.getKey().replace('.', '/'));
FileOutputStream out = null;
try {
out = new FileOutputStream(lambdaFile + ".class");
out.write(lambdaClass.getValue());
} finally {
if (out != null)
out.close();
}
}
}
File jar(File dir) throws Exception {
File newJar = new File(dir.getAbsolutePath() + ".jar");
JarOutputStream out = null;
try {
out = new JarOutputStream(new FileOutputStream(newJar));
addDirToJar(dir, dir, out);
} finally {
if (out != null)
out.close();
}
return newJar;
}
void addDirToJar(File baseDir, File dir, JarOutputStream out) throws IOException {
for (File file : dir.listFiles()) {
String nameInJar = file.getPath().substring(baseDir.getPath().length() + 1).replace(File.separatorChar,
'/');
if (file.isDirectory()) {
JarEntry jarEntry = new JarEntry(nameInJar.endsWith("/") ? nameInJar : nameInJar + "/");
jarEntry.setTime(file.lastModified());
out.putNextEntry(jarEntry);
out.closeEntry();
addDirToJar(baseDir, file, out);
} else {
JarEntry jarEntry = new JarEntry(nameInJar);
jarEntry.setTime(file.lastModified());
out.putNextEntry(jarEntry);
InputStream in = null;
try {
in = new FileInputStream(file);
int read;
while ((read = in.read(buffer)) != -1)
out.write(buffer, 0, read);
out.closeEntry();
} finally {
if (in != null)
in.close();
}
}
}
}
File unjar(JarFile jarFile) throws IOException {
InputStream in = null;
OutputStream out = null;
File tempDir = new File(getProperty("java.io.tmpdir"), new File(jarFile.getName()).getName() + "-" + currentTimeMillis());
ensureDirCreated(tempDir);
try {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
File file = new File(tempDir, jarEntry.getName());
if (jarEntry.isDirectory()) {
file.mkdir();
} else {
file.getParentFile().mkdirs();
in = jarFile.getInputStream(jarEntry);
out = new FileOutputStream(file);
int read;
while ((read = in.read(buffer)) != -1)
out.write(buffer, 0, read);
out.flush();
}
}
return tempDir;
} finally {
if (out != null)
out.close();
if (in != null)
in.close();
jarFile.close();
}
}
}