blob: f8137c4d30943a730866c5b07cd1f1bcf19401f8 [file] [log] [blame]
/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.tests.separate;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ConcurrentHashMap;
import java.io.*;
import java.net.URI;
import javax.tools.*;
import com.sun.source.util.JavacTask;
import static org.openjdk.tests.separate.SourceModel.Type;
import static org.openjdk.tests.separate.SourceModel.Class;
import static org.openjdk.tests.separate.SourceModel.Extends;
import static org.openjdk.tests.separate.SourceModel.SourceProcessor;
public class Compiler {
public enum Flags {
VERBOSE, // Prints out files as they are compiled
USECACHE // Keeps results around for reuse. Only use this is
// you're sure that each compilation name maps to the
// same source code
}
private static final AtomicInteger counter = new AtomicInteger();
private static final String targetDir = "gen-separate";
private static final File root = new File(targetDir);
private static ConcurrentHashMap<String,File> cache =
new ConcurrentHashMap<>();
Set<Flags> flags;
private JavaCompiler systemJavaCompiler;
private StandardJavaFileManager fm;
private List<File> tempDirs;
private List<ClassFilePreprocessor> postprocessors;
private static class SourceFile extends SimpleJavaFileObject {
private final String content;
public SourceFile(String name, String content) {
super(URI.create("myfo:/" + name + ".java"), Kind.SOURCE);
this.content = content;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return toString();
}
public String toString() { return this.content; }
}
public Compiler(Flags ... flags) {
setFlags(flags);
this.tempDirs = new ArrayList<>();
this.postprocessors = new ArrayList<>();
this.systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
this.fm = systemJavaCompiler.getStandardFileManager(null, null, null);
}
public void setFlags(Flags ... flags) {
this.flags = new HashSet<>(Arrays.asList(flags));
}
public void addPostprocessor(ClassFilePreprocessor cfp) {
this.postprocessors.add(cfp);
}
/**
* Compile hierarchies starting with each of the 'types' and return
* a ClassLoader that can be used to load the compiled classes.
*/
public ClassLoader compile(Type ... types) {
ClassFilePreprocessor[] cfps = this.postprocessors.toArray(
new ClassFilePreprocessor[0]);
DirectedClassLoader dcl = new DirectedClassLoader(cfps);
for (Type t : types) {
for (Map.Entry<String,File> each : compileHierarchy(t).entrySet()) {
dcl.setLocationFor(each.getKey(), each.getValue());
}
}
return dcl;
}
/**
* Compiles and loads a hierarchy, starting at 'type'
*/
public java.lang.Class<?> compileAndLoad(Type type)
throws ClassNotFoundException {
ClassLoader loader = compile(type);
return java.lang.Class.forName(type.getName(), false, loader);
}
/**
* Compiles a hierarchy, starting at 'type' and return a mapping of the
* name to the location where the classfile for that type resides.
*/
private Map<String,File> compileHierarchy(Type type) {
HashMap<String,File> outputDirs = new HashMap<>();
File outDir = compileOne(type);
outputDirs.put(type.getName(), outDir);
Class superClass = type.getSuperclass();
if (superClass != null)
outputDirs.putAll(compileHierarchy(superClass));
for (Extends ext : type.getSupertypes())
outputDirs.putAll(compileHierarchy(ext.getType()));
return outputDirs;
}
private File compileOne(Type type) {
if (this.flags.contains(Flags.USECACHE)) {
File dir = cache.get(type.getName());
if (dir != null) {
return dir;
}
}
List<JavaFileObject> files = new ArrayList<>();
SourceProcessor accum =
(name, src) -> { files.add(new SourceFile(name, src)); };
Collection<Type> deps = type.typeDependencies(type.isFullCompilation());
for (Type dep : deps) {
if (type.isFullCompilation())
dep.generate(accum);
else
dep.generateAsDependency(accum, type.methodDependencies());
}
type.generate(accum);
JavacTask ct = (JavacTask)this.systemJavaCompiler.getTask(
null, this.fm, null, null, null, files);
File destDir = null;
do {
int value = counter.incrementAndGet();
destDir = new File(root, Integer.toString(value));
} while (destDir.exists());
if (this.flags.contains(Flags.VERBOSE)) {
System.out.println("Compilation unit for " + type.getName() +
" : compiled into " + destDir);
for (JavaFileObject jfo : files) {
System.out.println(jfo.toString());
}
}
try {
destDir.mkdirs();
this.fm.setLocation(
StandardLocation.CLASS_OUTPUT, Arrays.asList(destDir));
} catch (IOException e) {
throw new RuntimeException(
"IOException encountered during compilation", e);
}
Boolean result = ct.call();
if (result == Boolean.FALSE) {
throw new RuntimeException(
"Compilation failure in " + type.getName() + " unit");
}
if (this.flags.contains(Flags.USECACHE)) {
File existing = cache.putIfAbsent(type.getName(), destDir);
if (existing != null) {
deleteDir(destDir);
return existing;
}
} else {
this.tempDirs.add(destDir);
}
return destDir;
}
private static void deleteDir(File dir) {
for (File f : dir.listFiles()) {
f.delete();
};
dir.delete();
}
public void cleanup() {
if (!this.flags.contains(Flags.USECACHE)) {
for (File d : tempDirs) {
deleteDir(d);
};
tempDirs = new ArrayList<>();
}
}
// Removes all of the elements in the cache and deletes the associated
// output directories. This may not actually empty the cache if there
// are concurrent users of it.
public static void purgeCache() {
for (Map.Entry<String,File> entry : cache.entrySet()) {
cache.remove(entry.getKey());
deleteDir(entry.getValue());
}
}
}