asmlib.Type.java Source code

Java tutorial

Introduction

Here is the source code for asmlib.Type.java

Source

/*
 * asmlib: a toolkit based on ASM for working with java bytecode
 * Copyright (C) 2015 Ivo Anjo <ivo.anjo@ist.utl.pt>
 *
 * This file is part of asmlib.
 *
 * asmlib is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * asmlib 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with asmlib.  If not, see <http://www.gnu.org/licenses/>.
 */

package asmlib;

import util.*;

import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.Opcodes;

/**
 * Classe que representa um tipo em Java/na JVM.
 * A ideia desta classe  isolar os diferentes "formatos" em que o nome de uma classe pode estar:
 * filePath: java/lang/String.class
 * simple: String
 * package: java.lang
 * bytecode: Ljava/lang/String;
 * asm: java/lang/String
 * common: java.lang.String
 * opcode: Tipos primitivos, em que o opcode  o utilizado nos classfiles do java para os representar
 * class: Instncia de java/lang/Class
 **/

/* Nota: Numa classe como
   public class GenTest<Xpto extends String> {
   Xpto field;
    
   GenTest(Xpto arg) { field = arg; }
   }
   field vai ter o description Ljava/lang/Object; e vai ter a signature TXpto; .
   Pelos vistos os nomes dos placeholders so guardados nos generics, prefixados com T e terminados com ; .
   (Da o T...; que esta classe suporta)
 */

public class Type implements Comparable<Type> {

    // Alguns tipos j pr-gerados, para mais fcil acesso
    public static final Type OBJECT = Type.fromCommon("java.lang.Object");
    public static final Type STRING = Type.fromCommon("java.lang.String");

    public static final Type PRIM_BOOLEAN = Type.fromBytecode('Z');
    public static final Type PRIM_BYTE = Type.fromBytecode('B');
    public static final Type PRIM_CHAR = Type.fromBytecode('C');
    public static final Type PRIM_SHORT = Type.fromBytecode('S');
    public static final Type PRIM_INT = Type.fromBytecode('I');
    public static final Type PRIM_LONG = Type.fromBytecode('J');
    public static final Type PRIM_FLOAT = Type.fromBytecode('F');
    public static final Type PRIM_DOUBLE = Type.fromBytecode('D');
    public static final Type PRIM_VOID = Type.fromBytecode('V');

    public static final Type OBJECT_BOOLEAN = Type.fromCommon("java.lang.Boolean");
    public static final Type OBJECT_BYTE = Type.fromCommon("java.lang.Byte");
    public static final Type OBJECT_CHARACTER = Type.fromCommon("java.lang.Character");
    public static final Type OBJECT_SHORT = Type.fromCommon("java.lang.Short");
    public static final Type OBJECT_INTEGER = Type.fromCommon("java.lang.Integer");
    public static final Type OBJECT_LONG = Type.fromCommon("java.lang.Long");
    public static final Type OBJECT_FLOAT = Type.fromCommon("java.lang.Float");
    public static final Type OBJECT_DOUBLE = Type.fromCommon("java.lang.Double");
    public static final Type OBJECT_VOID = Type.fromCommon("java.lang.Void");

    /** Nome da classe no formato bytecode **/
    private final String _bytecodeName;

    private Type(String bytecodeName) {
        _bytecodeName = bytecodeName;

        if (bytecodeName.isEmpty())
            throw new AssertionError("Empty bytecodeName");
        if (bytecodeName.length() == 1 && !isPrimitive())
            throw new Error("Invalid bytecodeName");
    }

    public static Type fromCommon(String common) {
        String bytecode = "L" + common.replace(".", "/").replace("[]", "") + ";";

        if (common.endsWith("[]")) {
            String prefix = common.substring(common.indexOf('['), common.lastIndexOf(']') + 1).replace(']', '[');
            bytecode = prefix.substring(0, prefix.length() / 2) + bytecode;
        }

        return Type.fromBytecode(bytecode);
    }

    public static Type fromFilePath(String path) {
        return Type.fromCommon(path.replace("/", ".").replace(".class", ""));
    }

    public static Type fromAsm(String asm) {
        if (asm.startsWith("[")) {
            // Caso especial, parece que o "formato asm" passa a "formato bytecode" quando  um array
            // Por exemplo o formato asm para array de object  "[Ljava/lang/Object;"
            return Type.fromBytecode(asm);
        }
        return Type.fromBytecode("L" + asm + ";");
    }

