/***
 *
 * Portions Copyright (c) 2007 Paul Hammant
 * Portions copyright (c) 2000-2007 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.zkoss.bind.paranamer;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;


/**
 * An ASM-based implementation of Paranamer. It relies on debug information compiled
 * with the "-g" javac option to retrieve parameter names.
 * <p>
 * Portions of this source file are a fork of ASM.
 *
 * @author Guilherme Silveira
 * @author Paul Hammant
 */
public class BytecodeReadingParanamer implements Paranamer {

    private static final Map<String, String> _primitives = new HashMap<String, String>() {
        {
            put("int", "I");
            put("boolean", "Z");
            put("byte", "B");
            put("char", "C");
            put("short", "S");
            put("float", "F");
            put("long", "J");
            put("double", "D");
        }
    };

    public String[] lookupParameterNames(AccessibleObject methodOrConstructor) {
        return lookupParameterNames(methodOrConstructor, true);
    }

    public String[] lookupParameterNames(AccessibleObject methodOrCtor, boolean throwExceptionIfMissing) {

        Class<?>[] types = null;
        Class<?> declaringClass = null;
        String name = null;
        if (methodOrCtor instanceof Method) {
            Method method = (Method) methodOrCtor;
            types = method.getParameterTypes();
            name = method.getName();
            declaringClass = method.getDeclaringClass();
        } else {
            Constructor<?> constructor = (Constructor<?>) methodOrCtor;
            types = constructor.getParameterTypes();
            declaringClass = constructor.getDeclaringClass();
            name = "<init>";
        }

        if (types.length == 0) {
            return EMPTY_NAMES;
        }
        InputStream byteCodeStream = getClassAsStream(declaringClass);
        if (byteCodeStream == null) {
            if (throwExceptionIfMissing) {
                throw new ParameterNamesNotFoundException("Unable to get class bytes");
            } else {
                return Paranamer.EMPTY_NAMES;
            }
        }
        try {
            ClassReader reader = new ClassReader(byteCodeStream);
            TypeCollector visitor = new TypeCollector(name, types, throwExceptionIfMissing);
            reader.accept(visitor);
            String[] parameterNamesForMethod = visitor.getParameterNamesForMethod();
            return parameterNamesForMethod;
        } catch (IOException e) {
            if (throwExceptionIfMissing) {
                throw new ParameterNamesNotFoundException("IoException while reading class bytes", e);
            } else {
                return Paranamer.EMPTY_NAMES;
            }
        } finally {
            try {
                byteCodeStream.close();
            } catch (IOException ignored) {
            }
        }
    }

    private InputStream getClassAsStream(Class<?> clazz) {
        ClassLoader classLoader = clazz.getClassLoader();
        if (classLoader == null) {
            classLoader = ClassLoader.getSystemClassLoader();
        }
        return getClassAsStream(classLoader, clazz.getName());
    }

    private InputStream getClassAsStream(ClassLoader classLoader, String className) {
        String name = className.replace('.', '/') + ".class";
        // better pre-cache all methods otherwise this content will be loaded
        // multiple times
        InputStream asStream = classLoader.getResourceAsStream(name);
        if (asStream == null) {
            asStream = BytecodeReadingParanamer.class.getResourceAsStream(name);
        }
        return asStream;
    }

    /**
     * The type collector waits for an specific method in order to start a method
     * collector.
     *
     * @author Guilherme Silveira
     */
    private static class TypeCollector {

        private static final String COMMA = ",";

        private final String methodName;

        private final Class<?>[] parameterTypes;
        private final boolean throwExceptionIfMissing;

        private MethodCollector collector;

        private TypeCollector(String methodName, Class<?>[] parameterTypes, boolean throwExceptionIfMissing) {
            this.methodName = methodName;
            this.parameterTypes = parameterTypes;
            this.throwExceptionIfMissing = throwExceptionIfMissing;
            this.collector = null;
        }

