1/* 2 * Copyright (c) 2012, 2013, 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 24package org.openjdk.tests.separate; 25 26import java.util.*; 27import java.util.concurrent.atomic.AtomicInteger; 28import java.util.concurrent.ConcurrentHashMap; 29import java.io.*; 30import java.net.URI; 31import javax.tools.*; 32 33import com.sun.source.util.JavacTask; 34 35import static org.openjdk.tests.separate.SourceModel.Type; 36import static org.openjdk.tests.separate.SourceModel.Class; 37import static org.openjdk.tests.separate.SourceModel.Extends; 38import static org.openjdk.tests.separate.SourceModel.SourceProcessor; 39 40public class Compiler { 41 42 public enum Flags { 43 VERBOSE, // Prints out files as they are compiled 44 USECACHE // Keeps results around for reuse. Only use this is 45 // you're sure that each compilation name maps to the 46 // same source code 47 } 48 49 private static final AtomicInteger counter = new AtomicInteger(); 50 private static final String targetDir = "gen-separate"; 51 private static final File root = new File(targetDir); 52 private static ConcurrentHashMap<String,File> cache = 53 new ConcurrentHashMap<>(); 54 55 Set<Flags> flags; 56 57 private JavaCompiler systemJavaCompiler; 58 private StandardJavaFileManager fm; 59 private List<File> tempDirs; 60 private List<ClassFilePreprocessor> postprocessors; 61 62 private static class SourceFile extends SimpleJavaFileObject { 63 private final String content; 64 65 public SourceFile(String name, String content) { 66 super(URI.create("myfo:/" + name + ".java"), Kind.SOURCE); 67 this.content = content; 68 } 69 70 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 71 return toString(); 72 } 73 74 public String toString() { return this.content; } 75 } 76 77 public Compiler(Flags ... flags) { 78 setFlags(flags); 79 this.tempDirs = new ArrayList<>(); 80 this.postprocessors = new ArrayList<>(); 81 this.systemJavaCompiler = ToolProvider.getSystemJavaCompiler(); 82 this.fm = systemJavaCompiler.getStandardFileManager(null, null, null); 83 } 84 85 public void setFlags(Flags ... flags) { 86 this.flags = new HashSet<>(Arrays.asList(flags)); 87 } 88 89 public void addPostprocessor(ClassFilePreprocessor cfp) { 90 this.postprocessors.add(cfp); 91 } 92 93 /** 94 * Compile hierarchies starting with each of the 'types' and return 95 * a ClassLoader that can be used to load the compiled classes. 96 */ 97 public ClassLoader compile(Type ... types) { 98 ClassFilePreprocessor[] cfps = this.postprocessors.toArray( 99 new ClassFilePreprocessor[0]); 100 101 DirectedClassLoader dcl = new DirectedClassLoader(cfps); 102 103 for (Type t : types) { 104 for (Map.Entry<String,File> each : compileHierarchy(t).entrySet()) { 105 dcl.setLocationFor(each.getKey(), each.getValue()); 106 } 107 } 108 return dcl; 109 } 110 111 /** 112 * Compiles and loads a hierarchy, starting at 'type' 113 */ 114 public java.lang.Class<?> compileAndLoad(Type type) 115 throws ClassNotFoundException { 116 117 ClassLoader loader = compile(type); 118 return java.lang.Class.forName(type.getName(), false, loader); 119 } 120 121 /** 122 * Compiles a hierarchy, starting at 'type' and return a mapping of the 123 * name to the location where the classfile for that type resides. 124 */ 125 private Map<String,File> compileHierarchy(Type type) { 126 HashMap<String,File> outputDirs = new HashMap<>(); 127 128 File outDir = compileOne(type); 129 outputDirs.put(type.getName(), outDir); 130 131 Class superClass = type.getSuperclass(); 132 if (superClass != null) 133 outputDirs.putAll(compileHierarchy(superClass)); 134 for (Extends ext : type.getSupertypes()) 135 outputDirs.putAll(compileHierarchy(ext.getType())); 136 137 return outputDirs; 138 } 139 140 private File compileOne(Type type) { 141 if (this.flags.contains(Flags.USECACHE)) { 142 File dir = cache.get(type.getName()); 143 if (dir != null) { 144 return dir; 145 } 146 } 147 List<JavaFileObject> files = new ArrayList<>(); 148 SourceProcessor accum = 149 (name, src) -> { files.add(new SourceFile(name, src)); }; 150 151 Collection<Type> deps = type.typeDependencies(type.isFullCompilation()); 152 for (Type dep : deps) { 153 if (type.isFullCompilation()) 154 dep.generate(accum); 155 else 156 dep.generateAsDependency(accum, type.methodDependencies()); 157 } 158 159 type.generate(accum); 160 161 JavacTask ct = (JavacTask)this.systemJavaCompiler.getTask( 162 null, this.fm, null, null, null, files); 163 File destDir = null; 164 do { 165 int value = counter.incrementAndGet(); 166 destDir = new File(root, Integer.toString(value)); 167 } while (destDir.exists()); 168 169 if (this.flags.contains(Flags.VERBOSE)) { 170 System.out.println("Compilation unit for " + type.getName() + 171 " : compiled into " + destDir); 172 for (JavaFileObject jfo : files) { 173 System.out.println(jfo.toString()); 174 } 175 } 176 177 try { 178 destDir.mkdirs(); 179 this.fm.setLocation( 180 StandardLocation.CLASS_OUTPUT, Arrays.asList(destDir)); 181 } catch (IOException e) { 182 throw new RuntimeException( 183 "IOException encountered during compilation", e); 184 } 185 Boolean result = ct.call(); 186 if (result == Boolean.FALSE) { 187 throw new RuntimeException( 188 "Compilation failure in " + type.getName() + " unit"); 189 } 190 if (this.flags.contains(Flags.USECACHE)) { 191 File existing = cache.putIfAbsent(type.getName(), destDir); 192 if (existing != null) { 193 deleteDir(destDir); 194 return existing; 195 } 196 } else { 197 this.tempDirs.add(destDir); 198 } 199 return destDir; 200 } 201 202 private static void deleteDir(File dir) { 203 for (File f : dir.listFiles()) { 204 f.delete(); 205 }; 206 dir.delete(); 207 } 208 209 public void cleanup() { 210 if (!this.flags.contains(Flags.USECACHE)) { 211 for (File d : tempDirs) { 212 deleteDir(d); 213 }; 214 tempDirs = new ArrayList<>(); 215 } 216 } 217 218 // Removes all of the elements in the cache and deletes the associated 219 // output directories. This may not actually empty the cache if there 220 // are concurrent users of it. 221 public static void purgeCache() { 222 for (Map.Entry<String,File> entry : cache.entrySet()) { 223 cache.remove(entry.getKey()); 224 deleteDir(entry.getValue()); 225 } 226 } 227} 228