package ca.uwaterloo.ece.qhanam.jrsrepair.compiler; import java.io.File; import java.io.FileFilter; import java.io.StringWriter; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import ca.uwaterloo.ece.qhanam.jrsrepair.DocumentASTRewrite; import ca.uwaterloo.ece.qhanam.jrsrepair.Utilities; /** * From https://weblogs.java.net/blog/malenkov/archive/2008/12/how_to_compile.html */ public class JavaJDKCompiler { private MemoryClassLoader mcl; private Map<String, DocumentASTRewrite> sourceFileContents; private String[] sourcePaths; private String classDirectory; private String[] classpath; private Queue<String> errors; private String[] copyIncludes; private String[] copyExcludes; public JavaJDKCompiler(String classDirectory, String[] classpath, Map<String, DocumentASTRewrite> sourceFileContents, String[] sourcePaths, String[] copyIncludes, String[] copyExcludes ){ this.classDirectory = classDirectory; this.classpath = classpath; this.mcl = null; this.errors = new LinkedList<String>(); this.sourceFileContents = sourceFileContents; this.sourcePaths = sourcePaths; this.copyIncludes = copyIncludes; this.copyExcludes = copyExcludes; } /** * Compiles the Java source file and writes the resulting .class file to the build/classes * directory. Returns the result of the compilation (COMPILED if ok or NOT_COMPILED if * there was an error). * @param packageName * @param className * @param document * @return JRSRepair.TestStatus (NOT_COMPILED or COMPILED) * @throws Exception */ public Status compile() throws Exception{ StringWriter output = new StringWriter(); /* Build the map of source files that the compiler will read from. The * modified files will be returned by DocumentASTRewrite. */ Map<String, String> sourceMap = this.buildSourceMap(); /* Compile the Java file. */ this.mcl = new MemoryClassLoader(sourceMap, this.classpath, output); /* Check the compilation went ok. */ if(output.toString().matches("(?s).*\\d+ errors?.*")){ this.errors.add(output.toString()); return Status.NOT_COMPILED; } /* Write the class files to disk. */ this.errors.add("Compiled"); this.storeCompiled(this.classDirectory); /* Copy data files to be packaged with class files. If copyIncludes is * empty, don't copy anything. */ if(this.copyIncludes.length > 0){ JavaSourceFilter javaSourceFilter = new JavaSourceFilter(this.copyIncludes, this.copyExcludes); for(String sourcePath : this.sourcePaths) { Utilities.copyDataFiles(new File(sourcePath), new File(this.classDirectory), javaSourceFilter); } } return Status.COMPILED; } /** * Stores the class files in the given directory. * @param directory Base directory for .class files. */ public void storeCompiled(String directory){ /* Write the class files to disk. */ List<Output> classFiles = mcl.getAllClasses(); /* Write the class to disk. */ try{ for(Output classFile : classFiles){ File f = new File(directory, classFile.getName()); f.getParentFile().mkdirs(); Utilities.writeToFile(new File(directory, classFile.getName()), classFile.toByteArray()); } }catch (Exception e){ System.out.println(e.getMessage()); } } /** * Returns the error message at the head of the queue. * * Recommended use is to dequeue after each compile, or * wait until execution has finished and iterate through * the queue. * @return */ public String dequeueCompileError() { return this.errors.remove(); } /** * Build the map of source files that the compiler will read from. * @throws Exception */ private Map<String, String> buildSourceMap() throws Exception{ Map<String, String> map = new HashMap<String, String>(); for(String sourcePath : this.sourceFileContents.keySet()){ DocumentASTRewrite drwt = this.sourceFileContents.get(sourcePath); map.put(this.getRelativePath(sourcePath), drwt.modifiedDocument.get()); drwt.untaintDocument(); } return map; } /** * Gets the relative path of the source (.java) file from its source * directory. We need to do this because JRSRepair handles multiple * source directory locations. * @param sourceFile * @return Relative path to .java file (i.e. [package]/[class]) * @throws Exception */ private String getRelativePath(String sourceFile) throws Exception{ for(String path : this.sourcePaths){ File directory = new File(path); File file = new File(sourceFile); if(isSubDirectory(directory, file)){ String relativePath = directory.toURI().relativize(file.toURI()).getPath(); relativePath = relativePath.substring(0, relativePath.length() - 5); return relativePath; } } return null; } /** * Checks, whether the child directory is a subdirectory of the base * directory. * * http://www.java2s.com/Tutorial/Java/0180__File/Checkswhetherthechilddirectoryisasubdirectoryofthebasedirectory.htm * * @param base the base directory. * @param child the suspected child directory. * @return true, if the child is a subdirectory of the base directory. * @throws IOException if an IOError occured during the test. */ private boolean isSubDirectory(File base, File child) throws Exception { base = base.getCanonicalFile(); child = child.getCanonicalFile(); File parentFile = child; while (parentFile != null) { if (base.equals(parentFile)) { return true; } parentFile = parentFile.getParentFile(); } return false; } /** * The possible results from the compile method. */ public enum Status{ NOT_COMPILED, COMPILED } private class JavaSourceFilter implements FileFilter { String[] includes; String[] excludes; public JavaSourceFilter(String[] includes, String[] excludes){ this.includes = includes; this.excludes = excludes; } public boolean accept(File pathname){ /* If includes is empty, nothing can match. */ if(includes.length == 0) return false; /* Include condition. */ for(String include : this.includes){ if(!pathname.getName().matches(include)){ return false; } } /* Exclude condition. */ for(String exclude : this.excludes){ if(pathname.getName().matches(exclude)){ return false; } } return true; } } }