Type.java

// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 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 com.alibaba.fastjson2.internal.asm;

/**
 * A Java field or method type. This class can be used to make it easier to manipulate type and
 * method descriptors.
 *
 * @author Eric Bruneton
 * @author Chris Nokleberg
 */
public final class Type {
    static final int VOID = 0;
    static final int BOOLEAN = 1;
    static final int CHAR = 2;
    static final int BYTE = 3;
    static final int SHORT = 4;
    static final int INT = 5;
    static final int FLOAT = 6;
    static final int LONG = 7;
    static final int DOUBLE = 8;
    static final int ARRAY = 9;
    static final int OBJECT = 10;
    static final int METHOD = 11;
    static final int INTERNAL = 12;

    static final Type VOID_TYPE = new Type(VOID, "VZCBSIFJD", VOID, VOID + 1);
    static final Type BOOLEAN_TYPE = new Type(BOOLEAN, "VZCBSIFJD", BOOLEAN, BOOLEAN + 1);
    static final Type CHAR_TYPE = new Type(CHAR, "VZCBSIFJD", CHAR, CHAR + 1);
    static final Type BYTE_TYPE = new Type(BYTE, "VZCBSIFJD", BYTE, BYTE + 1);
    static final Type SHORT_TYPE = new Type(SHORT, "VZCBSIFJD", SHORT, SHORT + 1);
    static final Type INT_TYPE = new Type(INT, "VZCBSIFJD", INT, INT + 1);
    static final Type FLOAT_TYPE = new Type(FLOAT, "VZCBSIFJD", FLOAT, FLOAT + 1);
    static final Type LONG_TYPE = new Type(LONG, "VZCBSIFJD", LONG, LONG + 1);
    static final Type DOUBLE_TYPE = new Type(DOUBLE, "VZCBSIFJD", DOUBLE, DOUBLE + 1);

    static final Type TYPE_CLASS = new Type(10, "Ljava/lang/Class;", 1, 16);
    static final Type TYPE_TYPE = new Type(10, "Ljava/lang/reflect/Type;", 1, 23);
    static final Type TYPE_OBJECT = new Type(10, "Ljava/lang/Object;", 1, 17);
    static final Type TYPE_STRING = new Type(10, "Ljava/lang/String;", 1, 17);
    static final Type TYPE_LIST = new Type(10, "Ljava/util/List;", 1, 15);
    static final Type TYPE_JSON_READER = new Type(10, "Lcom/alibaba/fastjson2/JSONReader;", 1, 33);
    static final Type TYPE_JSON_WRITER = new Type(10, "Lcom/alibaba/fastjson2/JSONWriter;", 1, 33);
    static final Type TYPE_SUPPLIER = new Type(10, "Ljava/util/function/Supplier;", 1, 28);

    static final Type[] TYPES_0 = new Type[] {TYPE_CLASS, TYPE_STRING, TYPE_STRING, LONG_TYPE, TYPE_LIST};
    static final Type[] TYPES_1 = new Type[] {TYPE_JSON_WRITER, TYPE_OBJECT, TYPE_OBJECT, TYPE_TYPE, LONG_TYPE};
    static final Type[] TYPES_2 = new Type[] {TYPE_CLASS, TYPE_SUPPLIER, TYPE_JSON_READER};
    static final Type[] TYPES_3 = new Type[] {LONG_TYPE};
    static final Type[] TYPES_4 = new Type[] {TYPE_JSON_READER, TYPE_TYPE, TYPE_OBJECT, LONG_TYPE};

    final int sort;
    final String valueBuffer;
    final int valueBegin;
    final int valueEnd;

    private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) {
        this.sort = sort;
        this.valueBuffer = valueBuffer;
        this.valueBegin = valueBegin;
        this.valueEnd = valueEnd;
    }

    // -----------------------------------------------------------------------------------------------
    // Methods to get Type(s) from a descriptor, a reflected Method or Constructor, other types, etc.
    // -----------------------------------------------------------------------------------------------
