blob: 41fb62138dc9ab23173c9bb36d22b4bd7983fc3c [file] [log] [blame]
/*
* Copyright (c) 1999, 2014, 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.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.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.*;
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;
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();
FileObject outFile
= fileManager.getFileForOutput(StandardLocation.NATIVE_HEADER_OUTPUT,
"", 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);
}
}
}