Type.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.bcel.generic;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.InvalidMethodSignatureException;
import org.apache.bcel.classfile.Utility;
import org.apache.commons.lang3.StringUtils;
/**
* Abstract super class for all possible Java types, namely basic types such as int, object types like String and array
* types, e.g. int[]
*/
public abstract class Type {
/**
* Predefined constants
*/
public static final BasicType VOID = new BasicType(Const.T_VOID);
public static final BasicType BOOLEAN = new BasicType(Const.T_BOOLEAN);
public static final BasicType INT = new BasicType(Const.T_INT);
public static final BasicType SHORT = new BasicType(Const.T_SHORT);
public static final BasicType BYTE = new BasicType(Const.T_BYTE);
public static final BasicType LONG = new BasicType(Const.T_LONG);
public static final BasicType DOUBLE = new BasicType(Const.T_DOUBLE);
public static final BasicType FLOAT = new BasicType(Const.T_FLOAT);
public static final BasicType CHAR = new BasicType(Const.T_CHAR);
public static final ObjectType OBJECT = new ObjectType("java.lang.Object");
public static final ObjectType CLASS = new ObjectType("java.lang.Class");
public static final ObjectType STRING = new ObjectType("java.lang.String");
public static final ObjectType STRINGBUFFER = new ObjectType("java.lang.StringBuffer");
public static final ObjectType THROWABLE = new ObjectType("java.lang.Throwable");
/**
* Empty array.
*/
public static final Type[] NO_ARGS = {};
public static final ReferenceType NULL = new ReferenceType() {
};
public static final Type UNKNOWN = new Type(Const.T_UNKNOWN, "<unknown object>") {
};
private static final ThreadLocal<Integer> CONSUMED_CHARS = ThreadLocal.withInitial(() -> Integer.valueOf(0));
// int consumed_chars=0; // Remember position in string, see getArgumentTypes
static int consumed(final int coded) {
return coded >> 2;
}
static int encode(final int size, final int consumed) {
return consumed << 2 | size;
}
/**
* Convert arguments of a method (signature) to an array of Type objects.
*
* @param signature signature string such as (Ljava/lang/String;)V
* @return array of argument types
*/
public static Type[] getArgumentTypes(final String signature) {
final List<Type> vec = new ArrayList<>();
int index;
try {
// Skip any type arguments to read argument declarations between '(' and ')'
index = signature.indexOf('(') + 1;
if (index <= 0) {
throw new InvalidMethodSignatureException(signature);
}
while (signature.charAt(index) != ')') {
vec.add(getType(signature.substring(index)));
// corrected concurrent private static field access
index += unwrap(CONSUMED_CHARS); // update position
}
} catch (final StringIndexOutOfBoundsException e) { // Should never occur
throw new InvalidMethodSignatureException(signature, e);
}
final Type[] types = new Type[vec.size()];
vec.toArray(types);
return types;
}
static int getArgumentTypesSize(final String signature) {
int res = 0;
int index;
try {
// Skip any type arguments to read argument declarations between '(' and ')'
index = signature.indexOf('(') + 1;
if (index <= 0) {
throw new InvalidMethodSignatureException(signature);
}
while (signature.charAt(index) != ')') {
final int coded = getTypeSize(signature.substring(index));
res += size(coded);
index += consumed(coded);
}
} catch (final StringIndexOutOfBoundsException e) { // Should never occur
throw new InvalidMethodSignatureException(signature, e);
}
return res;
}
/**
* Convert type to Java method signature, e.g. int[] f(java.lang.String x) becomes (Ljava/lang/String;)[I
*
* @param returnType what the method returns
* @param argTypes what are the argument types
* @return method signature for given type(s).
*/
public static String getMethodSignature(final Type returnType, final Type[] argTypes) {
final StringBuilder buf = new StringBuilder("(");
if (argTypes != null) {
for (final Type argType : argTypes) {
buf.append(argType.getSignature());
}
}
buf.append(')');
buf.append(returnType.getSignature());
return buf.toString();
}
/**
* Convert return value of a method (signature) to a Type object.
*
* @param signature signature string such as (Ljava/lang/String;)V
* @return return type
*/
public static Type getReturnType(final String signature) {
try {
// Read return type after ')'
final int index = signature.lastIndexOf(')') + 1;
return getType(signature.substring(index));
} catch (final StringIndexOutOfBoundsException e) { // Should never occur
throw new InvalidMethodSignatureException(signature, e);
}
}
static int getReturnTypeSize(final String signature) {
final int index = signature.lastIndexOf(')') + 1;
return size(getTypeSize(signature.substring(index)));
}
public static String getSignature(final java.lang.reflect.Method meth) {
final StringBuilder sb = new StringBuilder("(");
final Class<?>[] params = meth.getParameterTypes(); // avoid clone
for (final Class<?> param : params) {
sb.append(getType(param).getSignature());
}
sb.append(")");
sb.append(getType(meth.getReturnType()).getSignature());
return sb.toString();
}
/**
* Convert runtime {@link Class} to BCEL Type object.
*
* @param cls Java class
* @return corresponding Type object
*/
public static Type getType(final Class<?> cls) {
Objects.requireNonNull(cls, "cls");
/*
* That's an amazingly easy case, because getName() returns the signature. That's what we would have liked anyway.
*/
if (cls.isArray()) {
return getType(cls.getName());
}
if (!cls.isPrimitive()) { // "Real" class
return ObjectType.getInstance(cls.getName());
}
if (cls == Integer.TYPE) {
return INT;
}
if (cls == Void.TYPE) {
return VOID;
}
if (cls == Double.TYPE) {
return DOUBLE;
}
if (cls == Float.TYPE) {
return FLOAT;
}
if (cls == Boolean.TYPE) {
return BOOLEAN;
}
if (cls == Byte.TYPE) {
return BYTE;
}
if (cls == Short.TYPE) {
return SHORT;
}
if (cls == Long.TYPE) {
return LONG;
}
if (cls == Character.TYPE) {
return CHAR;
}
throw new IllegalStateException("Unknown primitive type " + cls);
}
/**
* Convert signature to a Type object.
*
* @param signature signature string such as Ljava/lang/String;
* @return type object
*/
public static Type getType(final String signature) throws StringIndexOutOfBoundsException {
final byte type = Utility.typeOfSignature(signature);
if (type <= Const.T_VOID) {
// corrected concurrent private static field access
wrap(CONSUMED_CHARS, 1);
return BasicType.getType(type);
}
if (type != Const.T_ARRAY) { // type == T_REFERENCE
// Utility.typeSignatureToString understands how to parse generic types.
final String parsedSignature = Utility.typeSignatureToString(signature, false);
wrap(CONSUMED_CHARS, parsedSignature.length() + 2); // "Lblabla;" 'L' and ';' are removed
return ObjectType.getInstance(Utility.pathToPackage(parsedSignature));
}
int dim = 0;
do { // Count dimensions
dim++;
} while (signature.charAt(dim) == '[');
// Recurse, but just once, if the signature is ok
final Type t = getType(signature.substring(dim));
// corrected concurrent private static field access
// consumed_chars += dim; // update counter - is replaced by
final int temp = unwrap(CONSUMED_CHARS) + dim;
wrap(CONSUMED_CHARS, temp);
return new ArrayType(t, dim);
}
/**
* Convert runtime {@code java.lang.Class[]} to BCEL Type objects.
*
* @param classes an array of runtime class objects
* @return array of corresponding Type objects
*/
public static Type[] getTypes(final Class<?>[] classes) {
final Type[] ret = new Type[classes.length];
Arrays.setAll(ret, i -> getType(classes[i]));
return ret;
}
static int getTypeSize(final String signature) throws StringIndexOutOfBoundsException {
final byte type = Utility.typeOfSignature(signature);
if (type <= Const.T_VOID) {
return encode(BasicType.getType(type).getSize(), 1);
}
if (type == Const.T_ARRAY) {
int dim = 0;
do { // Count dimensions
dim++;
} while (signature.charAt(dim) == '[');
// Recurse, but just once, if the signature is ok
final int consumed = consumed(getTypeSize(signature.substring(dim)));
return encode(1, dim + consumed);
}
final int index = signature.indexOf(';'); // Look for closing ';'
if (index < 0) {
throw new ClassFormatException("Invalid signature: " + signature);
}
return encode(1, index + 1);
}
static String internalTypeNameToSignature(final String internalTypeName) {
if (StringUtils.isEmpty(internalTypeName) || StringUtils.equalsAny(internalTypeName, Const.SHORT_TYPE_NAMES)) {
return internalTypeName;
}
switch (internalTypeName.charAt(0)) {
case '[':
return internalTypeName;
case 'L':
case 'T':
if (internalTypeName.charAt(internalTypeName.length() - 1) == ';') {
return internalTypeName;
}
return 'L' + internalTypeName + ';';
default:
return 'L' + internalTypeName + ';';
}
}
static int size(final int coded) {
return coded & 3;
}
private static int unwrap(final ThreadLocal<Integer> tl) {
return tl.get().intValue();
}
private static void wrap(final ThreadLocal<Integer> tl, final int value) {
tl.set(Integer.valueOf(value));
}
/**
* @deprecated (since 6.0) will be made private; do not access directly, use getter/setter
*/
@Deprecated
protected byte type; // TODO should be final (and private)
/**
* @deprecated (since 6.0) will be made private; do not access directly, use getter/setter
*/
@Deprecated
protected String signature; // signature for the type TODO should be private
protected Type(final byte type, final String signature) {
this.type = type;
this.signature = signature;
}
/**
* @return whether the Types are equal
*/
@Override
public boolean equals(final Object o) {
if (o instanceof Type) {
final Type t = (Type) o;
return type == t.type && signature.equals(t.signature);
}
return false;
}
public String getClassName() {
return toString();
}
/**
* @return signature for given type.
*/
public String getSignature() {
return signature;
}
/**
* @return stack size of this type (2 for long and double, 0 for void, 1 otherwise)
*/
public int getSize() {
switch (type) {
case Const.T_DOUBLE:
case Const.T_LONG:
return 2;
case Const.T_VOID:
return 0;
default:
return 1;
}
}
/**
* @return type as defined in Constants
*/
public byte getType() {
return type;
}
/**
* @return hash code of Type
*/
@Override
public int hashCode() {
return type ^ signature.hashCode();
}
/**
* boolean, short and char variable are considered as int in the stack or local variable area. Returns {@link #INT}
* for {@link #BOOLEAN}, {@link #SHORT} or {@link #CHAR}, otherwise returns the given type.
*
* @since 6.0
*/
public Type normalizeForStackOrLocal() {
if (this == BOOLEAN || this == BYTE || this == SHORT || this == CHAR) {
return INT;
}
return this;
}
/**
* @return Type string, e.g. 'int[]'
*/
@Override
public String toString() {
return equals(NULL) || type >= Const.T_UNKNOWN ? signature : Utility.signatureToString(signature, false);
}
}