        private MethodCollector visitMethod(int access, String name, String desc) {
            // already found the method, skip any processing
            if (collector != null) {
                return null;
            }
            // not the same name
            if (!name.equals(methodName)) {
                return null;
            }
            Type[] argumentTypes = Type.getArgumentTypes(desc);
            int longOrDoubleQuantity = 0;
            for (Type t : argumentTypes) {
                if (t.getClassName().equals("long")
                        || t.getClassName().equals("double")) {
                    longOrDoubleQuantity++;
                }
            }
            int paramCount = argumentTypes.length;
            // not the same quantity of parameters
            if (paramCount != this.parameterTypes.length) {
                return null;
            }
            for (int i = 0; i < argumentTypes.length; i++) {
                if (!correctTypeName(argumentTypes, i).equals(
                        this.parameterTypes[i].getName())) {
                    return null;
                }
            }
            this.collector = new MethodCollector((Modifier.isStatic(access) ? 0 : 1),
                    argumentTypes.length + longOrDoubleQuantity);
            return collector;
        }

        private String correctTypeName(Type[] argumentTypes, int i) {
            String s = argumentTypes[i].getClassName();
            // array notation needs cleanup.
            String braces = "";
            while (s.endsWith("[]")) {
                braces = braces + "[";
                s = s.substring(0, s.length() - 2);
            }
            if (!braces.equals("")) {
                if (_primitives.containsKey(s)) {
                    s = braces + _primitives.get(s);
                } else {
                s = braces + "L" + s + ";";
                }
            }
            return s;
        }

        private String[] getParameterNamesForMethod() {
            if (collector == null) {
                return Paranamer.EMPTY_NAMES;
            }
            if (!collector.isDebugInfoPresent()) {
                if (throwExceptionIfMissing) {
                    throw new ParameterNamesNotFoundException("Parameter names not found for " + methodName);
                } else {
                    return Paranamer.EMPTY_NAMES;
                }
            }
            return collector.getResult().split(COMMA);
        }

    }

    /**
     * Objects of this class collects information from a specific method.
     *
     * @author Guilherme Silveira
     */
    private static class MethodCollector {

        private final int paramCount;

        private final int ignoreCount;

        private int currentParameter;

        private final StringBuffer result;

        private boolean debugInfoPresent;

        private MethodCollector(int ignoreCount, int paramCount) {
            this.ignoreCount = ignoreCount;
            this.paramCount = paramCount;
            this.result = new StringBuffer();
            this.currentParameter = 0;
            // if there are 0 parameters, there is no need for debug info
            this.debugInfoPresent = paramCount == 0;
        }

        private void visitLocalVariable(String name, int index) {
            if (index >= ignoreCount && index < ignoreCount + paramCount) {
                if (!name.equals("arg" + currentParameter)) {
                    debugInfoPresent = true;
                }
                result.append(',');
                result.append(name);
                currentParameter++;
            }
        }

        private String getResult() {
            return result.length() != 0 ? result.substring(1) : "";
        }

        private boolean isDebugInfoPresent() {
            return debugInfoPresent;
        }

    }

/***
 * Portions Copyright (c) 2007 Paul Hammant
 * Portions copyright (c) 2000-2007 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */


    /**
     * A Java class parser to make a Class Visitor visit an existing class.
     * This class parses a byte array conforming to the Java class file format and
     * calls the appropriate visit methods of a given class visitor for each field,
     * method and bytecode instruction encountered.
     *
     * @author Eric Bruneton
     * @author Eugene Kuleshov
     */
    private static class ClassReader {

        /**
         * The class to be parsed. <i>The content of this array must not be
         * modified. This field is intended for Attribute sub classes, and
         * is normally not needed by class generators or adapters.</i>
         */
        public final byte[] b;

        /**
         * The start index of each constant pool item in {@link #b b}, plus one.
         * The one byte offset skips the constant pool item tag that indicates its
         * type.
         */
        private final int[] items;

