1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.javac;
18 
19 import com.google.common.collect.Lists;
20 import com.google.common.io.Files;
21 
22 import java.util.Collections;
23 import java.util.stream.Collectors;
24 import org.apache.bcel.classfile.ClassParser;
25 import org.apache.bcel.classfile.JavaClass;
26 
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.net.URI;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.Locale;
35 
36 import javax.annotation.processing.Processor;
37 import javax.tools.DiagnosticCollector;
38 import javax.tools.JavaCompiler;
39 import javax.tools.JavaFileObject;
40 import javax.tools.SimpleJavaFileObject;
41 import javax.tools.StandardJavaFileManager;
42 import javax.tools.StandardLocation;
43 import javax.tools.ToolProvider;
44 
45 /**
46  * Helper class for compiling snippets of Java source and providing access to the resulting class
47  * files.
48  */
49 public class Javac {
50 
51     private final JavaCompiler mJavac;
52     private final StandardJavaFileManager mFileMan;
53     private final List<JavaFileObject> mCompilationUnits;
54     private final File mClassOutDir;
55 
Javac()56     public Javac() throws IOException {
57         mJavac = ToolProvider.getSystemJavaCompiler();
58         mFileMan = mJavac.getStandardFileManager(null, Locale.US, null);
59         mClassOutDir = Files.createTempDir();
60         mFileMan.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(mClassOutDir));
61         mFileMan.setLocation(StandardLocation.CLASS_PATH, Arrays.asList(mClassOutDir));
62         mCompilationUnits = new ArrayList<>();
63     }
64 
classToFileName(String classname)65     private String classToFileName(String classname) {
66         return classname.replace('.', '/');
67     }
68 
addSource(String classname, String contents)69     public Javac addSource(String classname, String contents) {
70         JavaFileObject java = new SimpleJavaFileObject(URI.create(
71                 String.format("string:///%s.java", classToFileName(classname))),
72                 JavaFileObject.Kind.SOURCE
73                 ){
74             @Override
75             public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
76                 return contents;
77             }
78         };
79         mCompilationUnits.add(java);
80         return this;
81     }
82 
compile()83     public void compile() {
84         compileWithAnnotationProcessor(null);
85     }
86 
compileWithAnnotationProcessor(Processor processor)87     public void compileWithAnnotationProcessor(Processor processor) {
88         DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
89         JavaCompiler.CompilationTask task = mJavac.getTask(
90                 null,
91                 mFileMan,
92                 diagnosticCollector,
93                 null,
94                 null,
95                 mCompilationUnits);
96         if (processor != null) {
97             task.setProcessors(Lists.newArrayList(processor));
98         } else {
99             task.setProcessors(Collections.EMPTY_LIST);
100         }
101         boolean result = task.call();
102         if (!result) {
103             throw new IllegalStateException(
104                     "Compilation failed:" +
105                             diagnosticCollector.getDiagnostics()
106                                     .stream()
107                                     .map(Object::toString)
108                                     .collect(Collectors.joining("\n")));
109         }
110     }
111 
getOutputFile(String filename)112     public InputStream getOutputFile(String filename) throws IOException {
113         Iterable<? extends JavaFileObject> objs = mFileMan.getJavaFileObjects(
114                 new File(mClassOutDir, filename));
115         if (!objs.iterator().hasNext()) {
116             return null;
117         }
118         return objs.iterator().next().openInputStream();
119     }
120 
getClassFile(String classname)121     public InputStream getClassFile(String classname) throws IOException {
122         return getOutputFile(String.format("%s.class", classToFileName(classname)));
123     }
124 
getCompiledClass(String classname)125     public JavaClass getCompiledClass(String classname) throws IOException {
126         return new ClassParser(getClassFile(classname),
127                 String.format("%s.class", classToFileName(classname))).parse();
128     }
129 }
130