org.aspectj.weaver.UnresolvedType.java Source code

Java tutorial

Introduction

Here is the source code for org.aspectj.weaver.UnresolvedType.java

Source

/* *******************************************************************
 * Copyright (c) 2002,2005 Contributors
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/epl-v10.html 
 *  
 * Contributors: 
 *     PARC     initial implementation 
 *     Andy Clement  start of generics upgrade...
 *     Adrian Colyer - overhaul
 * ******************************************************************/

package org.aspectj.weaver;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.Map;

import org.aspectj.util.GenericSignature;
import org.aspectj.util.GenericSignature.ClassSignature;
import org.aspectj.util.GenericSignatureParser;
import org.aspectj.weaver.tools.Traceable;

/**
 * A UnresolvedType represents a type to the weaver. UnresolvedTypes are resolved in some World (a type repository). When a
 * UnresolvedType is resolved it turns into a ResolvedType which may be a primitive type, or a ReferenceType. ReferenceTypes may
 * refer to simple, generic, parameterized or type-variable based reference types. A ReferenceType is backed by a delegate that
 * provides information about the type based on some repository (for example an Eclipse based delegate, a bytecode based delegate or
 * a reflection based delegate).
 * <p>
 * Every UnresolvedType has a signature, the unique key for the type in the world.
 */
public class UnresolvedType implements Traceable, TypeVariableDeclaringElement {

    // common type structures
    public static final UnresolvedType[] NONE = new UnresolvedType[0];
    public static final UnresolvedType OBJECT = forSignature("Ljava/lang/Object;");
    public static final UnresolvedType OBJECTARRAY = forSignature("[Ljava/lang/Object;");
    public static final UnresolvedType CLONEABLE = forSignature("Ljava/lang/Cloneable;");
    public static final UnresolvedType SERIALIZABLE = forSignature("Ljava/io/Serializable;");
    public static final UnresolvedType THROWABLE = forSignature("Ljava/lang/Throwable;");
    public static final UnresolvedType RUNTIME_EXCEPTION = forSignature("Ljava/lang/RuntimeException;");
    public static final UnresolvedType ERROR = forSignature("Ljava/lang/Error;");
    public static final UnresolvedType AT_INHERITED = forSignature("Ljava/lang/annotation/Inherited;");
    public static final UnresolvedType AT_RETENTION = forSignature("Ljava/lang/annotation/Retention;");
    public static final UnresolvedType ENUM = forSignature("Ljava/lang/Enum;");
    public static final UnresolvedType ANNOTATION = forSignature("Ljava/lang/annotation/Annotation;");
    public static final UnresolvedType JL_CLASS = forSignature("Ljava/lang/Class;");
    public static final UnresolvedType JAVA_LANG_CLASS_ARRAY = forSignature("[Ljava/lang/Class;");
    public static final UnresolvedType JL_STRING = forSignature("Ljava/lang/String;");
    public static final UnresolvedType JL_EXCEPTION = forSignature("Ljava/lang/Exception;");
    public static final UnresolvedType JAVA_LANG_REFLECT_METHOD = forSignature("Ljava/lang/reflect/Method;");
    public static final UnresolvedType JAVA_LANG_REFLECT_FIELD = forSignature("Ljava/lang/reflect/Field;");
    public static final UnresolvedType JAVA_LANG_REFLECT_CONSTRUCTOR = forSignature(
            "Ljava/lang/reflect/Constructor;");
    public static final UnresolvedType JAVA_LANG_ANNOTATION = forSignature("Ljava/lang/annotation/Annotation;");
    public static final UnresolvedType SUPPRESS_AJ_WARNINGS = forSignature(
            "Lorg/aspectj/lang/annotation/SuppressAjWarnings;");
    public static final UnresolvedType AT_TARGET = forSignature("Ljava/lang/annotation/Target;");
    public static final UnresolvedType SOMETHING = new UnresolvedType("?");
    public static final UnresolvedType[] ARRAY_WITH_JUST_OBJECT = new UnresolvedType[] { OBJECT };
    public static final UnresolvedType JOINPOINT_STATICPART = forSignature(
            "Lorg/aspectj/lang/JoinPoint$StaticPart;");
    public static final UnresolvedType JOINPOINT_ENCLOSINGSTATICPART = forSignature(
            "Lorg/aspectj/lang/JoinPoint$EnclosingStaticPart;");
    public static final UnresolvedType AJC_PRIVILEGED = forSignature(
            "Lorg/aspectj/internal/lang/annotation/ajcPrivileged;");
    public static final UnresolvedType PROCEEDING_JOINPOINT = forSignature(
            "Lorg/aspectj/lang/ProceedingJoinPoint;");
    public static final UnresolvedType BOOLEAN = forPrimitiveType("Z");
    public static final UnresolvedType BYTE = forPrimitiveType("B");
    public static final UnresolvedType CHAR = forPrimitiveType("C");
    public static final UnresolvedType DOUBLE = forPrimitiveType("D");
    public static final UnresolvedType FLOAT = forPrimitiveType("F");
    public static final UnresolvedType INT = forPrimitiveType("I");
    public static final UnresolvedType LONG = forPrimitiveType("J");
    public static final UnresolvedType SHORT = forPrimitiveType("S");
    public static final UnresolvedType VOID = forPrimitiveType("V");

