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