        /**
         * The String objects corresponding to the CONSTANT_Utf8 items. This cache
         * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
         * which GREATLY improves performances (by a factor 2 to 3). This caching
         * strategy could be extended to all constant pool items, but its benefit
         * would not be so great for these items (because they are much less
         * expensive to parse than CONSTANT_Utf8 items).
         */
        private final String[] strings;

        /**
         * Maximum length of the strings contained in the constant pool of the
         * class.
         */
        private final int maxStringLength;

        /**
         * Start index of the class header information (access, name...) in
         * {@link #b b}.
         */
        public final int header;


        /**
         * The type of CONSTANT_Fieldref constant pool items.
         */
        static final int FIELD = 9;

        /**
         * The type of CONSTANT_Methodref constant pool items.
         */
        static final int METH = 10;

        /**
         * The type of CONSTANT_InterfaceMethodref constant pool items.
         */
        static final int IMETH = 11;

        /**
         * The type of CONSTANT_Integer constant pool items.
         */
        static final int INT = 3;

        /**
         * The type of CONSTANT_Float constant pool items.
         */
        static final int FLOAT = 4;

        /**
         * The type of CONSTANT_Long constant pool items.
         */
        static final int LONG = 5;

        /**
         * The type of CONSTANT_Double constant pool items.
         */
        static final int DOUBLE = 6;

        /**
         * The type of CONSTANT_NameAndType constant pool items.
         */
        static final int NAME_TYPE = 12;

        /**
        * The type of CONSTANT_MethodHandle constant pool items.
        */
        static final int MHANDLE = 15;

        /**
        * The type of CONSTANT_InvokeDynamic constant pool items.
        */
        static final int INVOKEDYN = 18;

        /**
         * The type of CONSTANT_Utf8 constant pool items.
         */
        static final int UTF8 = 1;

        // ------------------------------------------------------------------------
        // Constructors
        // ------------------------------------------------------------------------

        /**
         * Constructs a new {@link ClassReader} object.
         *
         * @param b the bytecode of the class to be read.
         */
        private ClassReader(final byte[] b) {
            this(b, 0);
        }

        /**
         * Constructs a new {@link ClassReader} object.
         *
         * @param b   the bytecode of the class to be read.
         * @param off the start offset of the class data.
         */
        private ClassReader(final byte[] b, final int off) {
            this.b = b;
            // parses the constant pool
            items = new int[readUnsignedShort(off + 8)];
            int n = items.length;
            strings = new String[n];
            int max = 0;
            int index = off + 10;
            for (int i = 1; i < n; ++i) {
                items[i] = index + 1;
                int size;
                switch (b[index]) {
                    case FIELD:
                    case METH:
                    case IMETH:
                    case INT:
                    case FLOAT:
                    case INVOKEDYN:
                    case NAME_TYPE:
                        size = 5;
                        break;
                    case LONG:
                    case DOUBLE:
                        size = 9;
                        ++i;
                        break;
                    case MHANDLE:
                        size = 4;
                        break;
                    case UTF8:
                        size = 3 + readUnsignedShort(index + 1);
                        if (size > max) {
                            max = size;
                        }
                        break;
                        // case HamConstants.CLASS:
                        // case HamConstants.STR:
                    default:
                        size = 3;
                        break;
                }
                index += size;
            }
            maxStringLength = max;
            // the class header information starts just after the constant pool
            header = index;
        }


        /**
         * Constructs a new {@link ClassReader} object.
         *
         * @param is an input stream from which to read the class.
         * @throws IOException if a problem occurs during reading.
         */
        private ClassReader(final InputStream is) throws IOException {
            this(readClass(is));
        }