    // A type is considered missing if we have a signature for it but cannot find the delegate
    public static final String MISSING_NAME = "@missing@";

    // OPTIMIZE I dont think you can ask something unresolved what kind of type it is, how can it always know? Push down into
    // resolvedtype that will force references to resolvedtypes to be correct rather than relying on unresolvedtypes to answer
    // questions
    protected TypeKind typeKind = TypeKind.SIMPLE; // what kind of type am I?

    protected String signature;

    /**
     * The erasure of the signature. Contains only the Java signature of the type with all supertype, superinterface, type variable,
     * and parameter information removed.
     */
    protected String signatureErasure;

    /**
     * Calculated on first request - the package name (java.lang for type java.lang.String)
     */
    private String packageName;

    /**
     * Calculated on first request - the class name (String for type java.lang.String)
     */
    private String className;

    /**
     * Iff isParameterized(), then these are the type parameters
     */
    protected UnresolvedType[] typeParameters;

    /**
     * Iff isGeneric(), then these are the type variables declared on the type Iff isParameterized(), then these are the type
     * variables bound as parameters in the type
     */
    // OPTIMIZE should be no state in here that will damage whether equals() is correct...
    protected TypeVariable[] typeVariables;

    public boolean isPrimitiveType() {
        return typeKind == TypeKind.PRIMITIVE;
    }

    public boolean isVoid() {
        // OPTIMIZE promote to bitflag?
        return signature.equals("V");
    }

    public boolean isSimpleType() {
        return typeKind == TypeKind.SIMPLE;
    }

    public boolean isRawType() {
        return typeKind == TypeKind.RAW;
    }

    public boolean isGenericType() {
        return typeKind == TypeKind.GENERIC;
    }

    public boolean isParameterizedType() {
        return typeKind == TypeKind.PARAMETERIZED;
    }

    public boolean isParameterizedOrGenericType() {
        return typeKind == TypeKind.GENERIC || typeKind == TypeKind.PARAMETERIZED;
    }

    public boolean isParameterizedOrRawType() {
        return typeKind == TypeKind.PARAMETERIZED || typeKind == TypeKind.RAW;
    }

    public boolean isTypeVariableReference() {
        return typeKind == TypeKind.TYPE_VARIABLE;
    }

    public boolean isGenericWildcard() {
        return typeKind == TypeKind.WILDCARD;
    }

    public TypeKind getTypekind() {
        return typeKind;
    }

    // for any reference type, we can get some extra information...
    public final boolean isArray() {
        return signature.length() > 0 && signature.charAt(0) == '[';
    }

    /**
     * Equality is checked based on the underlying signature.
     */
    @Override
    public boolean equals(Object other) {
        if (!(other instanceof UnresolvedType)) {
            return false;
        }
        return signature.equals(((UnresolvedType) other).signature);
    }

