1 /* 2 * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package separate; 25 26 import java.util.*; 27 import java.util.concurrent.atomic.AtomicInteger; 28 import java.util.concurrent.ConcurrentHashMap; 29 import java.io.*; 30 import java.net.URI; 31 import javax.tools.*; 32 33 import com.sun.source.util.JavacTask; 34 import java.nio.file.FileVisitResult; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.nio.file.SimpleFileVisitor; 38 import java.nio.file.attribute.BasicFileAttributes; 39 40 import static separate.SourceModel.Type; 41 import static separate.SourceModel.Class; 42 import static separate.SourceModel.Extends; 43 import static separate.SourceModel.SourceProcessor; 44 45 public class Compiler { 46 47 public enum Flags { 48 VERBOSE, // Prints out files as they are compiled 49 USECACHE // Keeps results around for reuse. Only use this is 50 // you're sure that each compilation name maps to the 51 // same source code 52 }; 53 54 private static final AtomicInteger counter = new AtomicInteger(); 55 private static final String targetDir = 56 System.getProperty("lambda.separate.targetDirectory", 57 "." + File.separator + "gen-separate"); 58 private static final File root = new File(targetDir); 59 private static ConcurrentHashMap<String,File> cache = 60 new ConcurrentHashMap<>(); 61 62 Set<Flags> flags; 63 64 private JavaCompiler systemJavaCompiler; 65 private StandardJavaFileManager fm; 66 private List<File> tempDirs; 67 private List<ClassFilePreprocessor> postprocessors; 68 69 private static class SourceFile extends SimpleJavaFileObject { 70 private final String content; 71 72 public SourceFile(String name, String content) { 73 super(URI.create("myfo:/" + name + ".java"), Kind.SOURCE); 74 this.content = content; 75 } 76 77 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 78 return toString(); 79 } 80 81 public String toString() { return this.content; } 82 } 83 84 public Compiler(Flags ... flags) { 85 setFlags(flags); 86 this.tempDirs = new ArrayList<>(); 87 this.postprocessors = new ArrayList<>(); 88 this.systemJavaCompiler = ToolProvider.getSystemJavaCompiler(); 89 this.fm = systemJavaCompiler.getStandardFileManager(null, null, null); 90 } 91 92 public void setFlags(Flags ... flags) { 93 this.flags = new HashSet<Flags>(Arrays.asList(flags)); 94 } 95 96 public void addPostprocessor(ClassFilePreprocessor cfp) { 97 this.postprocessors.add(cfp); 98 } 99 100 /** 101 * Compile hierarchies starting with each of the 'types' and return 102 * a ClassLoader that can be used to load the compiled classes. 103 */ 104 public ClassLoader compile(Type ... types) { 105 ClassFilePreprocessor[] cfps = this.postprocessors.toArray( 106 new ClassFilePreprocessor[0]); 107 108 DirectedClassLoader dcl = new DirectedClassLoader(cfps); 109 110 for (Type t : types) { 111 for (Map.Entry<String,File> each : compileHierarchy(t).entrySet()) { 112 dcl.setLocationFor(each.getKey(), each.getValue()); 113 } 114 } 115 return dcl; 116 } 117 118 /** 119 * Compiles and loads a hierarchy, starting at 'type' 120 */ 121 public java.lang.Class<?> compileAndLoad(Type type) 122 throws ClassNotFoundException { 123 124 ClassLoader loader = compile(type); 125 return java.lang.Class.forName(type.getName(), false, loader); 126 } 127 128 /** 129 * Compiles a hierarchy, starting at 'type' and return a mapping of the 130 * name to the location where the classfile for that type resides. 131 */ 132 private Map<String,File> compileHierarchy(Type type) { 133 HashMap<String,File> outputDirs = new HashMap<>(); 134 135 File outDir = compileOne(type); 136 outputDirs.put(type.getName(), outDir); 137 138 Class superClass = type.getSuperclass(); 139 if (superClass != null) { 140 for( Map.Entry<String,File> each : compileHierarchy(superClass).entrySet()) { 141 outputDirs.put(each.getKey(), each.getValue()); 142 } 143 } 144 for (Extends ext : type.getSupertypes()) { 145 Type iface = ext.getType(); 146 for( Map.Entry<String,File> each : compileHierarchy(iface).entrySet()) { 147 outputDirs.put(each.getKey(), each.getValue()); 148 } 149 } 150 151 return outputDirs; 152 } 153 154 private File compileOne(Type type) { 155 if (this.flags.contains(Flags.USECACHE)) { 156 File dir = cache.get(type.getName()); 157 if (dir != null) { 158 return dir; 159 } 160 } 161 List<JavaFileObject> files = new ArrayList<>(); 162 SourceProcessor accum = (name, src) -> files.add(new SourceFile(name, src)); 163 164 for (Type dep : type.typeDependencies()) { 165 dep.generateAsDependency(accum, type.methodDependencies()); 166 } 167 168 type.generate(accum); 169 170 JavacTask ct = (JavacTask)this.systemJavaCompiler.getTask( 171 null, this.fm, null, null, null, files); 172 File destDir = null; 173 do { 174 int value = counter.incrementAndGet(); 175 destDir = new File(root, Integer.toString(value)); 176 } while (destDir.exists()); 177 178 if (this.flags.contains(Flags.VERBOSE)) { 179 System.out.println("Compilation unit for " + type.getName() + 180 " : compiled into " + destDir); 181 for (JavaFileObject jfo : files) { 182 System.out.println(jfo.toString()); 183 } 184 } 185 186 try { 187 destDir.mkdirs(); 188 this.fm.setLocation( 189 StandardLocation.CLASS_OUTPUT, Arrays.asList(destDir)); 190 } catch (IOException e) { 191 throw new RuntimeException( 192 "IOException encountered during compilation: " + e.getMessage(), e); 193 } 194 Boolean result = ct.call(); 195 if (result == Boolean.FALSE) { 196 throw new RuntimeException( 197 "Compilation failure in " + type.getName() + " unit"); 198 } 199 if (this.flags.contains(Flags.USECACHE)) { 200 File existing = cache.putIfAbsent(type.getName(), destDir); 201 if (existing != null) { 202 deleteDir(destDir); 203 return existing; 204 } 205 } else { 206 this.tempDirs.add(destDir); 207 } 208 return destDir; 209 } 210 211 private static void deleteDir(File dir) { 212 if(!dir.exists()) { 213 return; 214 } 215 try { 216 Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<Path>() { 217 @Override 218 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 219 throws IOException { 220 Files.deleteIfExists(file); 221 return FileVisitResult.CONTINUE; 222 } 223 224 @Override 225 public FileVisitResult postVisitDirectory(Path dir, IOException e) 226 throws IOException { 227 if (e == null) { 228 Files.deleteIfExists(dir); 229 return FileVisitResult.CONTINUE; 230 } else { 231 // directory iteration failed 232 throw e; 233 } 234 } 235 }); 236 } catch (IOException failed) { 237 throw new RuntimeException(failed); 238 } 239 } 240 241 public void cleanup() { 242 if (!this.flags.contains(Flags.USECACHE)) { 243 tempDirs.forEach(dir -> { deleteDir(dir); }); 244 tempDirs.clear(); 245 } 246 } 247 248 // Removes all of the elements in the cache and deletes the associated 249 // output directories. This may not actually empty the cache if there 250 // are concurrent users of it. 251 public static void purgeCache() { 252 for (Map.Entry<String,File> entry : cache.entrySet()) { 253 cache.remove(entry.getKey()); 254 deleteDir(entry.getValue()); 255 } 256 } 257 }