//
//    /**
//     * Returns the {@link Type} corresponding to the given type descriptor.
//     *
//     * @param typeDescriptor a field or method type descriptor.
//     * @return the {@link Type} corresponding to the given type descriptor.
//     */
//    public static Type getType(final String typeDescriptor) {
//        return getTypeInternal(typeDescriptor, 0, typeDescriptor.length());
//    }

    /**
     * Returns the {@link Type} values corresponding to the argument types of the given method
     * descriptor.
     *
     * @param methodDescriptor a method descriptor.
     * @return the {@link Type} values corresponding to the argument types of the given method
     * descriptor.
     */
    static Type[] getArgumentTypes(final String methodDescriptor) {
        switch (methodDescriptor) {
            case "()V":
                return new Type[0];
            case "(J)Lcom/alibaba/fastjson2/reader/FieldReader;":
            case "(J)Ljava/lang/Object;":
                return TYPES_3;
            case "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;JLjava/util/List;)V":
                return TYPES_0;
            case "(Lcom/alibaba/fastjson2/JSONWriter;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;J)V":
                return TYPES_1;
            case "(Ljava/lang/Class;Ljava/util/function/Supplier;[Lcom/alibaba/fastjson2/reader/FieldReader;)V":
                return TYPES_2;
            case "(Lcom/alibaba/fastjson2/JSONReader;Ljava/lang/reflect/Type;Ljava/lang/Object;J)Ljava/lang/Object;":
                return TYPES_4;
            default:
                break;
        }

        // First step: compute the number of argument types in methodDescriptor.
        int numArgumentTypes = 0;
        // Skip the first character, which is always a '('.
        int currentOffset = 1;
        // Parse the argument types, one at a each loop iteration.
        while (methodDescriptor.charAt(currentOffset) != ')') {
            while (methodDescriptor.charAt(currentOffset) == '[') {
                currentOffset++;
            }
            if (methodDescriptor.charAt(currentOffset++) == 'L') {
                // Skip the argument descriptor content.
                int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
                currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
            }
            ++numArgumentTypes;
        }

        // Second step: create a Type instance for each argument type.
        Type[] argumentTypes = new Type[numArgumentTypes];
        // Skip the first character, which is always a '('.
        currentOffset = 1;
        // Parse and create the argument types, one at each loop iteration.
        int currentArgumentTypeIndex = 0;
        while (methodDescriptor.charAt(currentOffset) != ')') {
            final int currentArgumentTypeOffset = currentOffset;
            while (methodDescriptor.charAt(currentOffset) == '[') {
                currentOffset++;
            }
            if (methodDescriptor.charAt(currentOffset++) == 'L') {
                // Skip the argument descriptor content.
                int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
                currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
            }
            argumentTypes[currentArgumentTypeIndex++] =
                    getTypeInternal(methodDescriptor, currentArgumentTypeOffset, currentOffset);
        }
        return argumentTypes;
    }