    /**
     * Equality is checked based on the underlying signature, so the hash code of a particular type is the hash code of its
     * signature string.
     */
    @Override
    public int hashCode() {
        return signature.hashCode();
    }

    protected UnresolvedType(String signature) {
        this.signature = signature;
        this.signatureErasure = signature;
    }

    protected UnresolvedType(String signature, String signatureErasure) {
        this.signature = signature;
        this.signatureErasure = signatureErasure;
    }

    // called from TypeFactory
    public UnresolvedType(String signature, String signatureErasure, UnresolvedType[] typeParams) {
        this.signature = signature;
        this.signatureErasure = signatureErasure;
        this.typeParameters = typeParams;
        if (typeParams != null) {
            this.typeKind = TypeKind.PARAMETERIZED;
        }
    }

    // The operations supported by an UnresolvedType are those that do not require a world

    /**
     * This is the size of this type as used in JVM.
     */
    public int getSize() {
        return size;
    }

    private int size = 1;

    /**
     * NOTE: Use forSignature() if you can, it'll be cheaper ! Constructs a UnresolvedType for a java language type name. For
     * example:
     * 
     * <blockquote>
     * 
     * <pre>
     *   UnresolvedType.forName(&quot;java.lang.Thread[]&quot;)
     *   UnresolvedType.forName(&quot;int&quot;)
     * </pre>
     * 
     * </blockquote>
     * 
     * Types may equivalently be produced by this or by {@link #forSignature(String)}.
     * 
     * <blockquote>
     * 
     * <pre>
     *   UnresolvedType.forName(&quot;java.lang.Thread[]&quot;).equals(Type.forSignature(&quot;[Ljava/lang/Thread;&quot;)
     *   UnresolvedType.forName(&quot;int&quot;).equals(Type.forSignature(&quot;I&quot;))
     * </pre>
     * 
     * </blockquote>
     * 
     * @param name the java language type name in question.
     * @return a type object representing that java language type.
     */
    // OPTIMIZE change users of this to use forSignature, especially for simple cases
    public static UnresolvedType forName(String name) {
        return forSignature(nameToSignature(name));
    }

    /**
     * Constructs a UnresolvedType for each java language type name in an incoming array.
     * 
     * @param names an array of java language type names.
     * @return an array of UnresolvedType objects.
     * @see #forName(String)
     */
    public static UnresolvedType[] forNames(String[] names) {
        UnresolvedType[] ret = new UnresolvedType[names.length];
        for (int i = 0, len = names.length; i < len; i++) {
            ret[i] = UnresolvedType.forName(names[i]);
        }
        return ret;
    }

    public static UnresolvedType forGenericType(String name, TypeVariable[] tvbs, String genericSig) {
        String sig = nameToSignature(name);
        UnresolvedType ret = UnresolvedType.forSignature(sig);
        ret.typeKind = TypeKind.GENERIC;
        ret.typeVariables = tvbs;
        ret.signatureErasure = sig;
        return ret;
    }

    public static UnresolvedType forGenericTypeSignature(String sig, String declaredGenericSig) {
        UnresolvedType ret = UnresolvedType.forSignature(sig);
        ret.typeKind = TypeKind.GENERIC;

        ClassSignature csig = new GenericSignatureParser().parseAsClassSignature(declaredGenericSig);

        GenericSignature.FormalTypeParameter[] ftps = csig.formalTypeParameters;
        ret.typeVariables = new TypeVariable[ftps.length];
        for (int i = 0; i < ftps.length; i++) {
            GenericSignature.FormalTypeParameter parameter = ftps[i];
            if (parameter.classBound instanceof GenericSignature.ClassTypeSignature) {
                GenericSignature.ClassTypeSignature cts = (GenericSignature.ClassTypeSignature) parameter.classBound;
                ret.typeVariables[i] = new TypeVariable(ftps[i].identifier,
                        UnresolvedType.forSignature(cts.outerType.identifier + ";"));
            } else if (parameter.classBound instanceof GenericSignature.TypeVariableSignature) {
                GenericSignature.TypeVariableSignature tvs = (GenericSignature.TypeVariableSignature) parameter.classBound;
                UnresolvedTypeVariableReferenceType utvrt = new UnresolvedTypeVariableReferenceType(
                        new TypeVariable(tvs.typeVariableName));
                ret.typeVariables[i] = new TypeVariable(ftps[i].identifier, utvrt);
            } else {
                throw new BCException(
                        "UnresolvedType.forGenericTypeSignature(): Do not know how to process type variable bound of type '"
                                + parameter.classBound.getClass() + "'.  Full signature is '" + sig + "'");
            }
        }
        ret.signatureErasure = sig;
        ret.signature = ret.signatureErasure;
        return ret;
    }

