1 /* 2 * Copyright (c) 2013, 2016, 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 jdk.test.lib; 25 26 import java.io.ByteArrayOutputStream; 27 import java.io.IOException; 28 import java.io.OutputStream; 29 30 import java.net.URI; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.List; 34 35 import javax.tools.ForwardingJavaFileManager; 36 import javax.tools.FileObject; 37 import javax.tools.JavaCompiler; 38 import javax.tools.JavaCompiler.CompilationTask; 39 import javax.tools.JavaFileObject; 40 import javax.tools.JavaFileObject.Kind; 41 import javax.tools.SimpleJavaFileObject; 42 import javax.tools.StandardLocation; 43 import 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.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 */ 77 public 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, String pkgName) throws IOException { 143 if (fo == file && moduleOverride != null) { 144 return PATCH_LOCATION; 145 } 146 return super.getLocationForModule(location, fo, pkgName); 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("-Xmodule:")) { 193 moduleOverride = opt.substring("-Xmodule:".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 }