//
//    /**
//     * Returns the start index of the return type of the given method descriptor.
//     *
//     * @param descriptor a method descriptor.
//     * @return the start index of the return type of the given method descriptor.
//     */
//    static int getReturnTypeOffset(final String descriptor) {
//        // Skip the first character, which is always a '('.
//        int currentOffset = 1;
//        // Skip the argument types, one at a each loop iteration.
//        while (descriptor.charAt(currentOffset) != ')') {
//            while (descriptor.charAt(currentOffset) == '[') {
//                currentOffset++;
//            }
//            if (descriptor.charAt(currentOffset++) == 'L') {
//                // Skip the argument descriptor content.
//                int semiColumnOffset = descriptor.indexOf(';', currentOffset);
//                currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
//            }
//        }
//        return currentOffset + 1;
//    }

    /**
     * Returns the {@link Type} corresponding to the given field or method descriptor.
     *
     * @param descriptorBuffer a buffer containing the field or method descriptor.
     * @param descriptorBegin  the beginning index, inclusive, of the field or method descriptor in
     *                         descriptorBuffer.
     * @param descriptorEnd    the end index, exclusive, of the field or method descriptor in
     *                         descriptorBuffer.
     * @return the {@link Type} corresponding to the given type descriptor.
     */
    static Type getTypeInternal(
            final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) {
        switch (descriptorBuffer.charAt(descriptorBegin)) {
            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 '[':
                return new Type(ARRAY, descriptorBuffer, descriptorBegin, descriptorEnd);
            case 'L':
                int len = descriptorEnd - descriptorBegin;
                switch (len) {
                    case 16:
                        if (descriptorBuffer.regionMatches(descriptorBegin, TYPE_LIST.valueBuffer, 0, len)) {
                            return TYPE_LIST;
                        }
                        break;
                    case 17:
                        if (descriptorBuffer.regionMatches(descriptorBegin, TYPE_CLASS.valueBuffer, 0, len)) {
                            return TYPE_CLASS;
                        }
                        break;
                    case 18:
                        if (descriptorBuffer.regionMatches(descriptorBegin, TYPE_STRING.valueBuffer, 0, len)) {
                            return TYPE_STRING;
                        }
                        if (descriptorBuffer.regionMatches(descriptorBegin, TYPE_OBJECT.valueBuffer, 0, len)) {
                            return TYPE_OBJECT;
                        }
                        break;
                    case 24:
                        if (descriptorBuffer.regionMatches(descriptorBegin, TYPE_TYPE.valueBuffer, 0, len)) {
                            return TYPE_TYPE;
                        }
                        break;
                    case 29:
                        if (descriptorBuffer.regionMatches(descriptorBegin, TYPE_SUPPLIER.valueBuffer, 0, len)) {
                            return TYPE_SUPPLIER;
                        }
                        break;
                    case 34:
                        if (descriptorBuffer.regionMatches(descriptorBegin, TYPE_JSON_WRITER.valueBuffer, 0, len)) {
                            return TYPE_JSON_WRITER;
                        }
                        if (descriptorBuffer.regionMatches(descriptorBegin, TYPE_JSON_READER.valueBuffer, 0, len)) {
                            return TYPE_JSON_READER;
                        }
                        break;
                    default:
                        break;
                }

                return new Type(OBJECT, descriptorBuffer, descriptorBegin + 1, descriptorEnd - 1);
            case '(':
                return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd);
            default:
                throw new IllegalArgumentException();
        }
    }

    // -----------------------------------------------------------------------------------------------
    // Methods to get class names, internal names or descriptors.
    // -----------------------------------------------------------------------------------------------