    public static UnresolvedType forGenericTypeVariables(String sig, TypeVariable[] tVars) {
        UnresolvedType ret = UnresolvedType.forSignature(sig);
        ret.typeKind = TypeKind.GENERIC;
        ret.typeVariables = tVars;
        ret.signatureErasure = sig;
        ret.signature = ret.signatureErasure;
        return ret;
    }

    public static UnresolvedType forRawTypeName(String name) {
        UnresolvedType ret = UnresolvedType.forName(name);
        ret.typeKind = TypeKind.RAW;
        return ret;
    }

    public static UnresolvedType forPrimitiveType(String signature) {
        UnresolvedType ret = new UnresolvedType(signature);
        ret.typeKind = TypeKind.PRIMITIVE;
        if (signature.equals("J") || signature.equals("D")) {
            ret.size = 2;
        } else if (signature.equals("V")) {
            ret.size = 0;
        }
        return ret;
    }

    /**
     * Creates a new type array with a fresh type appended to the end.
     * 
     * @param types the left hand side of the new array
     * @param end the right hand side of the new array
     */
    public static UnresolvedType[] add(UnresolvedType[] types, UnresolvedType end) {
        int len = types.length;
        UnresolvedType[] ret = new UnresolvedType[len + 1];
        System.arraycopy(types, 0, ret, 0, len);
        ret[len] = end;
        return ret;
    }

    /**
     * Creates a new type array with a fresh type inserted at the beginning.
     * 
     * 
     * @param start the left hand side of the new array
     * @param types the right hand side of the new array
     */
    public static UnresolvedType[] insert(UnresolvedType start, UnresolvedType[] types) {
        int len = types.length;
        UnresolvedType[] ret = new UnresolvedType[len + 1];
        ret[0] = start;
        System.arraycopy(types, 0, ret, 1, len);
        return ret;
    }

    /**
     * Constructs a Type for a JVM bytecode signature string. For example:
     * 
     * <blockquote>
     * 
     * <pre>
     *   UnresolvedType.forSignature(&quot;[Ljava/lang/Thread;&quot;)
     *   UnresolvedType.forSignature(&quot;I&quot;);
     * </pre>
     * 
     * </blockquote>
     * 
     * Types may equivalently be produced by this or by {@link #forName(String)}. This method should not be passed P signatures.
     * 
     * <blockquote>
     * 
     * <pre>
     *   UnresolvedType.forName(&quot;java.lang.Thread[]&quot;).equals(Type.forSignature(&quot;[Ljava/lang/Thread;&quot;)
     *   UnresolvedType.forName(&quot;int&quot;).equals(Type.forSignature(&quot;I&quot;))
     * </pre>
     * 
     * </blockquote>
     * 
     * @param signature the JVM bytecode signature string for the desired type.
     * @return a type object represnting that JVM bytecode signature.
     */
    public static UnresolvedType forSignature(String signature) {
        assert !(signature.startsWith("L") && signature.indexOf("<") != -1);
        switch (signature.charAt(0)) {
        case 'B':
            return UnresolvedType.BYTE;
        case 'C':
            return UnresolvedType.CHAR;
        case 'D':
            return UnresolvedType.DOUBLE;
        case 'F':
            return UnresolvedType.FLOAT;
        case 'I':
            return UnresolvedType.INT;
        case 'J':
            return UnresolvedType.LONG;
        case 'L':
            return TypeFactory.createTypeFromSignature(signature);
        case 'P':
            return TypeFactory.createTypeFromSignature(signature);
        case 'S':
            return UnresolvedType.SHORT;
        case 'V':
            return UnresolvedType.VOID;
        case 'Z':
            return UnresolvedType.BOOLEAN;
        case '[':
            return TypeFactory.createTypeFromSignature(signature);
        case '+':
            return TypeFactory.createTypeFromSignature(signature);
        case '-':
            return TypeFactory.createTypeFromSignature(signature);
        case '?':
            return TypeFactory.createTypeFromSignature(signature);
        case 'T':
            return TypeFactory.createTypeFromSignature(signature);
        default:
            throw new BCException("Bad type signature " + signature);
        }
    }

