1/*
2 * Copyright (c) 2012, 2015, 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 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;
34import java.nio.file.FileVisitResult;
35import java.nio.file.Files;
36import java.nio.file.Path;
37import java.nio.file.SimpleFileVisitor;
38import java.nio.file.attribute.BasicFileAttributes;
39
40import static separate.SourceModel.Type;
41import static separate.SourceModel.Class;
42import static separate.SourceModel.Extends;
43import static separate.SourceModel.SourceProcessor;
44
45public 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}
258