/* * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.javac.jvm; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.Stack; import java.util.StringTokenizer; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.NoType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.TypeVisitor; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; import javax.tools.FileObject; import javax.tools.JavaFileManager; import javax.tools.StandardLocation; import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Scope; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; import static com.sun.tools.javac.main.Option.*; /** This class provides operations to write native header files for classes. * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ public class JNIWriter { protected static final Context.Key<JNIWriter> jniWriterKey = new Context.Key<JNIWriter>(); /** Access to files. */ private final JavaFileManager fileManager; JavacElements elements; JavacTypes types; /** The log to use for verbose output. */ private final Log log; /** Switch: verbose output. */ private boolean verbose; /** Switch: check all nested classes of top level class */ private boolean checkAll; private Mangle mangler; private Context context; private Symtab syms; private String lineSep; private final boolean isWindows = System.getProperty("os.name").startsWith("Windows"); /** Get the ClassWriter instance for this context. */ public static JNIWriter instance(Context context) { JNIWriter instance = context.get(jniWriterKey); if (instance == null) instance = new JNIWriter(context); return instance; } /** Construct a class writer, given an options table. */ private JNIWriter(Context context) { context.put(jniWriterKey, this); fileManager = context.get(JavaFileManager.class); log = Log.instance(context); Options options = Options.instance(context); verbose = options.isSet(VERBOSE); checkAll = options.isSet("javah:full"); this.context = context; // for lazyInit() syms = Symtab.instance(context); lineSep = System.getProperty("line.separator"); } private void lazyInit() { if (mangler == null) { elements = JavacElements.instance(context); types = JavacTypes.instance(context); mangler = new Mangle(elements, types); } } public boolean needsHeader(ClassSymbol c) { if (c.isLocal() || (c.flags() & Flags.SYNTHETIC) != 0) return false; if (checkAll) return needsHeader(c.outermostClass(), true); else return needsHeader(c, false); } private boolean needsHeader(ClassSymbol c, boolean checkNestedClasses) { if (c.isLocal() || (c.flags() & Flags.SYNTHETIC) != 0) return false; for (Scope.Entry i = c.members_field.elems; i != null; i = i.sibling) { if (i.sym.kind == Kinds.MTH && (i.sym.flags() & Flags.NATIVE) != 0) return true; for (Attribute.Compound a: i.sym.getDeclarationAttributes()) { if (a.type.tsym == syms.nativeHeaderType.tsym) return true; } } if (checkNestedClasses) { for (Scope.Entry i = c.members_field.elems; i != null; i = i.sibling) { if ((i.sym.kind == Kinds.TYP) && needsHeader(((ClassSymbol) i.sym), true)) return true; } } return false; } /** Emit a class file for a given class. * @param c The class from which a class file is generated. */ public FileObject write(ClassSymbol c) throws IOException { String className = c.flatName().toString(); FileObject outFile = fileManager.getFileForOutput(StandardLocation.NATIVE_HEADER_OUTPUT, "", className.replaceAll("[.$]", "_") + ".h", null); Writer out = outFile.openWriter(); try { write(out, c); if (verbose) log.printVerbose("wrote.file", outFile); out.close(); out = null; } finally { if (out != null) { // if we are propogating an exception, delete the file out.close(); outFile.delete(); outFile = null; } } return outFile; // may be null if write failed } public void write(Writer out, ClassSymbol sym) throws IOException { lazyInit(); try { String cname = mangler.mangle(sym.fullname, Mangle.Type.CLASS); println(out, fileTop()); println(out, includes()); println(out, guardBegin(cname)); println(out, cppGuardBegin()); writeStatics(out, sym); writeMethods(out, sym, cname); println(out, cppGuardEnd()); println(out, guardEnd(cname)); } catch (TypeSignature.SignatureException e) { throw new IOException(e); } } protected void writeStatics(Writer out, ClassSymbol sym) throws IOException { List<VariableElement> classfields = getAllFields(sym); for (VariableElement v: classfields) { if (!v.getModifiers().contains(Modifier.STATIC)) continue; String s = null; s = defineForStatic(sym, v); if (s != null) { println(out, s); } } } /** * Including super class fields. */ List<VariableElement> getAllFields(TypeElement subclazz) { List<VariableElement> fields = new ArrayList<VariableElement>(); TypeElement cd = null; Stack<TypeElement> s = new Stack<TypeElement>(); cd = subclazz; while (true) { s.push(cd); TypeElement c = (TypeElement) (types.asElement(cd.getSuperclass())); if (c == null) break; cd = c; } while (!s.empty()) { cd = s.pop(); fields.addAll(ElementFilter.fieldsIn(cd.getEnclosedElements())); } return fields; } protected String defineForStatic(TypeElement c, VariableElement f) { CharSequence cnamedoc = c.getQualifiedName(); CharSequence fnamedoc = f.getSimpleName(); String cname = mangler.mangle(cnamedoc, Mangle.Type.CLASS); String fname = mangler.mangle(fnamedoc, Mangle.Type.FIELDSTUB); Assert.check(f.getModifiers().contains(Modifier.STATIC)); if (f.getModifiers().contains(Modifier.FINAL)) { Object value = null; value = f.getConstantValue(); if (value != null) { /* so it is a ConstantExpression */ String constString = null; if ((value instanceof Integer) || (value instanceof Byte) || (value instanceof Short)) { /* covers byte, short, int */ constString = value.toString() + "L"; } else if (value instanceof Boolean) { constString = ((Boolean) value) ? "1L" : "0L"; } else if (value instanceof Character) { Character ch = (Character) value; constString = String.valueOf(((int) ch) & 0xffff) + "L"; } else if (value instanceof Long) { // Visual C++ supports the i64 suffix, not LL. if (isWindows) constString = value.toString() + "i64"; else constString = value.toString() + "LL"; } else if (value instanceof Float) { /* bug for bug */ float fv = ((Float)value).floatValue(); if (Float.isInfinite(fv)) constString = ((fv < 0) ? "-" : "") + "Inff"; else constString = value.toString() + "f"; } else if (value instanceof Double) { /* bug for bug */ double d = ((Double)value).doubleValue(); if (Double.isInfinite(d)) constString = ((d < 0) ? "-" : "") + "InfD"; else constString = value.toString(); } if (constString != null) { StringBuilder s = new StringBuilder("#undef "); s.append(cname); s.append("_"); s.append(fname); s.append(lineSep); s.append("#define "); s.append(cname); s.append("_"); s.append(fname); s.append(" "); s.append(constString); return s.toString(); } } } return null; } protected void writeMethods(Writer out, ClassSymbol sym, String cname) throws IOException, TypeSignature.SignatureException { List<ExecutableElement> classmethods = ElementFilter.methodsIn(sym.getEnclosedElements()); for (ExecutableElement md: classmethods) { if(md.getModifiers().contains(Modifier.NATIVE)){ TypeMirror mtr = types.erasure(md.getReturnType()); String sig = signature(md); TypeSignature newtypesig = new TypeSignature(elements); CharSequence methodName = md.getSimpleName(); boolean longName = false; for (ExecutableElement md2: classmethods) { if ((md2 != md) && (methodName.equals(md2.getSimpleName())) && (md2.getModifiers().contains(Modifier.NATIVE))) longName = true; } println(out, "/*"); println(out, " * Class: " + cname); println(out, " * Method: " + mangler.mangle(methodName, Mangle.Type.FIELDSTUB)); println(out, " * Signature: " + newtypesig.getTypeSignature(sig, mtr)); println(out, " */"); println(out, "JNIEXPORT " + jniType(mtr) + " JNICALL " + mangler.mangleMethod(md, sym, (longName) ? Mangle.Type.METHOD_JNI_LONG : Mangle.Type.METHOD_JNI_SHORT)); print(out, " (JNIEnv *, "); List<? extends VariableElement> paramargs = md.getParameters(); List<TypeMirror> args = new ArrayList<TypeMirror>(); for (VariableElement p: paramargs) { args.add(types.erasure(p.asType())); } if (md.getModifiers().contains(Modifier.STATIC)) print(out, "jclass"); else print(out, "jobject"); for (TypeMirror arg: args) { print(out, ", "); print(out, jniType(arg)); } println(out, ");" + lineSep); } } } // c.f. MethodDoc.signature String signature(ExecutableElement e) { StringBuilder sb = new StringBuilder("("); String sep = ""; for (VariableElement p: e.getParameters()) { sb.append(sep); sb.append(types.erasure(p.asType()).toString()); sep = ","; } sb.append(")"); return sb.toString(); } protected final String jniType(TypeMirror t) { TypeElement throwable = elements.getTypeElement("java.lang.Throwable"); TypeElement jClass = elements.getTypeElement("java.lang.Class"); TypeElement jString = elements.getTypeElement("java.lang.String"); Element tclassDoc = types.asElement(t); switch (t.getKind()) { case ARRAY: { TypeMirror ct = ((ArrayType) t).getComponentType(); switch (ct.getKind()) { case BOOLEAN: return "jbooleanArray"; case BYTE: return "jbyteArray"; case CHAR: return "jcharArray"; case SHORT: return "jshortArray"; case INT: return "jintArray"; case LONG: return "jlongArray"; case FLOAT: return "jfloatArray"; case DOUBLE: return "jdoubleArray"; case ARRAY: case DECLARED: return "jobjectArray"; default: throw new Error(ct.toString()); } } case VOID: return "void"; case BOOLEAN: return "jboolean"; case BYTE: return "jbyte"; case CHAR: return "jchar"; case SHORT: return "jshort"; case INT: return "jint"; case LONG: return "jlong"; case FLOAT: return "jfloat"; case DOUBLE: return "jdouble"; case DECLARED: { if (tclassDoc.equals(jString)) return "jstring"; else if (types.isAssignable(t, throwable.asType())) return "jthrowable"; else if (types.isAssignable(t, jClass.asType())) return "jclass"; else return "jobject"; } } Assert.check(false, "jni unknown type"); return null; /* dead code. */ } protected String fileTop() { return "/* DO NOT EDIT THIS FILE - it is machine generated */"; } protected String includes() { return "#include <jni.h>"; } /* * Deal with the C pre-processor. */ protected String cppGuardBegin() { return "#ifdef __cplusplus" + lineSep + "extern \"C\" {" + lineSep + "#endif"; } protected String cppGuardEnd() { return "#ifdef __cplusplus" + lineSep + "}" + lineSep + "#endif"; } protected String guardBegin(String cname) { return "/* Header for class " + cname + " */" + lineSep + lineSep + "#ifndef _Included_" + cname + lineSep + "#define _Included_" + cname; } protected String guardEnd(String cname) { return "#endif"; } protected void print(Writer out, String text) throws IOException { out.write(text); } protected void println(Writer out, String text) throws IOException { out.write(text); out.write(lineSep); } private static class Mangle { public static class Type { public static final int CLASS = 1; public static final int FIELDSTUB = 2; public static final int FIELD = 3; public static final int JNI = 4; public static final int SIGNATURE = 5; public static final int METHOD_JDK_1 = 6; public static final int METHOD_JNI_SHORT = 7; public static final int METHOD_JNI_LONG = 8; }; private Elements elems; private Types types; Mangle(Elements elems, Types types) { this.elems = elems; this.types = types; } public final String mangle(CharSequence name, int mtype) { StringBuilder result = new StringBuilder(100); int length = name.length(); for (int i = 0; i < length; i++) { char ch = name.charAt(i); if (isalnum(ch)) { result.append(ch); } else if ((ch == '.') && mtype == Mangle.Type.CLASS) { result.append('_'); } else if (( ch == '$') && mtype == Mangle.Type.CLASS) { result.append('_'); result.append('_'); } else if (ch == '_' && mtype == Mangle.Type.FIELDSTUB) { result.append('_'); } else if (ch == '_' && mtype == Mangle.Type.CLASS) { result.append('_'); } else if (mtype == Mangle.Type.JNI) { String esc = null; if (ch == '_') esc = "_1"; else if (ch == '.') esc = "_"; else if (ch == ';') esc = "_2"; else if (ch == '[') esc = "_3"; if (esc != null) { result.append(esc); } else { result.append(mangleChar(ch)); } } else if (mtype == Mangle.Type.SIGNATURE) { if (isprint(ch)) { result.append(ch); } else { result.append(mangleChar(ch)); } } else { result.append(mangleChar(ch)); } } return result.toString(); } public String mangleMethod(ExecutableElement method, TypeElement clazz, int mtype) throws TypeSignature.SignatureException { StringBuilder result = new StringBuilder(100); result.append("Java_"); if (mtype == Mangle.Type.METHOD_JDK_1) { result.append(mangle(clazz.getQualifiedName(), Mangle.Type.CLASS)); result.append('_'); result.append(mangle(method.getSimpleName(), Mangle.Type.FIELD)); result.append("_stub"); return result.toString(); } /* JNI */ result.append(mangle(getInnerQualifiedName(clazz), Mangle.Type.JNI)); result.append('_'); result.append(mangle(method.getSimpleName(), Mangle.Type.JNI)); if (mtype == Mangle.Type.METHOD_JNI_LONG) { result.append("__"); String typesig = signature(method); TypeSignature newTypeSig = new TypeSignature(elems); String sig = newTypeSig.getTypeSignature(typesig, method.getReturnType()); sig = sig.substring(1); sig = sig.substring(0, sig.lastIndexOf(')')); sig = sig.replace('/', '.'); result.append(mangle(sig, Mangle.Type.JNI)); } return result.toString(); } //where private String getInnerQualifiedName(TypeElement clazz) { return elems.getBinaryName(clazz).toString(); } public final String mangleChar(char ch) { String s = Integer.toHexString(ch); int nzeros = 5 - s.length(); char[] result = new char[6]; result[0] = '_'; for (int i = 1; i <= nzeros; i++) result[i] = '0'; for (int i = nzeros+1, j = 0; i < 6; i++, j++) result[i] = s.charAt(j); return new String(result); } // Warning: duplicated in Gen private String signature(ExecutableElement e) { StringBuilder sb = new StringBuilder(); String sep = "("; for (VariableElement p: e.getParameters()) { sb.append(sep); sb.append(types.erasure(p.asType()).toString()); sep = ","; } sb.append(")"); return sb.toString(); } /* Warning: Intentional ASCII operation. */ private static boolean isalnum(char ch) { return ch <= 0x7f && /* quick test */ ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')); } /* Warning: Intentional ASCII operation. */ private static boolean isprint(char ch) { return ch >= 32 && ch <= 126; } } private static class TypeSignature { static class SignatureException extends Exception { private static final long serialVersionUID = 1L; SignatureException(String reason) { super(reason); } } Elements elems; /* Signature Characters */ private static final String SIG_VOID = "V"; private static final String SIG_BOOLEAN = "Z"; private static final String SIG_BYTE = "B"; private static final String SIG_CHAR = "C"; private static final String SIG_SHORT = "S"; private static final String SIG_INT = "I"; private static final String SIG_LONG = "J"; private static final String SIG_FLOAT = "F"; private static final String SIG_DOUBLE = "D"; private static final String SIG_ARRAY = "["; private static final String SIG_CLASS = "L"; public TypeSignature(Elements elems){ this.elems = elems; } /* * Returns the type signature of a field according to JVM specs */ public String getTypeSignature(String javasignature) throws SignatureException { return getParamJVMSignature(javasignature); } /* * Returns the type signature of a method according to JVM specs */ public String getTypeSignature(String javasignature, TypeMirror returnType) throws SignatureException { String signature = null; //Java type signature. String typeSignature = null; //Internal type signature. List<String> params = new ArrayList<String>(); //List of parameters. String paramsig = null; //Java parameter signature. String paramJVMSig = null; //Internal parameter signature. String returnSig = null; //Java return type signature. String returnJVMType = null; //Internal return type signature. int dimensions = 0; //Array dimension. int startIndex = -1; int endIndex = -1; StringTokenizer st = null; int i = 0; // Gets the actual java signature without parentheses. if (javasignature != null) { startIndex = javasignature.indexOf("("); endIndex = javasignature.indexOf(")"); } if (((startIndex != -1) && (endIndex != -1)) &&(startIndex+1 < javasignature.length()) &&(endIndex < javasignature.length())) { signature = javasignature.substring(startIndex+1, endIndex); } // Separates parameters. if (signature != null) { if (signature.indexOf(",") != -1) { st = new StringTokenizer(signature, ","); if (st != null) { while (st.hasMoreTokens()) { params.add(st.nextToken()); } } } else { params.add(signature); } } /* JVM type signature. */ typeSignature = "("; // Gets indivisual internal parameter signature. while (params.isEmpty() != true) { paramsig = params.remove(i).trim(); paramJVMSig = getParamJVMSignature(paramsig); if (paramJVMSig != null) { typeSignature += paramJVMSig; } } typeSignature += ")"; // Get internal return type signature. returnJVMType = ""; if (returnType != null) { dimensions = dimensions(returnType); } //Gets array dimension of return type. while (dimensions-- > 0) { returnJVMType += "["; } if (returnType != null) { returnSig = qualifiedTypeName(returnType); returnJVMType += getComponentType(returnSig); } else { System.out.println("Invalid return type."); } typeSignature += returnJVMType; return typeSignature; } /* * Returns internal signature of a parameter. */ private String getParamJVMSignature(String paramsig) throws SignatureException { String paramJVMSig = ""; String componentType =""; if(paramsig != null){ if(paramsig.indexOf("[]") != -1) { // Gets array dimension. int endindex = paramsig.indexOf("[]"); componentType = paramsig.substring(0, endindex); String dimensionString = paramsig.substring(endindex); if(dimensionString != null){ while(dimensionString.indexOf("[]") != -1){ paramJVMSig += "["; int beginindex = dimensionString.indexOf("]") + 1; if(beginindex < dimensionString.length()){ dimensionString = dimensionString.substring(beginindex); }else dimensionString = ""; } } } else componentType = paramsig; paramJVMSig += getComponentType(componentType); } return paramJVMSig; } /* * Returns internal signature of a component. */ private String getComponentType(String componentType) throws SignatureException { String JVMSig = ""; if(componentType != null){ if(componentType.equals("void")) JVMSig += SIG_VOID ; else if(componentType.equals("boolean")) JVMSig += SIG_BOOLEAN ; else if(componentType.equals("byte")) JVMSig += SIG_BYTE ; else if(componentType.equals("char")) JVMSig += SIG_CHAR ; else if(componentType.equals("short")) JVMSig += SIG_SHORT ; else if(componentType.equals("int")) JVMSig += SIG_INT ; else if(componentType.equals("long")) JVMSig += SIG_LONG ; else if(componentType.equals("float")) JVMSig += SIG_FLOAT ; else if(componentType.equals("double")) JVMSig += SIG_DOUBLE ; else { if(!componentType.equals("")){ TypeElement classNameDoc = elems.getTypeElement(componentType); if(classNameDoc == null){ throw new SignatureException(componentType); }else { String classname = classNameDoc.getQualifiedName().toString(); String newclassname = classname.replace('.', '/'); JVMSig += "L"; JVMSig += newclassname; JVMSig += ";"; } } } } return JVMSig; } int dimensions(TypeMirror t) { if (t.getKind() != TypeKind.ARRAY) return 0; return 1 + dimensions(((ArrayType) t).getComponentType()); } String qualifiedTypeName(TypeMirror type) { TypeVisitor<Name, Void> v = new SimpleTypeVisitor8<Name, Void>() { @Override public Name visitArray(ArrayType t, Void p) { return t.getComponentType().accept(this, p); } @Override public Name visitDeclared(DeclaredType t, Void p) { return ((TypeElement) t.asElement()).getQualifiedName(); } @Override public Name visitPrimitive(PrimitiveType t, Void p) { return elems.getName(t.toString()); } @Override public Name visitNoType(NoType t, Void p) { if (t.getKind() == TypeKind.VOID) return elems.getName("void"); return defaultAction(t, p); } @Override public Name visitTypeVariable(TypeVariable t, Void p) { return t.getUpperBound().accept(this, p); } }; return v.visit(type).toString(); } } }