    /**
     * Constructs a UnresolvedType for each JVM bytecode type signature in an incoming array.
     * 
     * @param names an array of JVM bytecode type signatures
     * @return an array of UnresolvedType objects.
     * @see #forSignature(String)
     */
    public static UnresolvedType[] forSignatures(String[] sigs) {
        UnresolvedType[] ret = new UnresolvedType[sigs.length];
        for (int i = 0, len = sigs.length; i < len; i++) {
            ret[i] = UnresolvedType.forSignature(sigs[i]);
        }
        return ret;
    }

    /**
     * Returns the name of this type in java language form (e.g. java.lang.Thread or boolean[]). This produces a more aesthetically
     * pleasing string than {@link java.lang.Class#getName()}.
     * 
     * @return the java language name of this type.
     */
    public String getName() {
        return signatureToName(signature);
    }

    public String getSimpleName() {
        String name = getRawName();
        int lastDot = name.lastIndexOf('.');
        if (lastDot != -1) {
            name = name.substring(lastDot + 1);
        }
        if (isParameterizedType()) {
            StringBuffer sb = new StringBuffer(name);
            sb.append("<");
            for (int i = 0; i < (typeParameters.length - 1); i++) {
                sb.append(typeParameters[i].getSimpleName());
                sb.append(",");
            }
            sb.append(typeParameters[typeParameters.length - 1].getSimpleName());
            sb.append(">");
            name = sb.toString();
        }
        return name;
    }

    public String getRawName() {
        return signatureToName((signatureErasure == null ? signature : signatureErasure));
    }

    public String getBaseName() {
        String name = getName();
        if (isParameterizedType() || isGenericType()) {
            if (typeParameters == null) {
                return name;
            } else {
                return name.substring(0, name.indexOf("<"));
            }
        } else {
            return name;
        }
    }

    public String getSimpleBaseName() {
        String name = getBaseName();
        int lastDot = name.lastIndexOf('.');
        if (lastDot != -1) {
            name = name.substring(lastDot + 1);
        }
        return name;
    }

    /**
     * Returns an array of strings representing the java langauge names of an array of types.
     * 
     * @param types an array of UnresolvedType objects
     * @return an array of Strings fo the java language names of types.
     * @see #getName()
     */
    public static String[] getNames(UnresolvedType[] types) {
        String[] ret = new String[types.length];
        for (int i = 0, len = types.length; i < len; i++) {
            ret[i] = types[i].getName();
        }
        return ret;
    }

    /**
     * Returns the name of this type in JVM signature form. For all UnresolvedType t:
     * 
     * <blockquote>
     * 
     * <pre>
     * UnresolvedType.forSignature(t.getSignature()).equals(t)
     * </pre>
     * 
     * </blockquote>
     * 
     * and for all String s where s is a lexically valid JVM type signature string:
     * 
     * <blockquote>
     * 
     * <pre>
     * UnresolvedType.forSignature(s).getSignature().equals(s)
     * </pre>
     * 
     * </blockquote>
     * 
     * @return the java JVM signature string for this type.
     */
    public String getSignature() {
        return signature;
    }

