package water.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.tools.FileObject;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import water.H2O;
import water.exceptions.JCodeSB;
/** Internal utility for pretty-printing Models as Java code
*/
public class JCodeGen {
public static <T extends JCodeSB> T toStaticVar(T sb, String varname, int value, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
return (T) sb.ip("public static final int ").p(varname).p(" = ").p(value).p(';').nl();
}
public static JCodeSB toStaticVar(JCodeSB sb, String varname, String[] values, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
sb.ip("public static final String[] ").p(varname).p(" = ");
if (values == null) return sb.p("null;").nl();
sb.p("new String[]{").p("\""+values[0]+"\"");
for (int i = 1; i < values.length; ++i) sb.p(",").p("\""+values[i]+"\"");
return sb.p("};").nl();
}
public static JCodeSB toStaticVar(JCodeSB sb, String varname, float[] values, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
sb.ip("public static final float[] ").p(varname).p(" = ");
if (values == null) return sb.p("null;").nl();
sb.p("{").pj(values[0]);
for (int i = 1; i < values.length; ++i) sb.p(",").pj(values[i]);
return sb.p("};").nl();
}
public static JCodeSB toStaticVarZeros(JCodeSB sb, String varname, double[] values, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
sb.ip("public static final double[] ").p(varname).p(" = new double[" + values.length + "];");
return sb.nl();
}
public static JCodeSB toStaticVar(JCodeSB sb, String varname, double[] values, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
sb.ip("public static final double[] ").p(varname).p(" = ");
if (values == null) return sb.p("null;").nl();
sb.p("{").pj(values[0]);
for (int i = 1; i < values.length; ++i) sb.p(",").pj(values[i]);
return sb.p("};").nl();
}
public static JCodeSB toStaticVar(JCodeSB sb, String varname, int[] values, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
sb.ip("public static final int[] ").p(varname).p(" = ");
if (values == null) return sb.p("null;").nl();
sb.p("{").p(values[0]);
for (int i = 1; i < values.length; ++i) sb.p(",").p(values[i]);
return sb.p("};").nl();
}
public static JCodeSB toStaticVar(JCodeSB sb, String varname, double[][] values, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
sb.ip("public static final double[][] ").p(varname).p(" = ");
return sb.toJavaStringInit(values).p(';').nl();
}
public static JCodeSB toStaticVar(JCodeSB sb, String varname, double[][][] values, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
sb.ip("public static final double[][][] ").p(varname).p(" = ");
return sb.toJavaStringInit(values).p(';').nl();
}
public static JCodeSB toStaticVar(JCodeSB sb, String varname, boolean[] values, String comment) {
if (comment!=null) sb.ip("// ").p(comment).nl();
sb.ip("public static final boolean[] ").p(varname).p(" = ");
if (values == null) return sb.p("null;").nl();
sb.p("{").p(values[0]);
for (int i = 1; i < values.length; ++i) sb.p(",").p(values[i]);
return sb.p("};").nl();
}
/**
* Generates a new class with one static member called <em>VALUES</em> which
* is filled by values of given array.
* <p>The generator can generate more classes to avoid limit of class constant
* pool holding all generated literals</p>.
*
* @param sb output
* @param className name of generated class
* @param values array holding values which should be hold in generated field VALUES.
* @param comment comment to prefix the class with
* @return output buffer
*/
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, String[] values, String comment) {
if (comment != null) {
sb.p("// ").p(comment).nl();
}
sb.ip(modifiers!=null ? modifiers+" ": "").p("class ").p(className).p(" implements java.io.Serializable {").nl().ii(1);
sb.ip("public static final String[] VALUES = ");
if (values==null)
sb.p("null;").nl();
else {
sb.p("new String[").p(values.length).p("];").nl();
// Static part
int s = 0;
int remain = values.length;
int its = 0;
SB sb4fillers = new SB().ci(sb);
sb.ip("static {").ii(1).nl();
while (remain>0) {
String subClzName = className + "_" + its++;
int len = Math.min(MAX_STRINGS_IN_CONST_POOL, remain);
toClassWithArrayFill(sb4fillers, subClzName, values, s, len);
sb.ip(subClzName).p(".fill(VALUES);").nl();
s += len;
remain -= len;
}
sb.di(1).ip("}").nl();
sb.p(sb4fillers);
}
return sb.di(1).p("}").nl();
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, String[] values) {
return toClassWithArray(sb, modifiers, className, values, null);
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, double[] values, String comment) {
if (comment != null) {
sb.p("// ").p(comment).nl();
}
sb.ip(modifiers!=null ? modifiers+" ": "").p("class ").p(className).p(" implements java.io.Serializable {").nl().ii(1);
sb.ip("public static final double[] VALUES = ");
if (values==null)
sb.p("null;").nl();
else {
sb.p("new double[").p(values.length).p("];").nl();
// Static part
int s = 0;
int remain = values.length;
int its = 0;
SB sb4fillers = new SB().ci(sb);
sb.ip("static {").ii(1).nl();
while (remain>0) {
String subClzName = className + "_" + its++;
int len = Math.min(MAX_STRINGS_IN_CONST_POOL, remain);
toClassWithArrayFill(sb4fillers, subClzName, values, s, len);
sb.ip(subClzName).p(".fill(VALUES);").nl();
s += len;
remain -= len;
}
sb.di(1).ip("}").nl();
sb.p(sb4fillers);
}
return sb.di(1).p("}").nl();
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, double[] values) {
return toClassWithArray(sb, modifiers, className, values, null);
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, float[] values, String comment) {
if (comment != null) {
sb.p("// ").p(comment).nl();
}
sb.ip(modifiers!=null ? modifiers+" ": "").p("class ").p(className).p(" implements java.io.Serializable {").nl().ii(1);
sb.ip("public static final float[] VALUES = ");
if (values==null)
sb.p("null;").nl();
else {
sb.p("new float[").p(values.length).p("];").nl();
// Static part
int s = 0;
int remain = values.length;
int its = 0;
SB sb4fillers = new SB().ci(sb);
sb.ip("static {").ii(1).nl();
while (remain>0) {
String subClzName = className + "_" + its++;
int len = Math.min(MAX_STRINGS_IN_CONST_POOL, remain);
toClassWithArrayFill(sb4fillers, subClzName, values, s, len);
sb.ip(subClzName).p(".fill(VALUES);").nl();
s += len;
remain -= len;
}
sb.di(1).ip("}").nl();
sb.p(sb4fillers);
}
return sb.di(1).p("}").nl();
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, float[] values) {
return toClassWithArray(sb, modifiers, className, values, null);
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, int[] values, String comment) {
if (comment != null) {
sb.p("// ").p(comment).nl();
}
sb.ip(modifiers!=null ? modifiers+" ": "").p("class ").p(className).p(" implements java.io.Serializable {").nl().ii(1);
sb.ip("public static final int[] VALUES = ");
if (values==null)
sb.p("null;").nl();
else {
sb.p("new int[").p(values.length).p("];").nl();
// Static part
int s = 0;
int remain = values.length;
int its = 0;
SB sb4fillers = new SB().ci(sb);
sb.ip("static {").ii(1).nl();
while (remain>0) {
String subClzName = className + "_" + its++;
int len = Math.min(MAX_STRINGS_IN_CONST_POOL, remain);
toClassWithArrayFill(sb4fillers, subClzName, values, s, len);
sb.ip(subClzName).p(".fill(VALUES);").nl();
s += len;
remain -= len;
}
sb.di(1).ip("}").nl();
sb.p(sb4fillers);
}
return sb.di(1).p("}").nl();
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, int[] values) {
return toClassWithArray(sb, modifiers, className, values, null);
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, double[][] values, String comment) {
if (comment != null) {
sb.p("// ").p(comment).nl();
}
sb.ip(modifiers!=null ? modifiers+" ": "").p("class ").p(className).p(" implements java.io.Serializable {").nl().ii(1);
sb.ip("public static final double[][] VALUES = ");
if (values == null)
sb.p("null;").nl();
else {
sb.p("new double[").p(values.length).p("][];").nl();
// Static part
int s = 0;
int remain = values.length;
int its = 0;
SB sb4fillers = new SB().ci(sb);
sb.ip("static {").ii(1).nl();
while (remain>0) {
String subClzName = className + "_" + its++;
int len = Math.min(MAX_STRINGS_IN_CONST_POOL, remain);
toClassWithArrayFill(sb4fillers, subClzName, values, s, len);
sb.ip(subClzName).p(".fill(VALUES);").nl();
s += len;
remain -= len;
}
sb.di(1).ip("}").nl();
sb.p(sb4fillers);
}
return sb.di(1).p("}").nl();
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, double[][] values) {
return toClassWithArray(sb, modifiers, className, values, null);
}
public static JCodeSB toClassWithArray(JCodeSB sb, String modifiers, String className, double[][][] values, String comment) {
if (comment != null) {
sb.p("// ").p(comment).nl();
}
sb.ip(modifiers!=null ? modifiers+" ": "").p("class ").p(className).p(" implements java.io.Serializable {").nl().ii(1);
sb.ip("public static final double[][][] VALUES = ");
if (values == null)
sb.p("null;").nl();
else {
sb.p("new double[").p(values.length).p("][][];").nl();
// Static part
int s = 0;
int remain = values.length;
int its = 0;
SB sb4fillers = new SB().ci(sb);
sb.ip("static {").ii(1).nl();
while (remain>0) {
String subClzName = className + "_" + its++;
int len = Math.min(MAX_STRINGS_IN_CONST_POOL, remain);
toClassWithArrayFill(sb4fillers, subClzName, values, s, len);
sb.ip(subClzName).p(".fill(VALUES);").nl();
s += len;
remain -= len;
}
sb.di(1).ip("}").nl();
sb.p(sb4fillers);
}
return sb.di(1).p("}").nl();
}
/** Maximum number of string generated per class (static initializer) */
public static int MAX_STRINGS_IN_CONST_POOL = 3000;
public static JCodeSB toClassWithArrayFill(JCodeSB sb, String clzName, String[] values, int start, int len) {
sb.ip("static final class ").p(clzName).p(" implements java.io.Serializable {").ii(1).nl();
sb.ip("static final void fill(String[] sa) {").ii(1).nl();
for (int i=0; i<len; i++) {
sb.ip("sa[").p(start+i).p("] = ").ps(values[start+i]).p(";").nl();
}
sb.di(1).ip("}").nl();
sb.di(1).ip("}").nl();
return sb;
}
public static JCodeSB toClassWithArrayFill(JCodeSB sb, String clzName, float[] values, int start, int len) {
sb.ip("static final class ").p(clzName).p(" implements java.io.Serializable {").ii(1).nl();
sb.ip("static final void fill(float[] sa) {").ii(1).nl();
for (int i=0; i<len; i++) {
sb.ip("sa[").p(start+i).p("] = ").pj(values[start+i]).p(";").nl();
}
sb.di(1).ip("}").nl();
sb.di(1).ip("}").nl();
return sb;
}
public static JCodeSB toClassWithArrayFill(JCodeSB sb, String clzName, double[] values, int start, int len) {
sb.ip("static final class ").p(clzName).p(" implements java.io.Serializable {").ii(1).nl();
sb.ip("static final void fill(double[] sa) {").ii(1).nl();
for (int i=0; i<len; i++) {
sb.ip("sa[").p(start+i).p("] = ").pj(values[start+i]).p(";").nl();
}
sb.di(1).ip("}").nl();
sb.di(1).ip("}").nl();
return sb;
}
public static JCodeSB toClassWithArrayFill(JCodeSB sb, String clzName, int[] values, int start, int len) {
sb.ip("static final class ").p(clzName).p(" implements java.io.Serializable {").ii(1).nl();
sb.ip("static final void fill(int[] sa) {").ii(1).nl();
for (int i=0; i<len; i++) {
sb.ip("sa[").p(start+i).p("] = ").p(values[start + i]).p(";").nl();
}
sb.di(1).ip("}").nl();
sb.di(1).ip("}").nl();
return sb;
}
public static JCodeSB toClassWithArrayFill(JCodeSB sb, String clzName, double[][] values, int start, int len) {
for (int i = 0; i < len; i++) {
int idx = start + i;
toClassWithArray(sb, "static", clzName + "_" + idx, values[i + start]);
}
sb.ip("static final class ").p(clzName).p(" implements java.io.Serializable {").ii(1).nl();
sb.ip("static final void fill(double[][] sa) {").ii(1).nl();
for (int i=0; i<len; i++) {
int idx = start + i;
sb.ip("sa[").p(start+i).p("] = ").p(clzName + "_" + idx).p(".VALUES;").nl();
}
sb.di(1).ip("}").nl();
sb.di(1).ip("}").nl();
return sb;
}
public static JCodeSB toClassWithArrayFill(JCodeSB sb, String clzName, double[][][] values, int start, int len) {
for (int i = 0; i < len; i++) {
int idx = start + i;
toClassWithArray(sb, "static", clzName + "_" + idx, values[i + start]);
}
sb.ip("static final class ").p(clzName).p(" implements java.io.Serializable {").ii(1).nl();
sb.ip("static final void fill(double[][][] sa) {").ii(1).nl();
for (int i=0; i<len; i++) {
int idx = start + i;
sb.ip("sa[").p(start+i).p("] = ").p(clzName + "_" + idx).p(".VALUES;").nl();
}
sb.di(1).ip("}").nl();
sb.di(1).ip("}").nl();
return sb;
}
/** Transform given string to legal java Identifier (see Java grammar http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8) */
public static String toJavaId(String s) {
// Note that the leading 4 backslashes turn into 2 backslashes in the
// string - which turn into a single backslash in the REGEXP.
// "+-*/ !@#$%^&()={}[]|\\;:'\"<>,.?/"
return s.replaceAll("[+\\-* !@#$%^&()={}\\[\\]|;:'\"<>,.?/]", "_");
}
// Compiler loaded???
public static boolean canCompile() { return COMPILER!=null; }
public static Class compile(String class_name, String java_text) throws Exception {
if( COMPILER==null ) throw new UnsupportedOperationException("Unable to launch an internal instance of javac");
// Wrap input string up as a file-like java source thing
JavaFileObject file = new JavaSourceFromString(class_name, java_text);
// Capture all output class "files" as simple ByteArrayOutputStreams
JavacFileManager jfm = new JavacFileManager(COMPILER.getStandardFileManager(null, null, null));
// Invoke javac
if( !COMPILER.getTask(null, jfm, null, /*javac options*/null, null, Arrays.asList(file)).call() )
throw H2O.fail("Internal POJO compilation failed.");
// Load POJO classes via a separated classloader to separate POJO namespace
ClassLoader cl = new TestPojoCL(Thread.currentThread().getContextClassLoader());
for( Map.Entry<String, ByteArrayOutputStream> entry : jfm._buffers.entrySet()) {
byte[] bits = entry.getValue().toByteArray();
// Call classLoader.defineClass("className",byte[])
DEFINE_CLASS_METHOD.invoke(cl, entry.getKey(), bits, 0, bits.length);
}
return Class.forName(class_name, true, cl); // Return the original top-level class
}
/**
* A private pojo classloader to separate each pojo namespace and
* avoid collisions in loading
*/
private static class TestPojoCL extends ClassLoader {
public TestPojoCL(ClassLoader parent) {
super(parent);
}
}
// Parts of this code are shamelessly robbed from:
// OpenHFT/Java-Runtime-Compiler/blob/master/compiler/src/main/java/net/openhft/compiler
// Then a lot of extra stuff is tossed out.
private static final Method DEFINE_CLASS_METHOD;
private static final JavaCompiler COMPILER = ToolProvider.getSystemJavaCompiler();
// These lines rely on tools.jar in the test-set of jars, and may allow some
// Windows java installs to run the POJO tests that otherwise fail because an
// internal instance of javac cannot be launched. Untested; this code works
// on my Windows machine & on the Ubuntu Jenkins machines, but not the
// Jenkins Windows VM.
//import com.sun.tools.javac.api.JavacTool;
//private static final JavaCompiler COMPILER = COMPILER1==null ? JavacTool.create() : COMPILER1;
static {
try {
DEFINE_CLASS_METHOD = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
DEFINE_CLASS_METHOD.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
// Simple declaration of a string as a file-like thing
static class JavaSourceFromString extends javax.tools.SimpleJavaFileObject {
final String _code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE);
_code = code;
}
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return _code; }
}
// Manage all "files" being manipulated by javac - the input files are really
// Strings, the output files are simple byte[]'s holding the classes. Things
// other than Java source strings are routed through the standard fileManager
// so javac can look up related class files.
static class JavacFileManager implements JavaFileManager {
private final StandardJavaFileManager _fileManager;
final HashMap<String, ByteArrayOutputStream> _buffers = new HashMap<>();
JavacFileManager(StandardJavaFileManager fileManager) { _fileManager = fileManager; }
public ClassLoader getClassLoader(Location location) { return _fileManager.getClassLoader(location); }
public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException {
return _fileManager.list(location, packageName, kinds, recurse);
}
public String inferBinaryName(Location location, JavaFileObject file) { return _fileManager.inferBinaryName(location, file); }
public boolean isSameFile(FileObject a, FileObject b) { return _fileManager.isSameFile(a, b); }
public boolean handleOption(String current, Iterator<String> remaining) { return _fileManager.handleOption(current, remaining); }
public boolean hasLocation(Location location) { return _fileManager.hasLocation(location); }
public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
if( location == StandardLocation.CLASS_OUTPUT && _buffers.containsKey(className) && kind == Kind.CLASS ) {
final byte[] bytes = _buffers.get(className).toByteArray();
return new SimpleJavaFileObject(URI.create(className), kind) {
public InputStream openInputStream() {
return new ByteArrayInputStream(bytes);
}
};
}
return _fileManager.getJavaFileForInput(location, className, kind);
}
public JavaFileObject getJavaFileForOutput(Location location, final String className, Kind kind, FileObject sibling) throws IOException {
return new SimpleJavaFileObject(URI.create(className), kind) {
public OutputStream openOutputStream() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
_buffers.put(className, baos);
return baos;
}
};
}
public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
return _fileManager.getFileForInput(location, packageName, relativeName);
}
public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
return _fileManager.getFileForOutput(location, packageName, relativeName, sibling);
}
public void flush() throws IOException { _fileManager.flush(); }
public void close() throws IOException { _fileManager.close(); }
public int isSupportedOption(String option) { return _fileManager.isSupportedOption(option); }
}
}