/*
 * 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.contains(",")) {
                    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.contains("[]")) {
                    // Gets array dimension.
                    int endindex = paramsig.indexOf("[]");
                    componentType = paramsig.substring(0, endindex);
                    String dimensionString =  paramsig.substring(endindex);
                    if(dimensionString != null){
                        while(dimensionString.contains("[]")){
                            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){
                switch (componentType) {
                    case "void":    JVMSig += SIG_VOID;    break;
                    case "boolean": JVMSig += SIG_BOOLEAN; break;
                    case "byte":    JVMSig += SIG_BYTE;    break;
                    case "char":    JVMSig += SIG_CHAR;    break;
                    case "short":   JVMSig += SIG_SHORT;   break;
                    case "int":     JVMSig += SIG_INT;     break;
                    case "long":    JVMSig += SIG_LONG;    break;
                    case "float":   JVMSig += SIG_FLOAT;   break;
                    case "double":  JVMSig += SIG_DOUBLE;  break;
                    default:
                        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 += ";";
                            }
                        }
                        break;
                }
            }
            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();
        }
    }

}