    public static Type fromBytecode(char c) {
        return Type.fromBytecode(String.valueOf(c));
    }

    public static Type fromBytecode(String bytecode) {
        assert (bytecode.indexOf('.') == -1 || bytecode.indexOf('<') >= 0);
        return new Type(bytecode);
    }

    public static Type fromOpcode(int opcode) {
        char type;

        switch (opcode) {
        case Opcodes.T_BOOLEAN:
            type = 'Z';
            break;
        case Opcodes.T_BYTE:
            type = 'B';
            break;
        case Opcodes.T_CHAR:
            type = 'C';
            break;
        case Opcodes.T_SHORT:
            type = 'S';
            break;
        case Opcodes.T_INT:
            type = 'I';
            break;
        case Opcodes.T_LONG:
            type = 'J';
            break;
        case Opcodes.T_FLOAT:
            type = 'F';
            break;
        case Opcodes.T_DOUBLE:
            type = 'D';
            break;
        default:
            throw new InstrumentationException("Unknown or invalid bytecode type");
        }

        return Type.fromBytecode(type);
    }

    public static Type fromLoadOpcode(int opcode) {
        char type;

        switch (opcode) {
        case Opcodes.ILOAD:
            type = 'I';
            break;
        case Opcodes.LLOAD:
            type = 'J';
            break;
        case Opcodes.FLOAD:
            type = 'F';
            break;
        case Opcodes.DLOAD:
            type = 'D';
            break;
        case Opcodes.ALOAD:
            return Type.OBJECT;
        default:
            throw new InstrumentationException("Unknown or invalid bytecode type");
        }

        return Type.fromBytecode(type);
    }

    public static Type fromClass(Class<?> c) {
        if (c.isPrimitive()) {
            String name = c.getName().toLowerCase();
            for (String[] typeMapping : primitiveTypeNames) {
                if (typeMapping[0].equals(name))
                    return Type.fromBytecode(typeMapping[1]);
            }
            throw new AssertionError("This should never happen");
        }

        String s = c.getName();

        if (s.charAt(0) == '[') {
            // Java troca para um misto entre bytecodeName e commonName para arrays
            return Type.fromBytecode(s.replace('.', '/'));
        }

        return Type.fromCommon(s);
    }

    public static Type fromType(org.objectweb.asm.Type t) {
        return Type.fromBytecode(t.getDescriptor());
    }

    public static UtilList<Type> getArgumentTypes(String methodDescriptor) {
        String input = methodDescriptor;
        UtilList<Type> outList = new UtilArrayList<Type>();
        char c;
        int array = 0;
        Type t;

        // Exemplo descrio com "constraints" de generics
        // <T:Ljava/lang/Object;>([TT;)[TT;
        if ((input.charAt(0) != '(') && (input.charAt(0) == '<')) {
            input = input.substring(input.indexOf('('));
        }

        while (input.length() > 0) {
            c = input.charAt(0);
            input = input.substring(1);
            if (c == '[') {
                array++;
            } else if ((c == 'L') || (c == 'T')) {
                int pos = input.indexOf(';') + 1;
                String s = input.substring(0, pos);
                // Handling generics
                int genCount = StringUtils.countMatches(s, "<");
                if (genCount > 0) {
                    while (genCount > 0) {
                        pos = input.indexOf('>', pos);
                        genCount--;
                    }
                    pos = input.indexOf(';', pos) + 1;
                    s = input.substring(0, pos);
                }
                input = input.substring(pos);

                t = Type.fromBytecode(c + s);
                if (array > 0) {
                    t = t.toArray(array);
                    array = 0;
                }

                outList.add(t);
            } else if (c == '(') {
                // do nothing
            } else if (c == 'V') {
                throw new AssertionError("Invalid type in methodDescriptor");
            } else if (c == ')') {
                return outList;
            } else {
                t = Type.fromBytecode(Character.valueOf(c).toString());
                if (!t.isPrimitive())
                    throw new AssertionError("Invalid type in methodDescriptor");

                if (array > 0) {
                    t = t.toArray(array);
                    array = 0;
                }

                outList.add(t);
            }
        }

        throw new Error("Malformed methodDescriptor");
    }