        /**
         * Reads the bytecode of a class.
         *
         * @param is an input stream from which to read the class.
         * @return the bytecode read from the given input stream.
         * @throws IOException if a problem occurs during reading.
         */
        private static byte[] readClass(final InputStream is) throws IOException {
            if (is == null) {
                throw new IOException("Class not found");
            }
            try {
		          byte[] b = new byte[is.available()];
		          int len = 0;
		          while (true) {
		              int n = is.read(b, len, b.length - len);
		              if (n == -1) {
		                  if (len < b.length) {
		                      byte[] c = new byte[len];
		                      System.arraycopy(b, 0, c, 0, len);
		                      b = c;
		                  }
		                  return b;
		              }
		              len += n;
		              if (len == b.length) {
		                  int last = is.read();
		                  if (last < 0) {
		                      return b;
		                  }
		                  byte[] c = new byte[b.length + 1000];
		                  System.arraycopy(b, 0, c, 0, len);
		                  c[len++] = (byte) last;
		                  b = c;
		              }
		          }
            } finally {
            	try {
            		is.close();
            	} catch (IOException ex) {
            		//ignore
            	}
            }
        }

        // ------------------------------------------------------------------------
        // Public methods
        // ------------------------------------------------------------------------

        /**
         * Makes the given visitor visit the Java class of this {@link ClassReader}.
         * This class is the one specified in the constructor (see
         * {@link #ClassReader(byte[]) ClassReader}).
         *
         * @param classVisitor the visitor that must visit this class.
         */
        private void accept(final TypeCollector classVisitor) {
            char[] c = new char[maxStringLength]; // buffer used to read strings
            int i, j, k; // loop variables
            int u, v, w; // indexes in b

            String attrName;
            int anns = 0;
            int ianns = 0;

            // visits the header
            u = header;
            v = items[readUnsignedShort(u + 4)];
            int len = readUnsignedShort(u + 6);
            w = 0;
            u += 8;
            for (i = 0; i < len; ++i) {
                u += 2;
            }
            v = u;
            i = readUnsignedShort(v);
            v += 2;
            for (; i > 0; --i) {
                j = readUnsignedShort(v + 6);
                v += 8;
                for (; j > 0; --j) {
                    v += 6 + readInt(v + 2);
                }
            }
            i = readUnsignedShort(v);
            v += 2;
            for (; i > 0; --i) {
                j = readUnsignedShort(v + 6);
                v += 8;
                for (; j > 0; --j) {
                    v += 6 + readInt(v + 2);
                }
            }

            i = readUnsignedShort(v);
            v += 2;
            for (; i > 0; --i) {
                v += 6 + readInt(v + 2);
            }

            //annotations not needed.

            // visits the fields
            i = readUnsignedShort(u);
            u += 2;
            for (; i > 0; --i) {
                j = readUnsignedShort(u + 6);
                u += 8;
                for (; j > 0; --j) {
                    u += 6 + readInt(u + 2);
                }
            }

            // visits the methods
            i = readUnsignedShort(u);
            u += 2;
            for (; i > 0; --i) {
                // inlined in original ASM source, now a method call
                u = readMethod(classVisitor, c, u);
            }
        }

        private int readMethod(TypeCollector classVisitor, char[] c, int u) {
            int v;
            int w;
            int j;
            String attrName;
            int k;
            int access = readUnsignedShort(u);
            String name = readUTF8(u + 2, c);
            String desc = readUTF8(u + 4, c);
            v = 0;
            w = 0;

            // looks for Code and Exceptions attributes
            j = readUnsignedShort(u + 6);
            u += 8;
            for (; j > 0; --j) {
                attrName = readUTF8(u, c);
                int attrSize = readInt(u + 2);
                u += 6;
                // tests are sorted in decreasing frequency order
                // (based on frequencies observed on typical classes)
                if (attrName.equals("Code")) {
                    v = u;
                }
                u += attrSize;
            }
            // reads declared exceptions
            if (w == 0) {
                // noop
            } else {
                w += 2;
                for (j = 0; j < readUnsignedShort(w); ++j) {
                    w += 2;
                }
            }

            // visits the method's code, if any
            MethodCollector mv = classVisitor.visitMethod(access, name, desc);

            if (mv != null && v != 0) {
                int codeLength = readInt(v + 4);
                v += 8;

                int codeStart = v;
                int codeEnd = v + codeLength;
                v = codeEnd;

                j = readUnsignedShort(v);
                v += 2;
                for (; j > 0; --j) {
                    v += 8;
                }
                // parses the local variable, line number tables, and code
                // attributes
                int varTable = 0;
                int varTypeTable = 0;
                j = readUnsignedShort(v);
                v += 2;
                for (; j > 0; --j) {
                    attrName = readUTF8(v, c);
                    if (attrName.equals("LocalVariableTable")) {
                        varTable = v + 6;
                    } else if (attrName.equals("LocalVariableTypeTable")) {
                        varTypeTable = v + 6;
                    }
                    v += 6 + readInt(v + 2);
                }

                v = codeStart;
                // visits the local variable tables
                if (varTable != 0) {
                    if (varTypeTable != 0) {
                        k = readUnsignedShort(varTypeTable) * 3;
                        w = varTypeTable + 2;
                        int[] typeTable = new int[k];
                        while (k > 0) {
                            typeTable[--k] = w + 6; // signature
                            typeTable[--k] = readUnsignedShort(w + 8); // index
                            typeTable[--k] = readUnsignedShort(w); // start
                            w += 10;
                        }
                    }
                    k = readUnsignedShort(varTable);
                    w = varTable + 2;
                    for (; k > 0; --k) {
                        int index = readUnsignedShort(w + 8);
                        mv.visitLocalVariable(readUTF8(w + 4, c), index);
                        w += 10;
                    }
                }
            }
            return u;
        }