    /**
     * For parameterized types, return the signature for the raw type
     */
    public String getErasureSignature() {
        if (signatureErasure == null) {
            return signature;
        }
        return signatureErasure;
    }

    private boolean needsModifiableDelegate = false;

    public boolean needsModifiableDelegate() {
        return needsModifiableDelegate;
    }

    public void setNeedsModifiableDelegate(boolean b) {
        this.needsModifiableDelegate = b;
    }

    public UnresolvedType getRawType() {
        return UnresolvedType.forSignature(getErasureSignature());
    }

    /**
     * Returns a UnresolvedType object representing the effective outermost enclosing type for a name type. For all other types,
     * this will return the type itself.
     * 
     * The only guarantee is given in JLS 13.1 where code generated according to those rules will have type names that can be split
     * apart in this way.
     * 
     * @return the outermost enclosing UnresolvedType object or this.
     */
    public UnresolvedType getOutermostType() {
        if (isArray() || isPrimitiveType()) {
            return this;
        }
        String sig = getErasureSignature();
        int dollar = sig.indexOf('$');
        if (dollar != -1) {
            return UnresolvedType.forSignature(sig.substring(0, dollar) + ';');
        } else {
            return this;
        }
    }

    /**
     * Returns a UnresolvedType object representing the component type of this array, or null if this type does not represent an
     * array type.
     * 
     * @return the component UnresolvedType object, or null.
     */
    public UnresolvedType getComponentType() {
        if (isArray()) {
            return forSignature(signature.substring(1));
        } else {
            return null;
        }
    }

    /**
     * Returns a java language string representation of this type.
     */
    @Override
    public String toString() {
        return getName(); // + " - " + getKind();
    }

    public String toDebugString() {
        return getName();
    }

    // ---- requires worlds

    /**
     * Returns a resolved version of this type according to a particular world.
     * 
     * @param world the {@link World} within which to resolve.
     * @return a resolved type representing this type in the appropriate world.
     */
    public ResolvedType resolve(World world) {
        return world.resolve(this);
    }

    // ---- helpers

    private static String signatureToName(String signature) {
        switch (signature.charAt(0)) {
        case 'B':
            return "byte";
        case 'C':
            return "char";
        case 'D':
            return "double";
        case 'F':
            return "float";
        case 'I':
            return "int";
        case 'J':
            return "long";
        case 'L':
            String name = signature.substring(1, signature.length() - 1).replace('/', '.');
            return name;
        case 'T':
            StringBuffer nameBuff2 = new StringBuffer();
            int colon = signature.indexOf(";");
            String tvarName = signature.substring(1, colon);
            nameBuff2.append(tvarName);
            return nameBuff2.toString();
        case 'P': // it's one of our parameterized type sigs
            StringBuffer nameBuff = new StringBuffer();
            // signature for parameterized types is e.g.
            // List<String> -> Ljava/util/List<Ljava/lang/String;>;
            // Map<String,List<Integer>> -> Ljava/util/Map<java/lang/String;Ljava/util/List<Ljava/lang/Integer;>;>;
            int paramNestLevel = 0;
            for (int i = 1; i < signature.length(); i++) {
                char c = signature.charAt(i);
                switch (c) {
                case '/':
                    nameBuff.append('.');
                    break;
                case '<':
                    nameBuff.append("<");
                    paramNestLevel++;
                    StringBuffer innerBuff = new StringBuffer();
                    while (paramNestLevel > 0) {
                        c = signature.charAt(++i);
                        if (c == '<') {
                            paramNestLevel++;
                        }
                        if (c == '>') {
                            paramNestLevel--;
                        }
                        if (paramNestLevel > 0) {
                            innerBuff.append(c);
                        }
                        if (c == ';' && paramNestLevel == 1) {
                            nameBuff.append(signatureToName(innerBuff.toString()));
                            if (signature.charAt(i + 1) != '>') {
                                nameBuff.append(',');
                            }
                            innerBuff = new StringBuffer();
                        }
                    }
                    nameBuff.append(">");
                    break;
                case ';':
                    break;
                default:
                    nameBuff.append(c);
                }
            }
            return nameBuff.toString();
        case 'S':
            return "short";
        case 'V':
            return "void";
        case 'Z':
            return "boolean";
        case '[':
            return signatureToName(signature.substring(1, signature.length())) + "[]";
        // case '<':
        // // its a generic!
        // if (signature.charAt(1)=='>') return signatureToName(signature.substring(2));
        case '+':
            return "? extends " + signatureToName(signature.substring(1, signature.length()));
        case '-':
            return "? super " + signatureToName(signature.substring(1, signature.length()));
        case '*':
            return "?";
        default:
            throw new BCException("Bad type signature: " + signature);
        }
    }