//
//    /**
//     * Returns the internal name of the class corresponding to this object or array type. The internal
//     * name of a class is its fully qualified name (as returned by Class.getName(), where '.' are
//     * replaced by '/'). This method should only be used for an object or array type.
//     *
//     * @return the internal name of the class corresponding to this object type.
//     */
//    public String getInternalName() {
//        return valueBuffer.substring(valueBegin, valueEnd);
//    }

    /**
     * Returns the descriptor corresponding to this type.
     *
     * @return the descriptor corresponding to this type.
     */
    public String getDescriptor() {
        if (sort == OBJECT) {
            switch (valueBuffer) {
                case "(Ljava/lang/Class;Ljava/util/function/Supplier;[Lcom/alibaba/fastjson2/reader/FieldReader;)V":
                    if (valueBegin == 2 && valueEnd == 17) {
                        return "Ljava/lang/Class;";
                    }
                    if (valueBegin == 19 && valueEnd == 46) {
                        return "Ljava/util/function/Supplier;";
                    }
                    break;
                case "(Lcom/alibaba/fastjson2/JSONReader;Ljava/lang/reflect/Type;Ljava/lang/Object;J)Ljava/lang/Object;":
                    if (valueBegin == 2 && valueEnd == 34) {
                        return "Lcom/alibaba/fastjson2/JSONReader;";
                    }
                    if (valueBegin == 36 && valueEnd == 58) {
                        return "Ljava/lang/reflect/Type;";
                    }
                    if (valueBegin == 60 && valueEnd == 76) {
                        return "Ljava/lang/Object;";
                    }
                    break;
                case "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;JLjava/util/List;)V":
                    if (valueBegin == 2 && valueEnd == 17) {
                        return "Ljava/lang/Class;";
                    }
                    if (valueBegin == 19 && valueEnd == 35) {
                        return "Ljava/lang/String;";
                    }
                    if (valueBegin == 37 && valueEnd == 53) {
                        return "Ljava/lang/String;";
                    }
                    if (valueBegin == 56 && valueEnd == 70) {
                        return "Ljava/util/List;";
                    }
                    break;
                case "(Lcom/alibaba/fastjson2/JSONWriter;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;J)V":
                    if (valueBegin == 2 && valueEnd == 34) {
                        return "Lcom/alibaba/fastjson2/JSONWriter;";
                    }
                    if (valueBegin == 36 && valueEnd == 52) {
                        return "Ljava/lang/Object;";
                    }
                    if (valueBegin == 54 && valueEnd == 70) {
                        return "Ljava/lang/Object;";
                    }
                    if (valueBegin == 72 && valueEnd == 94) {
                        return "Ljava/lang/reflect/Type;";
                    }
                    break;
                default:
                    break;
            }

            if (valueBegin == 1 && valueEnd + 1 == valueBuffer.length()) {
                return valueBuffer;
            }
            return valueBuffer.substring(valueBegin - 1, valueEnd + 1);
        } else if (sort == INTERNAL) {
            return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';';
        } else {
            switch (valueBuffer) {
                case "VZCBSIFJD":
                    if (valueBegin == 7 && valueEnd == 8) {
                        return "J";
                    }
                    break;
                case "(Ljava/lang/Class;Ljava/util/function/Supplier;[Lcom/alibaba/fastjson2/reader/FieldReader;)V":
                    if (valueBegin == 47 && valueEnd == 90) {
                        return "[Lcom/alibaba/fastjson2/reader/FieldReader;";
                    }
                    break;
                default:
                    break;
            }
            return valueBuffer.substring(valueBegin, valueEnd);
        }
    }

    // -----------------------------------------------------------------------------------------------
    // Methods to get the sort, dimension, size, and opcodes corresponding to a Type or descriptor.
    // -----------------------------------------------------------------------------------------------