        /**
         * Reads an unsigned short value in {@link #b b}. <i>This method is
         * intended for Attribute sub classes, and is normally not needed by
         * class generators or adapters.</i>
         *
         * @param index the start index of the value to be read in {@link #b b}.
         * @return the read value.
         */
        private int readUnsignedShort(final int index) {
            byte[] b = this.b;
            return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
        }

        /**
         * Reads a signed int value in {@link #b b}. <i>This method is intended for
         * Attribute sub classes, and is normally not needed by class
         * generators or adapters.</i>
         *
         * @param index the start index of the value to be read in {@link #b b}.
         * @return the read value.
         */
        private int readInt(final int index) {
            byte[] b = this.b;
            return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
                    | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
        }

        /**
         * Reads an UTF8 string constant pool item in {@link #b b}. <i>This method
         * is intended for Attribute sub classes, and is normally not needed
         * by class generators or adapters.</i>
         *
         * @param index the start index of an unsigned short value in {@link #b b},
         *              whose value is the index of an UTF8 constant pool item.
         * @param buf   buffer to be used to read the item. This buffer must be
         *              sufficiently large. It is not automatically resized.
         * @return the String corresponding to the specified UTF8 item.
         */
        private String readUTF8(int index, final char[] buf) {
            int item = readUnsignedShort(index);
            String s = strings[item];
            if (s != null) {
                return s;
            }
            index = items[item];
            return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
        }

        /**
         * Reads UTF8 string in {@link #b b}.
         *
         * @param index  start offset of the UTF8 string to be read.
         * @param utfLen length of the UTF8 string to be read.
         * @param buf    buffer to be used to read the string. This buffer must be
         *               sufficiently large. It is not automatically resized.
         * @return the String corresponding to the specified UTF8 string.
         */
        private String readUTF(int index, final int utfLen, final char[] buf) {
            int endIndex = index + utfLen;
            byte[] b = this.b;
            int strLen = 0;
            int c;
            int st = 0;
            char cc = 0;
            while (index < endIndex) {
                c = b[index++];
                switch (st) {
                    case 0:
                        c = c & 0xFF;
                        if (c < 0x80) {  // 0xxxxxxx
                            buf[strLen++] = (char) c;
                        } else if (c < 0xE0 && c > 0xBF) {  // 110x xxxx 10xx xxxx
                            cc = (char) (c & 0x1F);
                            st = 1;
                        } else {  // 1110 xxxx 10xx xxxx 10xx xxxx
                            cc = (char) (c & 0x0F);
                            st = 2;
                        }
                        break;

                    case 1:  // byte 2 of 2-byte char or byte 3 of 3-byte char
                        buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
                        st = 0;
                        break;

                    case 2:  // byte 2 of 3-byte char
                        cc = (char) ((cc << 6) | (c & 0x3F));
                        st = 1;
                        break;
                }
            }
            return new String(buf, 0, strLen);
        }

    }

/***
 * Portions Copyright (c) 2007 Paul Hammant
 * Portions copyright (c) 2000-2007 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

    /**
     * A Java type. This class can be used to make it easier to manipulate type and
     * method descriptors.
     *
     * @author Eric Bruneton
     * @author Chris Nokleberg
     */
    private static class Type {

