| /* |
| * Copyright (c) 1999, 2016, 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.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import javax.tools.FileObject; |
| import javax.tools.JavaFileManager; |
| import javax.tools.JavaFileManager.Location; |
| import javax.tools.StandardLocation; |
| |
| import com.sun.tools.javac.code.Attribute; |
| import com.sun.tools.javac.code.Flags; |
| import com.sun.tools.javac.code.Symbol; |
| import com.sun.tools.javac.code.Symbol.ClassSymbol; |
| import com.sun.tools.javac.code.Symbol.ModuleSymbol; |
| import com.sun.tools.javac.code.Symbol.VarSymbol; |
| import com.sun.tools.javac.code.Symtab; |
| import com.sun.tools.javac.code.Type; |
| import com.sun.tools.javac.code.Types; |
| import com.sun.tools.javac.model.JavacElements; |
| 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 com.sun.tools.javac.util.Pair; |
| |
| import static com.sun.tools.javac.main.Option.*; |
| import static com.sun.tools.javac.code.Kinds.Kind.*; |
| import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; |
| |
| /** 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<>(); |
| |
| /** Access to files. */ |
| private final JavaFileManager fileManager; |
| |
| Types types; |
| Symtab syms; |
| |
| /** 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; |
| |
| /** |
| * If true, class files will be written in module-specific subdirectories |
| * of the NATIVE_HEADER_OUTPUT location. |
| */ |
| public boolean multiModuleMode; |
| |
| private Context context; |
| |
| private static 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() |
| } |
| |
| private void lazyInit() { |
| if (types == null) |
| types = Types.instance(context); |
| if (syms == null) |
| syms = Symtab.instance(context); |
| |
| } |
| |
| static boolean isSynthetic(Symbol s) { |
| return hasFlag(s, Flags.SYNTHETIC); |
| } |
| static boolean isStatic(Symbol s) { |
| return hasFlag(s, Flags.STATIC); |
| } |
| static boolean isFinal(Symbol s) { |
| return hasFlag(s, Flags.FINAL); |
| } |
| static boolean isNative(Symbol s) { |
| return hasFlag(s, Flags.NATIVE); |
| } |
| static private boolean hasFlag(Symbol m, int flag) { |
| return (m.flags() & flag) != 0; |
| } |
| |
| public boolean needsHeader(ClassSymbol c) { |
| lazyInit(); |
| if (c.isLocal() || isSynthetic(c)) |
| return false; |
| return (checkAll) |
| ? needsHeader(c.outermostClass(), true) |
| : needsHeader(c, false); |
| } |
| |
| private boolean needsHeader(ClassSymbol c, boolean checkNestedClasses) { |
| if (c.isLocal() || isSynthetic(c)) |
| return false; |
| |
| for (Symbol sym : c.members_field.getSymbols(NON_RECURSIVE)) { |
| if (sym.kind == MTH && isNative(sym)) |
| return true; |
| for (Attribute.Compound a: sym.getDeclarationAttributes()) { |
| if (a.type.tsym == syms.nativeHeaderType.tsym) |
| return true; |
| } |
| } |
| if (checkNestedClasses) { |
| for (Symbol sym : c.members_field.getSymbols(NON_RECURSIVE)) { |
| if ((sym.kind == TYP) && needsHeader(((ClassSymbol) 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(); |
| Location outLocn; |
| if (multiModuleMode) { |
| ModuleSymbol msym = c.owner.kind == MDL ? (ModuleSymbol) c.owner : c.packge().modle; |
| outLocn = fileManager.getLocationForModule(StandardLocation.NATIVE_HEADER_OUTPUT, msym.name.toString()); |
| } else { |
| outLocn = StandardLocation.NATIVE_HEADER_OUTPUT; |
| } |
| FileObject outFile |
| = fileManager.getFileForOutput(outLocn, |
| "", className.replaceAll("[.$]", "_") + ".h", null); |
| PrintWriter out = new PrintWriter(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(PrintWriter out, ClassSymbol sym) throws IOException { |
| lazyInit(); |
| try { |
| String cname = encode(sym.fullname, EncoderType.CLASS); |
| fileTop(out); |
| includes(out); |
| guardBegin(out, cname); |
| cppGuardBegin(out); |
| |
| writeStatics(out, sym); |
| writeMethods(out, sym, cname); |
| |
| cppGuardEnd(out); |
| guardEnd(out); |
| } catch (TypeSignature.SignatureException e) { |
| throw new IOException(e); |
| } |
| } |
| protected void writeStatics(PrintWriter out, ClassSymbol sym) throws IOException { |
| List<ClassSymbol> clist = new ArrayList<>(); |
| for (ClassSymbol cd = sym; cd != null; |
| cd = (ClassSymbol) cd.getSuperclass().tsym) { |
| clist.add(cd); |
| } |
| /* |
| * list needs to be super-class, base-class1, base-class2 and so on, |
| * so we reverse class hierarchy |
| */ |
| Collections.reverse(clist); |
| for (ClassSymbol cd : clist) { |
| for (Symbol i : cd.getEnclosedElements()) { |
| // consider only final, static and fields with ConstantExpressions |
| if (isFinal(i) && i.isStatic() && i.kind == VAR) { |
| VarSymbol v = (VarSymbol) i; |
| if (v.getConstantValue() != null) { |
| Pair<ClassSymbol, VarSymbol> p = new Pair<>(sym, v); |
| printStaticDefines(out, p); |
| } |
| } |
| } |
| } |
| } |
| static void printStaticDefines(PrintWriter out, Pair<ClassSymbol, VarSymbol> p) { |
| ClassSymbol cls = p.fst; |
| VarSymbol f = p.snd; |
| Object value = f.getConstantValue(); |
| String valueStr = null; |
| switch (f.asType().getKind()) { |
| case BOOLEAN: |
| valueStr = (((Boolean) value) ? "1L" : "0L"); |
| break; |
| case BYTE: case SHORT: case INT: |
| valueStr = value.toString() + "L"; |
| break; |
| case LONG: |
| // Visual C++ supports the i64 suffix, not LL. |
| valueStr = value.toString() + ((isWindows) ? "i64" : "LL"); |
| break; |
| case CHAR: |
| Character ch = (Character) value; |
| valueStr = String.valueOf(((int) ch) & 0xffff) + "L"; |
| break; |
| case FLOAT: |
| // bug compatible |
| float fv = ((Float) value).floatValue(); |
| valueStr = (Float.isInfinite(fv)) |
| ? ((fv < 0) ? "-" : "") + "Inff" |
| : value.toString() + "f"; |
| break; |
| case DOUBLE: |
| // bug compatible |
| double d = ((Double) value).doubleValue(); |
| valueStr = (Double.isInfinite(d)) |
| ? ((d < 0) ? "-" : "") + "InfD" |
| : value.toString(); |
| break; |
| default: |
| valueStr = null; |
| } |
| if (valueStr != null) { |
| out.print("#undef "); |
| String cname = encode(cls.getQualifiedName(), EncoderType.CLASS); |
| String fname = encode(f.getSimpleName(), EncoderType.FIELDSTUB); |
| out.println(cname + "_" + fname); |
| out.print("#define " + cname + "_"); |
| out.println(fname + " " + valueStr); |
| } |
| } |
| protected void writeMethods(PrintWriter out, ClassSymbol sym, String cname) |
| throws IOException, TypeSignature.SignatureException { |
| List<Symbol> classmethods = sym.getEnclosedElements(); |
| for (Symbol md : classmethods) { |
| if (isNative(md)) { |
| TypeSignature newtypesig = new TypeSignature(types); |
| CharSequence methodName = md.getSimpleName(); |
| boolean isOverloaded = false; |
| for (Symbol md2 : classmethods) { |
| if ((md2 != md) |
| && (methodName.equals(md2.getSimpleName())) |
| && isNative(md2)) { |
| isOverloaded = true; |
| } |
| } |
| out.println("/*"); |
| out.println(" * Class: " + cname); |
| out.println(" * Method: " + encode(methodName, EncoderType.FIELDSTUB)); |
| out.println(" * Signature: " + newtypesig.getSignature(md.type)); |
| out.println(" */"); |
| out.println("JNIEXPORT " + jniType(types.erasure(md.type.getReturnType())) |
| + " JNICALL " + encodeMethod(md, sym, isOverloaded)); |
| out.print(" (JNIEnv *, "); |
| out.print((md.isStatic()) |
| ? "jclass" |
| : "jobject"); |
| for (Type arg : types.erasure(md.type.getParameterTypes())) { |
| out.print(", "); |
| out.print(jniType(arg)); |
| } |
| out.println(");"); |
| out.println(); |
| } |
| } |
| } |
| @SuppressWarnings("fallthrough") |
| protected final String jniType(Type t) { |
| switch (t.getKind()) { |
| case ARRAY: { |
| Type ct = ((Type.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 (t.tsym.type == syms.stringType) { |
| return "jstring"; |
| } else if (types.isAssignable(t, syms.throwableType)) { |
| return "jthrowable"; |
| } else if (types.isAssignable(t, syms.classType)) { |
| return "jclass"; |
| } else { |
| return "jobject"; |
| } |
| } |
| } |
| |
| Assert.check(false, "jni unknown type"); |
| return null; /* dead code. */ |
| } |
| |
| protected void fileTop(PrintWriter out) { |
| out.println("/* DO NOT EDIT THIS FILE - it is machine generated */"); |
| } |
| |
| protected void includes(PrintWriter out) { |
| out.println("#include <jni.h>"); |
| } |
| |
| /* |
| * Deal with the C pre-processor. |
| */ |
| protected void cppGuardBegin(PrintWriter out) { |
| out.println("#ifdef __cplusplus"); |
| out.println("extern \"C\" {"); |
| out.println("#endif"); |
| } |
| |
| protected void cppGuardEnd(PrintWriter out) { |
| out.println("#ifdef __cplusplus"); |
| out.println("}"); |
| out.println("#endif"); |
| } |
| |
| protected void guardBegin(PrintWriter out, String cname) { |
| out.println("/* Header for class " + cname + " */"); |
| out.println(); |
| out.println("#ifndef _Included_" + cname); |
| out.println("#define _Included_" + cname); |
| } |
| |
| protected void guardEnd(PrintWriter out) { |
| out.println("#endif"); |
| } |
| |
| String encodeMethod(Symbol msym, ClassSymbol clazz, |
| boolean isOverloaded) throws TypeSignature.SignatureException { |
| StringBuilder result = new StringBuilder(100); |
| result.append("Java_"); |
| /* JNI */ |
| result.append(encode(clazz.flatname.toString(), EncoderType.JNI)); |
| result.append('_'); |
| result.append(encode(msym.getSimpleName(), EncoderType.JNI)); |
| if (isOverloaded) { |
| TypeSignature typeSig = new TypeSignature(types); |
| StringBuilder sig = typeSig.getParameterSignature(msym.type); |
| result.append("__").append(encode(sig, EncoderType.JNI)); |
| } |
| return result.toString(); |
| } |
| |
| static enum EncoderType { |
| CLASS, |
| FIELDSTUB, |
| FIELD, |
| JNI, |
| SIGNATURE |
| } |
| @SuppressWarnings("fallthrough") |
| static String encode(CharSequence name, EncoderType 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); |
| continue; |
| } |
| switch (mtype) { |
| case CLASS: |
| switch (ch) { |
| case '.': |
| case '_': |
| result.append("_"); |
| break; |
| case '$': |
| result.append("__"); |
| break; |
| default: |
| result.append(encodeChar(ch)); |
| } |
| break; |
| case JNI: |
| switch (ch) { |
| case '/': |
| case '.': |
| result.append("_"); |
| break; |
| case '_': |
| result.append("_1"); |
| break; |
| case ';': |
| result.append("_2"); |
| break; |
| case '[': |
| result.append("_3"); |
| break; |
| default: |
| result.append(encodeChar(ch)); |
| } |
| break; |
| case SIGNATURE: |
| result.append(isprint(ch) ? ch : encodeChar(ch)); |
| break; |
| case FIELDSTUB: |
| result.append(ch == '_' ? ch : encodeChar(ch)); |
| break; |
| default: |
| result.append(encodeChar(ch)); |
| } |
| } |
| return result.toString(); |
| } |
| |
| static String encodeChar(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: 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); |
| } |
| } |
| |
| JavacElements elems; |
| Types types; |
| |
| /* 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(Types types) { |
| this.types = types; |
| } |
| |
| StringBuilder getParameterSignature(Type mType) |
| throws SignatureException { |
| StringBuilder result = new StringBuilder(); |
| for (Type pType : mType.getParameterTypes()) { |
| result.append(getJvmSignature(pType)); |
| } |
| return result; |
| } |
| |
| StringBuilder getReturnSignature(Type mType) |
| throws SignatureException { |
| return getJvmSignature(mType.getReturnType()); |
| } |
| |
| StringBuilder getSignature(Type mType) throws SignatureException { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("(").append(getParameterSignature(mType)).append(")"); |
| sb.append(getReturnSignature(mType)); |
| return sb; |
| } |
| |
| /* |
| * Returns jvm internal signature. |
| */ |
| static class JvmTypeVisitor extends JNIWriter.SimpleTypeVisitor<Type, StringBuilder> { |
| |
| @Override |
| public Type visitClassType(Type.ClassType t, StringBuilder s) { |
| setDeclaredType(t, s); |
| return null; |
| } |
| |
| @Override |
| public Type visitArrayType(Type.ArrayType t, StringBuilder s) { |
| s.append("["); |
| return t.getComponentType().accept(this, s); |
| } |
| |
| @Override |
| public Type visitType(Type t, StringBuilder s) { |
| if (t.isPrimitiveOrVoid()) { |
| s.append(getJvmPrimitiveSignature(t)); |
| return null; |
| } |
| return t.accept(this, s); |
| } |
| private void setDeclaredType(Type t, StringBuilder s) { |
| String classname = t.tsym.getQualifiedName().toString(); |
| classname = classname.replace('.', '/'); |
| s.append("L").append(classname).append(";"); |
| } |
| private String getJvmPrimitiveSignature(Type t) { |
| switch (t.getKind()) { |
| case VOID: return SIG_VOID; |
| case BOOLEAN: return SIG_BOOLEAN; |
| case BYTE: return SIG_BYTE; |
| case CHAR: return SIG_CHAR; |
| case SHORT: return SIG_SHORT; |
| case INT: return SIG_INT; |
| case LONG: return SIG_LONG; |
| case FLOAT: return SIG_FLOAT; |
| case DOUBLE: return SIG_DOUBLE; |
| default: |
| Assert.error("unknown type: should not happen"); |
| } |
| return null; |
| } |
| } |
| |
| StringBuilder getJvmSignature(Type type) { |
| Type t = types.erasure(type); |
| StringBuilder sig = new StringBuilder(); |
| JvmTypeVisitor jv = new JvmTypeVisitor(); |
| jv.visitType(t, sig); |
| return sig; |
| } |
| } |
| |
| static class SimpleTypeVisitor<R, P> implements Type.Visitor<R, P> { |
| |
| protected final R DEFAULT_VALUE; |
| |
| protected SimpleTypeVisitor() { |
| DEFAULT_VALUE = null; |
| } |
| |
| protected SimpleTypeVisitor(R defaultValue) { |
| DEFAULT_VALUE = defaultValue; |
| } |
| |
| protected R defaultAction(Type t, P p) { |
| return DEFAULT_VALUE; |
| } |
| |
| @Override |
| public R visitClassType(Type.ClassType t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitWildcardType(Type.WildcardType t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitArrayType(Type.ArrayType t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitMethodType(Type.MethodType t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitPackageType(Type.PackageType t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitTypeVar(Type.TypeVar t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitCapturedType(Type.CapturedType t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitForAll(Type.ForAll t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitUndetVar(Type.UndetVar t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitErrorType(Type.ErrorType t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitType(Type t, P p) { |
| return defaultAction(t, p); |
| } |
| |
| @Override |
| public R visitModuleType(Type.ModuleType t, P p) { |
| return defaultAction(t, p); |
| } |
| } |
| } |