JNIWriter.java revision 2571:10fc81ac75b4
1/*
2 * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javac.jvm;
27
28import java.io.IOException;
29import java.io.PrintWriter;
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.List;
33
34import javax.tools.FileObject;
35import javax.tools.JavaFileManager;
36import javax.tools.StandardLocation;
37
38import com.sun.tools.javac.code.Attribute;
39import com.sun.tools.javac.code.Flags;
40import com.sun.tools.javac.code.Symbol;
41import com.sun.tools.javac.code.Symbol.ClassSymbol;
42import com.sun.tools.javac.code.Symbol.VarSymbol;
43import com.sun.tools.javac.code.Symtab;
44import com.sun.tools.javac.code.Type;
45import com.sun.tools.javac.code.Types;
46import com.sun.tools.javac.model.JavacElements;
47import com.sun.tools.javac.util.Assert;
48import com.sun.tools.javac.util.Context;
49import com.sun.tools.javac.util.Log;
50import com.sun.tools.javac.util.Options;
51import com.sun.tools.javac.util.Pair;
52
53import static com.sun.tools.javac.main.Option.*;
54import static com.sun.tools.javac.code.Kinds.*;
55import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
56
57/** This class provides operations to write native header files for classes.
58 *
59 *  <p><b>This is NOT part of any supported API.
60 *  If you write code that depends on this, you do so at your own risk.
61 *  This code and its internal interfaces are subject to change or
62 *  deletion without notice.</b>
63 */
64public class JNIWriter {
65    protected static final Context.Key<JNIWriter> jniWriterKey = new Context.Key<>();
66
67    /** Access to files. */
68    private final JavaFileManager fileManager;
69
70    Types      types;
71    Symtab     syms;
72
73    /** The log to use for verbose output.
74     */
75    private final Log log;
76
77    /** Switch: verbose output.
78     */
79    private boolean verbose;
80
81    /** Switch: check all nested classes of top level class
82     */
83    private boolean checkAll;
84
85    private Context context;
86
87    private static final boolean isWindows =
88        System.getProperty("os.name").startsWith("Windows");
89
90    /** Get the ClassWriter instance for this context. */
91    public static JNIWriter instance(Context context) {
92        JNIWriter instance = context.get(jniWriterKey);
93        if (instance == null)
94            instance = new JNIWriter(context);
95        return instance;
96    }
97
98    /** Construct a class writer, given an options table.
99     */
100    private JNIWriter(Context context) {
101        context.put(jniWriterKey, this);
102        fileManager = context.get(JavaFileManager.class);
103        log = Log.instance(context);
104
105        Options options = Options.instance(context);
106        verbose = options.isSet(VERBOSE);
107        checkAll = options.isSet("javah:full");
108
109        this.context = context; // for lazyInit()
110    }
111
112    private void lazyInit() {
113        if (types == null)
114            types = Types.instance(context);
115        if (syms == null)
116            syms = Symtab.instance(context);
117
118    }
119
120    static boolean isSynthetic(Symbol s) {
121        return hasFlag(s, Flags.SYNTHETIC);
122    }
123    static boolean isStatic(Symbol s) {
124        return hasFlag(s, Flags.STATIC);
125    }
126    static boolean isFinal(Symbol s) {
127        return hasFlag(s, Flags.FINAL);
128    }
129    static boolean isNative(Symbol s) {
130        return hasFlag(s, Flags.NATIVE);
131    }
132    static private boolean hasFlag(Symbol m, int flag) {
133        return (m.flags() & flag) != 0;
134    }
135
136    public boolean needsHeader(ClassSymbol c) {
137        lazyInit();
138        if (c.isLocal() || isSynthetic(c))
139            return false;
140        return (checkAll)
141                ? needsHeader(c.outermostClass(), true)
142                : needsHeader(c, false);
143    }
144
145    private boolean needsHeader(ClassSymbol c, boolean checkNestedClasses) {
146        if (c.isLocal() || isSynthetic(c))
147            return false;
148
149        for (Symbol sym : c.members_field.getSymbols(NON_RECURSIVE)) {
150            if (sym.kind == MTH && isNative(sym))
151                return true;
152            for (Attribute.Compound a: sym.getDeclarationAttributes()) {
153                if (a.type.tsym == syms.nativeHeaderType.tsym)
154                    return true;
155            }
156        }
157        if (checkNestedClasses) {
158            for (Symbol sym : c.members_field.getSymbols(NON_RECURSIVE)) {
159                if ((sym.kind == TYP) && needsHeader(((ClassSymbol) sym), true))
160                    return true;
161            }
162        }
163        return false;
164    }
165
166    /** Emit a class file for a given class.
167     *  @param c      The class from which a class file is generated.
168     */
169    public FileObject write(ClassSymbol c) throws IOException {
170        String className = c.flatName().toString();
171        FileObject outFile
172            = fileManager.getFileForOutput(StandardLocation.NATIVE_HEADER_OUTPUT,
173                "", className.replaceAll("[.$]", "_") + ".h", null);
174        PrintWriter out = new PrintWriter(outFile.openWriter());
175        try {
176            write(out, c);
177            if (verbose)
178                log.printVerbose("wrote.file", outFile);
179            out.close();
180            out = null;
181        } finally {
182            if (out != null) {
183                // if we are propogating an exception, delete the file
184                out.close();
185                outFile.delete();
186                outFile = null;
187            }
188        }
189        return outFile; // may be null if write failed
190    }
191
192    public void write(PrintWriter out, ClassSymbol sym) throws IOException {
193        lazyInit();
194        try {
195            String cname = encode(sym.fullname, EncoderType.CLASS);
196            fileTop(out);
197            includes(out);
198            guardBegin(out, cname);
199            cppGuardBegin(out);
200
201            writeStatics(out, sym);
202            writeMethods(out, sym, cname);
203
204            cppGuardEnd(out);
205            guardEnd(out);
206        } catch (TypeSignature.SignatureException e) {
207            throw new IOException(e);
208        }
209    }
210    protected void writeStatics(PrintWriter out, ClassSymbol sym) throws IOException {
211        List<ClassSymbol> clist = new ArrayList<>();
212        for (ClassSymbol cd = sym; cd != null;
213                cd = (ClassSymbol) cd.getSuperclass().tsym) {
214            clist.add(cd);
215        }
216        /*
217         * list needs to be super-class, base-class1, base-class2 and so on,
218         * so we reverse class hierarchy
219         */
220        Collections.reverse(clist);
221        for (ClassSymbol cd : clist) {
222            for (Symbol i : cd.getEnclosedElements()) {
223                // consider only final, static and fields with ConstantExpressions
224                if (isFinal(i) && i.isStatic() && i.kind == VAR) {
225                    VarSymbol v = (VarSymbol) i;
226                    if (v.getConstantValue() != null) {
227                        Pair<ClassSymbol, VarSymbol> p = new Pair<>(sym, v);
228                        printStaticDefines(out, p);
229                    }
230                }
231            }
232        }
233    }
234    static void printStaticDefines(PrintWriter out, Pair<ClassSymbol, VarSymbol> p) {
235        ClassSymbol cls = p.fst;
236        VarSymbol f = p.snd;
237        Object value = f.getConstantValue();
238        String valueStr = null;
239        switch (f.asType().getKind()) {
240            case BOOLEAN:
241                valueStr = (((Boolean) value) ? "1L" : "0L");
242                break;
243            case BYTE: case SHORT: case INT:
244                valueStr = value.toString() + "L";
245                break;
246            case LONG:
247                // Visual C++ supports the i64 suffix, not LL.
248                valueStr = value.toString() + ((isWindows) ? "i64" : "LL");
249                break;
250            case CHAR:
251                Character ch = (Character) value;
252                valueStr = String.valueOf(((int) ch) & 0xffff) + "L";
253                break;
254            case FLOAT:
255                // bug compatible
256                float fv = ((Float) value).floatValue();
257                valueStr = (Float.isInfinite(fv))
258                        ? ((fv < 0) ? "-" : "") + "Inff"
259                        : value.toString() + "f";
260                break;
261            case DOUBLE:
262                // bug compatible
263                double d = ((Double) value).doubleValue();
264                valueStr = (Double.isInfinite(d))
265                        ? ((d < 0) ? "-" : "") + "InfD"
266                        : value.toString();
267                break;
268            default:
269                valueStr = null;
270        }
271        if (valueStr != null) {
272            out.print("#undef ");
273            String cname = encode(cls.getQualifiedName(), EncoderType.CLASS);
274            String fname = encode(f.getSimpleName(), EncoderType.FIELDSTUB);
275            out.println(cname + "_" + fname);
276            out.print("#define " + cname + "_");
277            out.println(fname + " " + valueStr);
278        }
279    }
280    protected void writeMethods(PrintWriter out, ClassSymbol sym, String cname)
281            throws IOException, TypeSignature.SignatureException {
282        List<Symbol> classmethods = sym.getEnclosedElements();
283        for (Symbol md : classmethods) {
284            if (isNative(md)) {
285                TypeSignature newtypesig = new TypeSignature(types);
286                CharSequence methodName = md.getSimpleName();
287                boolean isOverloaded = false;
288                for (Symbol md2 : classmethods) {
289                    if ((md2 != md)
290                            && (methodName.equals(md2.getSimpleName()))
291                            && isNative(md2)) {
292                        isOverloaded = true;
293                    }
294                }
295                out.println("/*");
296                out.println(" * Class:     " + cname);
297                out.println(" * Method:    " + encode(methodName, EncoderType.FIELDSTUB));
298                out.println(" * Signature: " + newtypesig.getSignature(md.type));
299                out.println(" */");
300                out.println("JNIEXPORT " + jniType(types.erasure(md.type.getReturnType()))
301                        + " JNICALL " + encodeMethod(md, sym, isOverloaded));
302                out.print("  (JNIEnv *, ");
303                out.print((md.isStatic())
304                        ? "jclass"
305                        : "jobject");
306                for (Type arg : types.erasure(md.type.getParameterTypes())) {
307                    out.print(", ");
308                    out.print(jniType(arg));
309                }
310                out.println(");");
311                out.println();
312            }
313        }
314    }
315    @SuppressWarnings("fallthrough")
316    protected final String jniType(Type t) {
317        switch (t.getKind()) {
318            case ARRAY: {
319                Type ct = ((Type.ArrayType)t).getComponentType();
320                switch (ct.getKind()) {
321                    case BOOLEAN:  return "jbooleanArray";
322                    case BYTE:     return "jbyteArray";
323                    case CHAR:     return "jcharArray";
324                    case SHORT:    return "jshortArray";
325                    case INT:      return "jintArray";
326                    case LONG:     return "jlongArray";
327                    case FLOAT:    return "jfloatArray";
328                    case DOUBLE:   return "jdoubleArray";
329                    case ARRAY:
330                    case DECLARED: return "jobjectArray";
331                    default: throw new Error(ct.toString());
332                }
333            }
334
335            case VOID:     return "void";
336            case BOOLEAN:  return "jboolean";
337            case BYTE:     return "jbyte";
338            case CHAR:     return "jchar";
339            case SHORT:    return "jshort";
340            case INT:      return "jint";
341            case LONG:     return "jlong";
342            case FLOAT:    return "jfloat";
343            case DOUBLE:   return "jdouble";
344            case DECLARED: {
345                if (t.tsym.type == syms.stringType) {
346                    return "jstring";
347                } else if (types.isAssignable(t, syms.throwableType)) {
348                    return "jthrowable";
349                } else if (types.isAssignable(t, syms.classType)) {
350                    return "jclass";
351                } else {
352                    return "jobject";
353                }
354            }
355        }
356
357        Assert.check(false, "jni unknown type");
358        return null; /* dead code. */
359    }
360
361    protected void  fileTop(PrintWriter out) {
362        out.println("/* DO NOT EDIT THIS FILE - it is machine generated */");
363    }
364
365    protected void includes(PrintWriter out) {
366        out.println("#include <jni.h>");
367    }
368
369    /*
370     * Deal with the C pre-processor.
371     */
372    protected void cppGuardBegin(PrintWriter out) {
373        out.println("#ifdef __cplusplus");
374        out.println("extern \"C\" {");
375        out.println("#endif");
376    }
377
378    protected void cppGuardEnd(PrintWriter out) {
379        out.println("#ifdef __cplusplus");
380        out.println("}");
381        out.println("#endif");
382    }
383
384    protected void guardBegin(PrintWriter out, String cname) {
385        out.println("/* Header for class " + cname + " */");
386        out.println();
387        out.println("#ifndef _Included_" + cname);
388        out.println("#define _Included_" + cname);
389    }
390
391    protected void guardEnd(PrintWriter out) {
392        out.println("#endif");
393    }
394
395    String encodeMethod(Symbol msym, ClassSymbol clazz,
396            boolean isOverloaded) throws TypeSignature.SignatureException {
397        StringBuilder result = new StringBuilder(100);
398        result.append("Java_");
399        /* JNI */
400        result.append(encode(clazz.flatname.toString(), EncoderType.JNI));
401        result.append('_');
402        result.append(encode(msym.getSimpleName(), EncoderType.JNI));
403        if (isOverloaded) {
404            TypeSignature typeSig = new TypeSignature(types);
405            StringBuilder sig = typeSig.getParameterSignature(msym.type);
406            result.append("__").append(encode(sig, EncoderType.JNI));
407        }
408        return result.toString();
409    }
410
411    static enum EncoderType {
412        CLASS,
413        FIELDSTUB,
414        FIELD,
415        JNI,
416        SIGNATURE
417    }
418    @SuppressWarnings("fallthrough")
419    static String encode(CharSequence name, EncoderType mtype) {
420        StringBuilder result = new StringBuilder(100);
421        int length = name.length();
422
423        for (int i = 0; i < length; i++) {
424            char ch = name.charAt(i);
425            if (isalnum(ch)) {
426                result.append(ch);
427                continue;
428            }
429            switch (mtype) {
430                case CLASS:
431                    switch (ch) {
432                        case '.':
433                        case '_':
434                            result.append("_");
435                            break;
436                        case '$':
437                            result.append("__");
438                            break;
439                        default:
440                            result.append(encodeChar(ch));
441                    }
442                    break;
443                case JNI:
444                    switch (ch) {
445                        case '/':
446                        case '.':
447                            result.append("_");
448                            break;
449                        case '_':
450                            result.append("_1");
451                            break;
452                        case ';':
453                            result.append("_2");
454                            break;
455                        case '[':
456                            result.append("_3");
457                            break;
458                        default:
459                            result.append(encodeChar(ch));
460                    }
461                    break;
462                case SIGNATURE:
463                    result.append(isprint(ch) ? ch : encodeChar(ch));
464                    break;
465                case FIELDSTUB:
466                    result.append(ch == '_' ? ch : encodeChar(ch));
467                    break;
468                default:
469                    result.append(encodeChar(ch));
470            }
471        }
472        return result.toString();
473    }
474
475    static String encodeChar(char ch) {
476        String s = Integer.toHexString(ch);
477        int nzeros = 5 - s.length();
478        char[] result = new char[6];
479        result[0] = '_';
480        for (int i = 1; i <= nzeros; i++) {
481            result[i] = '0';
482        }
483        for (int i = nzeros + 1, j = 0; i < 6; i++, j++) {
484            result[i] = s.charAt(j);
485        }
486        return new String(result);
487    }
488
489    /* Warning: Intentional ASCII operation. */
490    private static boolean isalnum(char ch) {
491        return ch <= 0x7f && /* quick test */
492                ((ch >= 'A' && ch <= 'Z')  ||
493                 (ch >= 'a' && ch <= 'z')  ||
494                 (ch >= '0' && ch <= '9'));
495    }
496
497    /* Warning: Intentional ASCII operation. */
498    private static boolean isprint(char ch) {
499        return ch >= 32 && ch <= 126;
500    }
501
502    private static class TypeSignature {
503        static class SignatureException extends Exception {
504            private static final long serialVersionUID = 1L;
505            SignatureException(String reason) {
506                super(reason);
507            }
508        }
509
510        JavacElements elems;
511        Types    types;
512
513        /* Signature Characters */
514        private static final String SIG_VOID                   = "V";
515        private static final String SIG_BOOLEAN                = "Z";
516        private static final String SIG_BYTE                   = "B";
517        private static final String SIG_CHAR                   = "C";
518        private static final String SIG_SHORT                  = "S";
519        private static final String SIG_INT                    = "I";
520        private static final String SIG_LONG                   = "J";
521        private static final String SIG_FLOAT                  = "F";
522        private static final String SIG_DOUBLE                 = "D";
523        private static final String SIG_ARRAY                  = "[";
524        private static final String SIG_CLASS                  = "L";
525
526        public TypeSignature(Types types) {
527            this.types = types;
528        }
529
530        StringBuilder getParameterSignature(Type mType)
531                throws SignatureException {
532            StringBuilder result = new StringBuilder();
533            for (Type pType : mType.getParameterTypes()) {
534                result.append(getJvmSignature(pType));
535            }
536            return result;
537        }
538
539        StringBuilder getReturnSignature(Type mType)
540                throws SignatureException {
541            return getJvmSignature(mType.getReturnType());
542        }
543
544        StringBuilder getSignature(Type mType) throws SignatureException {
545            StringBuilder sb = new StringBuilder();
546            sb.append("(").append(getParameterSignature(mType)).append(")");
547            sb.append(getReturnSignature(mType));
548            return sb;
549        }
550
551        /*
552         * Returns jvm internal signature.
553         */
554        static class JvmTypeVisitor extends JNIWriter.SimpleTypeVisitor<Type, StringBuilder> {
555
556            @Override
557            public Type visitClassType(Type.ClassType t, StringBuilder s) {
558                setDeclaredType(t, s);
559                return null;
560            }
561
562            @Override
563            public Type visitArrayType(Type.ArrayType t, StringBuilder s) {
564                s.append("[");
565                return t.getComponentType().accept(this, s);
566            }
567
568            @Override
569            public Type visitType(Type t, StringBuilder s) {
570                if (t.isPrimitiveOrVoid()) {
571                    s.append(getJvmPrimitiveSignature(t));
572                    return null;
573                }
574                return t.accept(this, s);
575            }
576            private void setDeclaredType(Type t, StringBuilder s) {
577                    String classname = t.tsym.getQualifiedName().toString();
578                    classname = classname.replace('.', '/');
579                    s.append("L").append(classname).append(";");
580            }
581            private String getJvmPrimitiveSignature(Type t) {
582                switch (t.getKind()) {
583                    case VOID:      return SIG_VOID;
584                    case BOOLEAN:   return SIG_BOOLEAN;
585                    case BYTE:      return SIG_BYTE;
586                    case CHAR:      return SIG_CHAR;
587                    case SHORT:     return SIG_SHORT;
588                    case INT:       return SIG_INT;
589                    case LONG:      return SIG_LONG;
590                    case FLOAT:     return SIG_FLOAT;
591                    case DOUBLE:    return SIG_DOUBLE;
592                    default:
593                        Assert.error("unknown type: should not happen");
594                }
595                return null;
596            }
597        }
598
599        StringBuilder getJvmSignature(Type type) {
600            Type t = types.erasure(type);
601            StringBuilder sig = new StringBuilder();
602            JvmTypeVisitor jv = new JvmTypeVisitor();
603            jv.visitType(t, sig);
604            return sig;
605        }
606    }
607
608    static class SimpleTypeVisitor<R, P> implements Type.Visitor<R, P> {
609
610        protected final R DEFAULT_VALUE;
611
612        protected SimpleTypeVisitor() {
613            DEFAULT_VALUE = null;
614        }
615
616        protected SimpleTypeVisitor(R defaultValue) {
617            DEFAULT_VALUE = defaultValue;
618        }
619
620        protected R defaultAction(Type t, P p) {
621            return DEFAULT_VALUE;
622        }
623
624        @Override
625        public R visitClassType(Type.ClassType t, P p) {
626            return defaultAction(t, p);
627        }
628
629        @Override
630        public R visitWildcardType(Type.WildcardType t, P p) {
631            return defaultAction(t, p);
632        }
633
634        @Override
635        public R visitArrayType(Type.ArrayType t, P p) {
636            return defaultAction(t, p);
637        }
638
639        @Override
640        public R visitMethodType(Type.MethodType t, P p) {
641            return defaultAction(t, p);
642        }
643
644        @Override
645        public R visitPackageType(Type.PackageType t, P p) {
646            return defaultAction(t, p);
647        }
648
649        @Override
650        public R visitTypeVar(Type.TypeVar t, P p) {
651            return defaultAction(t, p);
652        }
653
654        @Override
655        public R visitCapturedType(Type.CapturedType t, P p) {
656            return defaultAction(t, p);
657        }
658
659        @Override
660        public R visitForAll(Type.ForAll t, P p) {
661            return defaultAction(t, p);
662        }
663
664        @Override
665        public R visitUndetVar(Type.UndetVar t, P p) {
666            return defaultAction(t, p);
667        }
668
669        @Override
670        public R visitErrorType(Type.ErrorType t, P p) {
671            return defaultAction(t, p);
672        }
673
674        @Override
675        public R visitType(Type t, P p) {
676            return defaultAction(t, p);
677        }
678    }
679}
680