    private static String nameToSignature(String name) {
        int len = name.length();
        if (len < 8) {
            if (name.equals("int")) {
                return "I";
            }
            if (name.equals("void")) {
                return "V";
            }
            if (name.equals("long")) {
                return "J";
            }
            if (name.equals("boolean")) {
                return "Z";
            }
            if (name.equals("double")) {
                return "D";
            }
            if (name.equals("float")) {
                return "F";
            }
            if (name.equals("byte")) {
                return "B";
            }
            if (name.equals("short")) {
                return "S";
            }
            if (name.equals("char")) {
                return "C";
            }
            if (name.equals("?")) {
                return name;
            }
        }
        if (len == 0) {
            throw new BCException("Bad type name: " + name);
        }
        if (name.endsWith("[]")) {
            return "[" + nameToSignature(name.substring(0, name.length() - 2));
        }

        // Sometimes the 'name' for an array is of the form: [Ljava.lang.String;
        if (name.charAt(0) == '[') {
            return name.replace('.', '/');
        }

        if (name.indexOf("<") == -1) {
            // not parameterized
            return new StringBuilder("L").append(name.replace('.', '/')).append(';').toString();
        } else {
            StringBuffer nameBuff = new StringBuffer();
            int nestLevel = 0;
            nameBuff.append("P");
            for (int i = 0; i < len; i++) {
                char c = name.charAt(i);
                switch (c) {
                case '.':
                    nameBuff.append('/');
                    break;
                case '<':
                    nameBuff.append("<");
                    nestLevel++;
                    StringBuffer innerBuff = new StringBuffer();
                    while (nestLevel > 0) {
                        c = name.charAt(++i);
                        if (c == '<') {
                            nestLevel++;
                        } else if (c == '>') {
                            nestLevel--;
                        }
                        if (c == ',' && nestLevel == 1) {
                            nameBuff.append(nameToSignature(innerBuff.toString()));
                            innerBuff = new StringBuffer();
                        } else {
                            if (nestLevel > 0) {
                                innerBuff.append(c);
                            }
                        }
                    }
                    nameBuff.append(nameToSignature(innerBuff.toString()));
                    nameBuff.append('>');
                    break;
                //            case '>':
                //               throw new IllegalStateException("Should by matched by <");
                //            case ',':
                //               throw new IllegalStateException("Should only happen inside <...>");
                default:
                    nameBuff.append(c);
                }
            }
            nameBuff.append(";");
            return nameBuff.toString();
        }
    }

    /**
     * Write out an UnresolvedType - the signature should be enough.
     */
    public final void write(CompressingDataOutputStream s) throws IOException {
        s.writeUTF(getSignature());
    }

    /**
     * Read in an UnresolvedType - just read the signature and rebuild the UnresolvedType.
     */
    public static UnresolvedType read(DataInputStream s) throws IOException {
        String sig = s.readUTF();
        if (sig.equals(MISSING_NAME)) {
            return ResolvedType.MISSING;
        } else {
            // TODO isn't it a shame to build these (this method is expensive) and then chuck them away on resolution?
            // TODO review callers and see if they are immediately resolving it, maybe we can do something more optimal if they are
            return UnresolvedType.forSignature(sig);
        }
    }

    public String getNameAsIdentifier() {
        return getName().replace('.', '_');
    }