    @Override
    public String toString() {
        return commonName();
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Type) {
            Type other = (Type) o;
            return compareTo(other) == 0;
        }
        return false;
    }

    public int compareTo(Type other) {
        return bytecodeName().compareTo(other.bytecodeName());
    }

    @Override
    public int hashCode() {
        return bytecodeName().hashCode();
    }

    public String simpleName() {
        return StringList.split(commonName(), ".").last();
    }

    public String commonName() {
        if (isPrimitive())
            return primitiveTypeName();

        String bytecode = bytecodeName();
        String suffix = "";
        // Transformar arrays
        if (isArray()) {
            int dimensions = arrayDimensions();
            for (int i = 0; i < dimensions; i++)
                suffix += "[]";
            bytecode = bytecode.substring(dimensions, bytecode.length());
        }

        if (bytecode.charAt(0) == 'L') {
            bytecode = bytecode.substring(1, bytecode.length() - 1);
        } else if (bytecode.charAt(0) == 'T') {
            // FIXME: No tenho bem a certeza quanto a este
            bytecode = bytecode.substring(1, bytecode.length() - 1);
        } else {
            // array de tipo primitivo
        }

        bytecode = bytecode.replace('/', '.');

        if (!suffix.isEmpty())
            bytecode += suffix;

        return bytecode;
    }

    public String bytecodeName() {
        return _bytecodeName;
    }

    public String asmName() {
        String type = bytecodeName();

        // O asmName de um array  aparentemente o seu bytecodeName sem alteraes
        if (isArray())
            return type;
        if (isPrimitive())
            throw new Error("Cannot represent primitive types using asmName");

        return type.substring(1, type.length() - 1);
    }

    public org.objectweb.asm.Type toType() {
        return org.objectweb.asm.Type.getObjectType(asmName());
    }

    /** Converte arrays na sua classe original: Object[][][] passa a Object **/
    public Type stripArray() {
        String strip = bytecodeName();
        return Type.fromBytecode(strip.replace("[", ""));
    }

    public int arrayDimensions() {
        String bytecodeName = bytecodeName();
        int pos = 0;
        while (pos < bytecodeName.length() && (bytecodeName.charAt(pos) == '['))
            pos++;
        return pos;
    }

    public boolean isPrimitive() {
        String name = bytecodeName();
        if (name.length() != 1)
            return false;
        char c = name.charAt(0);
        return ((c == 'Z') || (c == 'B') || (c == 'C') || (c == 'S') || (c == 'I') || (c == 'J') || (c == 'F')
                || (c == 'D')) || (c == 'V');
    }

    public boolean isArray() {
        return bytecodeName().charAt(0) == '[';
    }

    public Type toArray() {
        return toArray(1);
    }

    public Type toArray(int dimensions) {
        if (dimensions <= 0)
            throw new AssertionError("Dimensions cannot be <= 0");
        String dims = "";
        for (int i = 0; i < dimensions; i++)
            dims += "[";
        return Type.fromBytecode(dims + bytecodeName());
    }

    public boolean isGeneric() {
        return bytecodeName().contains("<");
    }

    private static final Type[][] primitiveWrappers = new Type[][] { { OBJECT_BOOLEAN, PRIM_BOOLEAN },
            { OBJECT_BYTE, PRIM_BYTE }, { OBJECT_CHARACTER, PRIM_CHAR }, { OBJECT_SHORT, PRIM_SHORT },
            { OBJECT_INTEGER, PRIM_INT }, { OBJECT_LONG, PRIM_LONG }, { OBJECT_FLOAT, PRIM_FLOAT },
            { OBJECT_DOUBLE, PRIM_DOUBLE }, { OBJECT_VOID, PRIM_VOID } };

    public Type toObject() {
        for (Type[] map : primitiveWrappers) {
            if (this.equals(map[1]))
                return map[0];
        }
        return null;
    }

    public Type toPrimitive() {
        for (Type[] map : primitiveWrappers) {
            if (this.equals(map[0]))
                return map[1];
        }
        return null;
    }

    /** Mtodo que retorna o opcode certo para o tipo de varivel que se quer fazer load.
      * Opcodes disponveis:
      * - ILOAD para boolean, byte, char, short, int
      * - LLOAD para long
      * - FLOAD para float
      * - DLOAD para double
      * - ALOAD para referncia (objecto ou array)
      **/
    public int getLoadInsn() {
        char c = bytecodeName().charAt(0);
        switch (c) {
        case 'Z': // boolean
        case 'B': // byte
        case 'C': // char
        case 'S': // short
        case 'I': // int
            return Opcodes.ILOAD;
        case 'J': // long
            return Opcodes.LLOAD;
        case 'F': // float
            return Opcodes.FLOAD;
        case 'D': // double
            return Opcodes.DLOAD;
        case '[': // Algum tipo de array
        case 'L': // objecto
        case 'T': // objecto (generics)
            return Opcodes.ALOAD;
        }
        throw new InstrumentationException("Unknown fieldType in getLoadInsn");
    }

    /** Mtodo que retorna o opcode certo para o tipo de varivel que se quer fazer store.
      * Opcodes disponveis:
      * - ISTORE para boolean, byte, char, short, int
      * - LSTORE para long
      * - FSTORE para float
      * - DSTORE para double
      * - ASTORE para referncia (objecto ou array)
      **/
    public int getStoreInsn() {
        char c = bytecodeName().charAt(0);
        switch (c) {
        case 'Z': // boolean
        case 'B': // byte
        case 'C': // char
        case 'S': // short
        case 'I': // int
            return Opcodes.ISTORE;
        case 'J': // long
            return Opcodes.LSTORE;
        case 'F': // float
            return Opcodes.FSTORE;
        case 'D': // double
            return Opcodes.DSTORE;
        case '[': // Algum tipo de array
        case 'L': // objecto
        case 'T': // objecto (generics)
            return Opcodes.ASTORE;
        }
        throw new InstrumentationException("Unknown fieldType in getStoreInsn");
    }

    /** Mtodo que retorna o opcode certo para o tipo de varivel que se quer fazer return.
      * Opcodes disponveis:
      * - IRETURN para boolean, byte, char, short, int
      * - LRETURN para long
      * - FRETURN para float
      * - DRETURN para double
      * - ARETURN para referncia (objecto ou array)
      * -  RETURN para mtodos void
      **/
    public int getReturnInsn() {
        char c = bytecodeName().charAt(0);
        switch (c) {
        case 'Z': // boolean
        case 'B': // byte
        case 'C': // char
        case 'S': // short
        case 'I': // int
            return Opcodes.IRETURN;
        case 'J': // long
            return Opcodes.LRETURN;
        case 'F': // float
            return Opcodes.FRETURN;
        case 'D': // double
            return Opcodes.DRETURN;
        case '[': // Algum tipo de array
        case 'L': // objecto
        case 'T': // objecto (generics)
            return Opcodes.ARETURN;
        case 'V': // void
            return Opcodes.RETURN;
        }
        throw new InstrumentationException("Unknown fieldType in getReturnInsn");
    }

    /** Mtodo que retorna o opcode certo para o tipo de varivel que se quer fazer pop na stack.
      * Opcodes disponveis:
      * - POP2 para long, double
      * - POP para todos os restantes
      **/
    public int getPopInsn() {
        if (getNumberSlots() == 1) {
            return Opcodes.POP;
        } else {
            return Opcodes.POP2;
        }
    }

    /** Mtodo que retorna o opcode certo para o tipo de varivel que se quer fazer dup na stack.
      * Opcodes disponveis:
      * - DUP2 para long, double
      * - DUP para todos os restantes
      **/
    public int getDupInsn() {
        if (getNumberSlots() == 1) {
            return Opcodes.DUP;
        } else {
            return Opcodes.DUP2;
        }
    }

    private static final String[][] primitiveTypeNames = new String[][] { { "boolean", "Z" }, { "byte", "B" },
            { "char", "C" }, { "short", "S" }, { "int", "I" }, { "long", "J" }, { "float", "F" }, { "double", "D" },
            { "void", "V" } };

    /** Devolve o nome do tipo bsico (int, long, ...).
      * til para por exemplo chamar intValue() sobre Integer, longValue() sobre Long, ...
      **/
    public String primitiveTypeName() {
        if (!isPrimitive())
            throw new InstrumentationException("Type doesn't represent a Primitive Type");

        String c = Character.valueOf(bytecodeName().charAt(0)).toString();
        for (String[] typeMapping : primitiveTypeNames) {
            if (typeMapping[1].equals(c))
                return typeMapping[0];
        }

        throw new AssertionError("This should never happen");
    }

    /** Mtodo que retorna o nmero de slots necessrios por um certo tipo na stack.
      * Basicamente, tudo ocupa 1 slot, excepto Long e Double, que ocupam 2.
      **/
    public int getNumberSlots() {
        char c = bytecodeName().charAt(0);
        switch (c) {
        case 'J': // long
        case 'D': // double
            return 2;
        default:
            return 1;
        }
    }
}