        /**
         * The sort of the <tt>void</tt> type.
         */
        private static final int VOID = 0;

        /**
         * The sort of the <tt>boolean</tt> type.
         */
        private static final int BOOLEAN = 1;

        /**
         * The sort of the <tt>char</tt> type.
         */
        private static final int CHAR = 2;

        /**
         * The sort of the <tt>byte</tt> type.
         */
        private static final int BYTE = 3;

        /**
         * The sort of the <tt>short</tt> type.
         */
        private static final int SHORT = 4;

        /**
         * The sort of the <tt>int</tt> type.
         */
        private static final int INT = 5;

        /**
         * The sort of the <tt>float</tt> type.
         */
        private static final int FLOAT = 6;

        /**
         * The sort of the <tt>long</tt> type.
         */
        private static final int LONG = 7;

        /**
         * The sort of the <tt>double</tt> type.
         */
        private static final int DOUBLE = 8;

        /**
         * The sort of array reference types.
         */
        private static final int ARRAY = 9;

        /**
         * The sort of object reference type.
         */
        private static final int OBJECT = 10;

        /**
         * The <tt>void</tt> type.
         */
        private static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24)
            | (5 << 16) | (0 << 8) | 0, 1);

        /**
         * The <tt>boolean</tt> type.
         */
        private static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24)
            | (0 << 16) | (5 << 8) | 1, 1);

        /**
         * The <tt>char</tt> type.
         */
        private static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24)
            | (0 << 16) | (6 << 8) | 1, 1);

        /**
         * The <tt>byte</tt> type.
         */
        private static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24)
            | (0 << 16) | (5 << 8) | 1, 1);

        /**
         * The <tt>short</tt> type.
         */
        private static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24)
            | (0 << 16) | (7 << 8) | 1, 1);

        /**
         * The <tt>int</tt> type.
         */
        private static final Type INT_TYPE = new Type(INT, null, ('I' << 24)
            | (0 << 16) | (0 << 8) | 1, 1);

        /**
         * The <tt>float</tt> type.
         */
        private static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24)
            | (2 << 16) | (2 << 8) | 1, 1);

        /**
         * The <tt>long</tt> type.
         */
        private static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24)
            | (1 << 16) | (1 << 8) | 2, 1);

        /**
         * The <tt>double</tt> type.
         */
        private static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24)
            | (3 << 16) | (3 << 8) | 2, 1);

        // ------------------------------------------------------------------------
        // Fields
        // ------------------------------------------------------------------------

        /**
         * The sort of this Java type.
         */
        private final int sort;

        /**
         * A buffer containing the internal name of this Java type. This field is
         * only used for reference types.
         */
        private char[] buf;

        /**
         * The offset of the internal name of this Java type in {@link #buf buf} or,
         * for primitive types, the size, descriptor and getOpcode offsets for this
         * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset
         * for IALOAD or IASTORE, byte 3 the offset for all other instructions).
         */
        private int off;

        /**
         * The length of the internal name of this Java type.
         */
        private final int len;

        // ------------------------------------------------------------------------
        // Constructors
        // ------------------------------------------------------------------------

        /**
         * Constructs a primitive type.
         *
         * @param sort the sort of the primitive type to be constructed.
         */
        private Type(final int sort) {
            this.sort = sort;
            this.len = 1;
        }

        /**
         * Constructs a reference type.
         *
         * @param sort the sort of the reference type to be constructed.
         * @param buf  a buffer containing the descriptor of the previous type.
         * @param off  the offset of this descriptor in the previous buffer.
         * @param len  the length of this descriptor.
         */
        private Type(final int sort, final char[] buf, final int off, final int len) {
            this.sort = sort;
            this.buf = buf;
            this.off = off;
            this.len = len;
        }


        /**
         * Returns the Java types corresponding to the argument types of the given
         * method descriptor.
         *
         * @param methodDescriptor a method descriptor.
         * @return the Java types corresponding to the argument types of the given
         *         method descriptor.
         */
        private static Type[] getArgumentTypes(final String methodDescriptor) {
            char[] buf = methodDescriptor.toCharArray();
            int off = 1;
            int size = 0;
            while (true) {
                char car = buf[off++];
                if (car == ')') {
                    break;
                } else if (car == 'L') {
                    while (buf[off++] != ';') {
                    }
                    ++size;
                } else if (car != '[') {
                    ++size;
                }
            }

            Type[] args = new Type[size];
            off = 1;
            size = 0;
            while (buf[off] != ')') {
                args[size] = getType(buf, off);
                off += args[size].len + (args[size].sort == OBJECT ? 2 : 0);
                size += 1;
            }
            return args;
        }


        /**
         * Returns the Java type corresponding to the given type descriptor.
         *
         * @param buf a buffer containing a type descriptor.
         * @param off the offset of this descriptor in the previous buffer.
         * @return the Java type corresponding to the given type descriptor.
         */
        private static Type getType(final char[] buf, final int off) {
            int len;
            switch (buf[off]) {
                case 'V':
                    return VOID_TYPE;
                case 'Z':
                    return BOOLEAN_TYPE;
                case 'C':
                    return CHAR_TYPE;
                case 'B':
                    return BYTE_TYPE;
                case 'S':
                    return SHORT_TYPE;
                case 'I':
                    return INT_TYPE;
                case 'F':
                    return FLOAT_TYPE;
                case 'J':
                    return LONG_TYPE;
                case 'D':
                    return DOUBLE_TYPE;
                case '[':
                    len = 1;
                    while (buf[off + len] == '[') {
                        ++len;
                    }
                    if (buf[off + len] == 'L') {
                        ++len;
                        while (buf[off + len] != ';') {
                            ++len;
                        }
                    }
                    return new Type(ARRAY, buf, off, len + 1);
                    // case 'L':
                default:
                    len = 1;
                    while (buf[off + len] != ';') {
                        ++len;
                    }
                    return new Type(OBJECT, buf, off + 1, len - 1);
            }
        }

        // ------------------------------------------------------------------------
        // Accessors
        // ------------------------------------------------------------------------

        /**
         * Returns the number of dimensions of this array type. This method should
         * only be used for an array type.
         *
         * @return the number of dimensions of this array type.
         */
        private int getDimensions() {
            int i = 1;
            while (buf[off + i] == '[') {
                ++i;
            }
            return i;
        }

        /**
         * Returns the type of the elements of this array type. This method should
         * only be used for an array type.
         *
         * @return Returns the type of the elements of this array type.
         */
        private Type getElementType() {
            return getType(buf, off + getDimensions());
        }

        /**
         * Returns the name of the class corresponding to this type.
         *
         * @return the fully qualified name of the class corresponding to this type.
         */
        private String getClassName() {
            switch (sort) {
                case VOID:
                    return "void";
                case BOOLEAN:
                    return "boolean";
                case CHAR:
                    return "char";
                case BYTE:
                    return "byte";
                case SHORT:
                    return "short";
                case INT:
                    return "int";
                case FLOAT:
                    return "float";
                case LONG:
                    return "long";
                case DOUBLE:
                    return "double";
                case ARRAY:
                    StringBuffer b = new StringBuffer(getElementType().getClassName());
                    for (int i = getDimensions(); i > 0; --i) {
                        b.append("[]");
                    }
                    return b.toString();
                    // case OBJECT:
                default:
                    return new String(buf, off, len).replace('/', '.');
            }
        }
    }
}