    public String getPackageNameAsIdentifier() {
        String name = getName();
        int index = name.lastIndexOf('.');
        if (index == -1) {
            return "";
        } else {
            return name.substring(0, index).replace('.', '_');
        }
    }

    public UnresolvedType[] getTypeParameters() {
        return typeParameters == null ? UnresolvedType.NONE : typeParameters;
    }

    public TypeVariable[] getTypeVariables() {
        return typeVariables;
    }

    public static class TypeKind {
        // Note: It is not sufficient to say that a parameterized type with no type parameters in fact
        // represents a raw type - a parameterized type with no type parameters can represent
        // an inner type of a parameterized type that specifies no type parameters of its own.
        public final static TypeKind PRIMITIVE = new TypeKind("primitive");
        public final static TypeKind SIMPLE = new TypeKind("simple"); // a type with NO type parameters/vars
        public final static TypeKind RAW = new TypeKind("raw"); // the erasure of a generic type
        public final static TypeKind GENERIC = new TypeKind("generic"); // a generic type
        public final static TypeKind PARAMETERIZED = new TypeKind("parameterized"); // a parameterized type
        public final static TypeKind TYPE_VARIABLE = new TypeKind("type_variable"); // a type variable
        public final static TypeKind WILDCARD = new TypeKind("wildcard"); // a generic wildcard type

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

        private TypeKind(String type) {
            this.type = type;
        }

        private final String type;
    }

    @Override
    public TypeVariable getTypeVariableNamed(String name) {
        TypeVariable[] vars = getTypeVariables();
        if (vars == null || vars.length == 0) {
            return null;
        }
        for (int i = 0; i < vars.length; i++) {
            TypeVariable aVar = vars[i];
            if (aVar.getName().equals(name)) {
                return aVar;
            }
        }
        return null;
    }

    @Override
    public String toTraceString() {
        return getClass().getName() + "[" + getName() + "]";
    }

    /**
     * Return a version of this parameterized type in which any type parameters that are type variable references are replaced by
     * their matching type variable binding.
     */
    // OPTIMIZE methods like this just allow callers to be lazy and not ensure they are working with the right (resolved) subtype
    public UnresolvedType parameterize(Map<String, UnresolvedType> typeBindings) {
        throw new UnsupportedOperationException("unable to parameterize unresolved type: " + signature);
    }

    /**
     * @return the class name (does not include the package name)
     */
    public String getClassName() {
        if (className == null) {
            String name = getName();
            if (name.indexOf("<") != -1) {
                name = name.substring(0, name.indexOf("<"));
            }
            int index = name.lastIndexOf('.');
            if (index == -1) {
                className = name;
            } else {
                className = name.substring(index + 1);
            }
        }
        return className;
    }

    /**
     * @return the package name (no class name included)
     */
    public String getPackageName() {
        if (packageName == null) {
            String name = getName();
            int angly = name.indexOf('<');
            if (angly != -1) {
                name = name.substring(0, angly);
            }
            int index = name.lastIndexOf('.');
            if (index == -1) {
                packageName = "";
            } else {
                packageName = name.substring(0, index);
            }
        }
        return packageName;
    }

    public static void writeArray(UnresolvedType[] types, CompressingDataOutputStream stream) throws IOException {
        int len = types.length;
        stream.writeShort(len);
        for (UnresolvedType type : types) {
            type.write(stream);
        }
    }

    public static UnresolvedType[] readArray(DataInputStream s) throws IOException {
        int len = s.readShort();
        if (len == 0) {
            return UnresolvedType.NONE;
        }
        UnresolvedType[] types = new UnresolvedType[len];
        for (int i = 0; i < len; i++) {
            types[i] = UnresolvedType.read(s);
        }
        return types;
    }

    public static UnresolvedType makeArray(UnresolvedType base, int dims) {
        StringBuffer sig = new StringBuffer();
        for (int i = 0; i < dims; i++) {
            sig.append("[");
        }
        sig.append(base.getSignature());
        return UnresolvedType.forSignature(sig.toString());
    }
}