InMemoryJavaCompiler.java revision 2763:730f776ffa59
1/*
2 * Copyright (c) 2013, 2017, 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 jdk.test.lib.compiler;
25
26import java.io.ByteArrayOutputStream;
27import java.io.IOException;
28import java.io.OutputStream;
29
30import java.net.URI;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.List;
34
35import javax.tools.ForwardingJavaFileManager;
36import javax.tools.FileObject;
37import javax.tools.JavaCompiler;
38import javax.tools.JavaCompiler.CompilationTask;
39import javax.tools.JavaFileObject;
40import javax.tools.JavaFileObject.Kind;
41import javax.tools.SimpleJavaFileObject;
42import javax.tools.StandardLocation;
43import javax.tools.ToolProvider;
44
45/**
46 * {@code InMemoryJavaCompiler} can be used for compiling a {@link
47 * CharSequence} to a {@code byte[]}.
48 *
49 * The compiler will not use the file system at all, instead using a {@link
50 * ByteArrayOutputStream} for storing the byte code. For the source code, any
51 * kind of {@link CharSequence} can be used, e.g. {@link String}, {@link
52 * StringBuffer} or {@link StringBuilder}.
53 *
54 * The {@code InMemoryCompiler} can easily be used together with a {@code
55 * ByteClassLoader} to easily compile and load source code in a {@link String}:
56 *
57 * <pre>
58 * {@code
59 * import jdk.test.lib.compiler.InMemoryJavaCompiler;
60 * import jdk.test.lib.ByteClassLoader;
61 *
62 * class Example {
63 *     public static void main(String[] args) {
64 *         String className = "Foo";
65 *         String sourceCode = "public class " + className + " {" +
66 *                             "    public void bar() {" +
67 *                             "        System.out.println("Hello from bar!");" +
68 *                             "    }" +
69 *                             "}";
70 *         byte[] byteCode = InMemoryJavaCompiler.compile(className, sourceCode);
71 *         Class fooClass = ByteClassLoader.load(className, byteCode);
72 *     }
73 * }
74 * }
75 * </pre>
76 */
77public class InMemoryJavaCompiler {
78    private static class MemoryJavaFileObject extends SimpleJavaFileObject {
79        private final String className;
80        private final CharSequence sourceCode;
81        private final ByteArrayOutputStream byteCode;
82
83        public MemoryJavaFileObject(String className, CharSequence sourceCode) {
84            super(URI.create("string:///" + className.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
85            this.className = className;
86            this.sourceCode = sourceCode;
87            this.byteCode = new ByteArrayOutputStream();
88        }
89
90        @Override
91        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
92            return sourceCode;
93        }
94
95        @Override
96        public OutputStream openOutputStream() throws IOException {
97            return byteCode;
98        }
99
100        public byte[] getByteCode() {
101            return byteCode.toByteArray();
102        }
103
104        public String getClassName() {
105            return className;
106        }
107    }
108
109    private static class FileManagerWrapper extends ForwardingJavaFileManager {
110        private static final Location PATCH_LOCATION = new Location() {
111            @Override
112            public String getName() {
113                return "patch module location";
114            }
115
116            @Override
117            public boolean isOutputLocation() {
118                return false;
119            }
120        };
121        private final MemoryJavaFileObject file;
122        private final String moduleOverride;
123
124        public FileManagerWrapper(MemoryJavaFileObject file, String moduleOverride) {
125            super(getCompiler().getStandardFileManager(null, null, null));
126            this.file = file;
127            this.moduleOverride = moduleOverride;
128        }
129
130        @Override
131        public JavaFileObject getJavaFileForOutput(Location location, String className,
132                                                   Kind kind, FileObject sibling)
133            throws IOException {
134            if (!file.getClassName().equals(className)) {
135                throw new IOException("Expected class with name " + file.getClassName() +
136                                      ", but got " + className);
137            }
138            return file;
139        }
140
141        @Override
142        public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
143            if (fo == file && moduleOverride != null) {
144                return PATCH_LOCATION;
145            }
146            return super.getLocationForModule(location, fo);
147        }
148
149        @Override
150        public String inferModuleName(Location location) throws IOException {
151            if (location == PATCH_LOCATION) {
152                return moduleOverride;
153            }
154            return super.inferModuleName(location);
155        }
156
157        @Override
158        public boolean hasLocation(Location location) {
159            return super.hasLocation(location) || location == StandardLocation.PATCH_MODULE_PATH;
160        }
161
162    }
163
164    /**
165     * Compiles the class with the given name and source code.
166     *
167     * @param className The name of the class
168     * @param sourceCode The source code for the class with name {@code className}
169     * @param options additional command line options
170     * @throws RuntimeException if the compilation did not succeed
171     * @return The resulting byte code from the compilation
172     */
173    public static byte[] compile(String className, CharSequence sourceCode, String... options) {
174        MemoryJavaFileObject file = new MemoryJavaFileObject(className, sourceCode);
175        CompilationTask task = getCompilationTask(file, options);
176
177        if(!task.call()) {
178            throw new RuntimeException("Could not compile " + className + " with source code " + sourceCode);
179        }
180
181        return file.getByteCode();
182    }
183
184    private static JavaCompiler getCompiler() {
185        return ToolProvider.getSystemJavaCompiler();
186    }
187
188    private static CompilationTask getCompilationTask(MemoryJavaFileObject file, String... options) {
189        List<String> opts = new ArrayList<>();
190        String moduleOverride = null;
191        for (String opt : options) {
192            if (opt.startsWith("--patch-module=")) {
193                moduleOverride = opt.substring("--patch-module=".length());
194            } else {
195                opts.add(opt);
196            }
197        }
198        return getCompiler().getTask(null, new FileManagerWrapper(file, moduleOverride), null, opts, null, Arrays.asList(file));
199    }
200}
201