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.Arrays;
  32 
  33 import javax.tools.ForwardingJavaFileManager;
  34 import javax.tools.FileObject;
  35 import javax.tools.JavaCompiler;
  36 import javax.tools.JavaCompiler.CompilationTask;
  37 import javax.tools.JavaFileObject;
  38 import javax.tools.JavaFileObject.Kind;
  39 import javax.tools.SimpleJavaFileObject;
  40 import javax.tools.ToolProvider;
  41 
  42 /**
  43  * {@code InMemoryJavaCompiler} can be used for compiling a {@link
  44  * CharSequence} to a {@code byte[]}.
  45  *
  46  * The compiler will not use the file system at all, instead using a {@link
  47  * ByteArrayOutputStream} for storing the byte code. For the source code, any
  48  * kind of {@link CharSequence} can be used, e.g. {@link String}, {@link
  49  * StringBuffer} or {@link StringBuilder}.
  50  *
  51  * The {@code InMemoryCompiler} can easily be used together with a {@code
  52  * ByteClassLoader} to easily compile and load source code in a {@link String}:
  53  *
  54  * <pre>
  55  * {@code
  56  * import jdk.test.lib.InMemoryJavaCompiler;
  57  * import jdk.test.lib.ByteClassLoader;
  58  *
  59  * class Example {
  60  *     public static void main(String[] args) {
  61  *         String className = "Foo";
  62  *         String sourceCode = "public class " + className + " {" +
  63  *                             "    public void bar() {" +
  64  *                             "        System.out.println("Hello from bar!");" +
  65  *                             "    }" +
  66  *                             "}";
  67  *         byte[] byteCode = InMemoryJavaCompiler.compile(className, sourceCode);
  68  *         Class fooClass = ByteClassLoader.load(className, byteCode);
  69  *     }
  70  * }
  71  * }
  72  * </pre>
  73  */
  74 public class InMemoryJavaCompiler {
  75     private static class MemoryJavaFileObject extends SimpleJavaFileObject {
  76         private final String className;
  77         private final CharSequence sourceCode;
  78         private final ByteArrayOutputStream byteCode;
  79 
  80         public MemoryJavaFileObject(String className, CharSequence sourceCode) {
  81             super(URI.create("string:///" + className.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
  82             this.className = className;
  83             this.sourceCode = sourceCode;
  84             this.byteCode = new ByteArrayOutputStream();
  85         }
  86 
  87         @Override
  88         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
  89             return sourceCode;
  90         }
  91 
  92         @Override
  93         public OutputStream openOutputStream() throws IOException {
  94             return byteCode;
  95         }
  96 
  97         public byte[] getByteCode() {
  98             return byteCode.toByteArray();
  99         }
 100 
 101         public String getClassName() {
 102             return className;
 103         }
 104     }
 105 
 106     private static class FileManagerWrapper extends ForwardingJavaFileManager {
 107         private MemoryJavaFileObject file;
 108 
 109         public FileManagerWrapper(MemoryJavaFileObject file) {
 110             super(getCompiler().getStandardFileManager(null, null, null));
 111             this.file = file;
 112         }
 113 
 114         @Override
 115         public JavaFileObject getJavaFileForOutput(Location location, String className,
 116                                                    Kind kind, FileObject sibling)
 117             throws IOException {
 118             if (!file.getClassName().equals(className)) {
 119                 throw new IOException("Expected class with name " + file.getClassName() +
 120                                       ", but got " + className);
 121             }
 122             return file;
 123         }
 124     }
 125 
 126     /**
 127      * Compiles the class with the given name and source code.
 128      *
 129      * @param className The name of the class
 130      * @param sourceCode The source code for the class with name {@code className}
 131      * @param options additional command line options
 132      * @throws RuntimeException if the compilation did not succeed
 133      * @return The resulting byte code from the compilation
 134      */
 135     public static byte[] compile(String className, CharSequence sourceCode, String... options) {
 136         MemoryJavaFileObject file = new MemoryJavaFileObject(className, sourceCode);
 137         CompilationTask task = getCompilationTask(file, options);
 138 
 139         if(!task.call()) {
 140             throw new RuntimeException("Could not compile " + className + " with source code " + sourceCode);
 141         }
 142 
 143         return file.getByteCode();
 144     }
 145 
 146     private static JavaCompiler getCompiler() {
 147         return ToolProvider.getSystemJavaCompiler();
 148     }
 149 
 150     private static CompilationTask getCompilationTask(MemoryJavaFileObject file, String... options) {
 151         return getCompiler().getTask(null, new FileManagerWrapper(file), null, Arrays.asList(options), null, Arrays.asList(file));
 152     }
 153 }