//
//    public int getSort() {
//        return sort == INTERNAL ? OBJECT : sort;
//    }

    /**
     * Computes the size of the arguments and of the return value of a method.
     *
     * @param methodDescriptor a method descriptor.
     * @return the size of the arguments of the method (plus one for the implicit this argument),
     * argumentsSize, and the size of its return value, returnSize, packed into a single int i =
     * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
     * i >> 2}, and returnSize to {@code i & 0x03}).
     */
    public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
        switch (methodDescriptor) {
            case "()V":
                return 4;
            case "()I":
            case "()Z":
            case "()Ljava/lang/String;":
            case "()Ljava/lang/Class;":
                return 5;
            case "()J":
                return 6;
            case "(I)V":
            case "(Ljava/lang/String;)V":
            case "(Lcom/alibaba/fastjson2/JSONWriter;)V":
            case "(Ljava/lang/Object;)V":
            case "(Ljava/lang/Enum;)V":
                return 8;
            case "(C)Z":
            case "(Lcom/alibaba/fastjson2/JSONReader;)Lcom/alibaba/fastjson2/reader/ObjectReader;":
            case "(Ljava/lang/Object;)Z":
            case "(I)Ljava/lang/Object;":
            case "(Lcom/alibaba/fastjson2/JSONReader;)Ljava/lang/Object;":
            case "(Lcom/alibaba/fastjson2/JSONWriter;)Z":
            case "(I)Ljava/lang/Integer;":
                return 9;
            case "(J)V":
            case "(Lcom/alibaba/fastjson2/JSONReader;Ljava/lang/Object;)V":
            case "(Ljava/util/List;Ljava/lang/reflect/Type;)V":
            case "(Lcom/alibaba/fastjson2/JSONWriter;Ljava/lang/Enum;)V":
            case "(Lcom/alibaba/fastjson2/JSONWriter;I)V":
                return 12;
            case "(J)Z":
            case "(J)Ljava/lang/Object;":
            case "(J)Lcom/alibaba/fastjson2/reader/FieldReader;":
            case "(Ljava/lang/Object;Ljava/lang/reflect/Type;)Z":
            case "(Lcom/alibaba/fastjson2/writer/FieldWriter;Ljava/lang/Object;)Ljava/lang/String;":
            case "(Lcom/alibaba/fastjson2/JSONWriter;Ljava/lang/Class;)Lcom/alibaba/fastjson2/writer/ObjectWriter;":
            case "(Lcom/alibaba/fastjson2/JSONWriter;Ljava/lang/reflect/Type;)Lcom/alibaba/fastjson2/writer/ObjectWriter;":
                return 13;
            case "(Lcom/alibaba/fastjson2/JSONReader;Ljava/lang/Object;Ljava/lang/String;)V":
            case "(Ljava/lang/Class;Ljava/util/function/Supplier;[Lcom/alibaba/fastjson2/reader/FieldReader;)V":
            case "(Lcom/alibaba/fastjson2/JSONWriter;ZLjava/util/List;)V":
            case "(Lcom/alibaba/fastjson2/JSONWriter;J)V":
                return 16;
            case "(Ljava/lang/Object;JLjava/lang/Object;)V":
            case "(Lcom/alibaba/fastjson2/JSONReader;Ljava/util/List;ILjava/lang/String;)V":
                return 20;
            case "(Lcom/alibaba/fastjson2/JSONReader;Ljava/lang/Class;J)Ljava/lang/Object;":
            case "(Lcom/alibaba/fastjson2/JSONReader;Ljava/lang/Class;J)Lcom/alibaba/fastjson2/reader/ObjectReader;":
                return 21;
            case "(Lcom/alibaba/fastjson2/JSONReader;Ljava/lang/reflect/Type;Ljava/lang/Object;J)Ljava/lang/Object;":
                return 25;
            case "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;JLjava/util/List;)V":
            case "(Lcom/alibaba/fastjson2/JSONWriter;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;J)V":
                return 28;
            case "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;JLcom/alibaba/fastjson2/schema/JSONSchema;Ljava/util/function/Supplier;Ljava/util/function/Function;[Lcom/alibaba/fastjson2/reader/FieldReader;)V":
                return 40;
            default:
                break;
        }

        int argumentsSize = 1;
        // Skip the first character, which is always a '('.
        int currentOffset = 1;
        int currentChar = methodDescriptor.charAt(currentOffset);
        // Parse the argument types and compute their size, one at a each loop iteration.
        while (currentChar != ')') {
            if (currentChar == 'J' || currentChar == 'D') {
                currentOffset++;
                argumentsSize += 2;
            } else {
                while (methodDescriptor.charAt(currentOffset) == '[') {
                    currentOffset++;
                }
                if (methodDescriptor.charAt(currentOffset++) == 'L') {
                    // Skip the argument descriptor content.
                    int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
                    currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
                }
                argumentsSize += 1;
            }
            currentChar = methodDescriptor.charAt(currentOffset);
        }
        currentChar = methodDescriptor.charAt(currentOffset + 1);
        if (currentChar == 'V') {
            return argumentsSize << 2;
        } else {
            int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1;
            return argumentsSize << 2 | returnSize;
        }
    }

    public 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:
                Type elementType = getTypeInternal(valueBuffer, valueBegin + getDimensions(), valueEnd);
                StringBuilder stringBuilder = new StringBuilder(elementType.getClassName());
                for (int i = getDimensions(); i > 0; --i) {
                    stringBuilder.append("[]");
                }
                return stringBuilder.toString();
            case OBJECT:
            case INTERNAL:
                return valueBuffer.substring(valueBegin, valueEnd).replace('/', '.');
            default:
                throw new AssertionError();
        }
    }
//
//    public Type getElementType() {
//        return getTypeInternal(valueBuffer, valueBegin + getDimensions(), valueEnd);
//    }

    public int getDimensions() {
        int numDimensions = 1;
        while (valueBuffer.charAt(valueBegin + numDimensions) == '[') {
            numDimensions++;
        }
        return numDimensions;
    }
}