com.redhat.ceylon.langtools.tools.javac.jvm.JNIWriter Maven / Gradle / Ivy
/*
* 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.redhat.ceylon.langtools.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 com.redhat.ceylon.javax.lang.model.element.Element;
import com.redhat.ceylon.javax.lang.model.element.ExecutableElement;
import com.redhat.ceylon.javax.lang.model.element.Modifier;
import com.redhat.ceylon.javax.lang.model.element.Name;
import com.redhat.ceylon.javax.lang.model.element.TypeElement;
import com.redhat.ceylon.javax.lang.model.element.VariableElement;
import com.redhat.ceylon.javax.lang.model.type.ArrayType;
import com.redhat.ceylon.javax.lang.model.type.DeclaredType;
import com.redhat.ceylon.javax.lang.model.type.NoType;
import com.redhat.ceylon.javax.lang.model.type.PrimitiveType;
import com.redhat.ceylon.javax.lang.model.type.TypeKind;
import com.redhat.ceylon.javax.lang.model.type.TypeMirror;
import com.redhat.ceylon.javax.lang.model.type.TypeVariable;
import com.redhat.ceylon.javax.lang.model.type.TypeVisitor;
import com.redhat.ceylon.javax.lang.model.util.ElementFilter;
import com.redhat.ceylon.javax.lang.model.util.Elements;
import com.redhat.ceylon.javax.lang.model.util.SimpleTypeVisitor8;
import com.redhat.ceylon.javax.lang.model.util.Types;
import com.redhat.ceylon.javax.tools.FileObject;
import com.redhat.ceylon.javax.tools.JavaFileManager;
import com.redhat.ceylon.javax.tools.StandardLocation;
import com.redhat.ceylon.langtools.tools.javac.code.Attribute;
import com.redhat.ceylon.langtools.tools.javac.code.Flags;
import com.redhat.ceylon.langtools.tools.javac.code.Kinds;
import com.redhat.ceylon.langtools.tools.javac.code.Scope;
import com.redhat.ceylon.langtools.tools.javac.code.Symbol.ClassSymbol;
import com.redhat.ceylon.langtools.tools.javac.code.Symtab;
import com.redhat.ceylon.langtools.tools.javac.model.JavacElements;
import com.redhat.ceylon.langtools.tools.javac.model.JavacTypes;
import com.redhat.ceylon.langtools.tools.javac.util.Assert;
import com.redhat.ceylon.langtools.tools.javac.util.Context;
import com.redhat.ceylon.langtools.tools.javac.util.Log;
import com.redhat.ceylon.langtools.tools.javac.util.Options;
import static com.redhat.ceylon.langtools.tools.javac.main.Option.*;
/** This class provides operations to write native header files for classes.
*
* 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.
*/
public class JNIWriter {
protected static final Context.Key jniWriterKey =
new Context.Key();
/** 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 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 getAllFields(TypeElement subclazz) {
List fields = new ArrayList();
TypeElement cd = null;
Stack s = new Stack();
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 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 args = new ArrayList();
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 ";
}
/*
* 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 params = new ArrayList(); //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 v = new SimpleTypeVisitor8() {
@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();
}
}
}