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 
  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             System.getProperty("java.io.tmpdir") + 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");
 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 }