org.codehaus.janino.IClass Maven / Gradle / Ivy
Show all versions of janino Show documentation
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2001-2010 Arno Unkrig. All rights reserved.
* Copyright (c) 2015-2016 TIBCO Software Inc. 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 holder 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 HOLDER 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.codehaus.janino;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.nullanalysis.Nullable;
/**
* A simplified equivalent to "java.lang.reflect".
*
* 'JLS7' means a reference to the Java Language Specification, Java
* SE 7 Edition.
*
*/
public abstract
class IClass {
private static final Logger LOGGER = Logger.getLogger(IClass.class.getName());
/**
* Special return value for {@link IField#getConstantValue()} indicating that the field does not have a
* constant value.
*/
public static final Object NOT_CONSTANT = new Object() {
@Override public String toString() { return "NOT_CONSTANT"; }
};
/**
* The {@link IClass} object for the type VOID.
*/
public static final IClass VOID = new PrimitiveIClass(Descriptor.VOID);
/**
* The {@link IClass} object for the primitive type BYTE.
*/
public static final IClass BYTE = new PrimitiveIClass(Descriptor.BYTE);
/**
* The {@link IClass} object for the primitive type CHAR.
*/
public static final IClass CHAR = new PrimitiveIClass(Descriptor.CHAR);
/**
* The {@link IClass} object for the primitive type DOUBLE.
*/
public static final IClass DOUBLE = new PrimitiveIClass(Descriptor.DOUBLE);
/**
* The {@link IClass} object for the primitive type FLOAT.
*/
public static final IClass FLOAT = new PrimitiveIClass(Descriptor.FLOAT);
/**
* The {@link IClass} object for the primitive type INT.
*/
public static final IClass INT = new PrimitiveIClass(Descriptor.INT);
/**
* The {@link IClass} object for the primitive type LONG.
*/
public static final IClass LONG = new PrimitiveIClass(Descriptor.LONG);
/**
* The {@link IClass} object for the primitive type SHORT.
*/
public static final IClass SHORT = new PrimitiveIClass(Descriptor.SHORT);
/**
* The {@link IClass} object for the primitive type BOOLEAN.
*/
public static final IClass BOOLEAN = new PrimitiveIClass(Descriptor.BOOLEAN);
private static
class PrimitiveIClass extends IClass {
private final String fieldDescriptor;
PrimitiveIClass(String fieldDescriptor) { this.fieldDescriptor = fieldDescriptor; }
@Override @Nullable protected IClass getComponentType2() { return null; } // SUPPRESS CHECKSTYLE LineLength:17
@Override protected IClass[] getDeclaredIClasses2() { return new IClass[0]; }
@Override protected IConstructor[] getDeclaredIConstructors2() { return new IConstructor[0]; }
@Override protected IField[] getDeclaredIFields2() { return new IField[0]; }
@Override protected IMethod[] getDeclaredIMethods2() { return new IMethod[0]; }
@Override @Nullable protected IClass getDeclaringIClass2() { return null; }
@Override protected String getDescriptor2() { return this.fieldDescriptor; }
@Override protected IClass[] getInterfaces2() { return new IClass[0]; }
@Override @Nullable protected IClass getOuterIClass2() { return null; }
@Override @Nullable protected IClass getSuperclass2() { return null; }
@Override public boolean isAbstract() { return false; }
@Override public boolean isArray() { return false; }
@Override public boolean isFinal() { return true; }
@Override public boolean isEnum() { return false; }
@Override public boolean isInterface() { return false; }
@Override public boolean isPrimitive() { return true; }
@Override public boolean isPrimitiveNumeric() { return Descriptor.isPrimitiveNumeric(this.fieldDescriptor); }
@Override public Access getAccess() { return Access.PUBLIC; }
}
/**
* Returns all the constructors declared by the class represented by the type. If the class has a default
* constructor, it is included.
*
* Returns an array with zero elements for an interface, array, primitive type or {@code void}.
*
*/
public final IConstructor[]
getDeclaredIConstructors() {
if (this.declaredIConstructorsCache != null) return this.declaredIConstructorsCache;
return (this.declaredIConstructorsCache = this.getDeclaredIConstructors2());
}
@Nullable private IConstructor[] declaredIConstructorsCache;
/**
* The uncached version of {@link #getDeclaredIConstructors()} which must be implemented by derived classes.
*/
protected abstract IConstructor[] getDeclaredIConstructors2();
/**
* Returns the methods of the class or interface (but not inherited methods). For covariant methods, only the
* method with the most derived return type is included.
*
* Returns an empty array for an array, primitive type or {@code void}.
*
*/
public final IMethod[]
getDeclaredIMethods() {
if (this.declaredIMethodsCache != null) return this.declaredIMethodsCache;
return (this.declaredIMethodsCache = this.getDeclaredIMethods2());
}
@Nullable private IMethod[] declaredIMethodsCache;
/**
* The uncached version of {@link #getDeclaredIMethods()} which must be implemented by derived classes.
*/
protected abstract IMethod[] getDeclaredIMethods2();
/**
* Returns all methods with the given name declared in the class or interface (but not inherited methods).
*
* Returns an empty array if no methods with that name are declared.
*
*
* @return an array of {@link IMethod}s that must not be modified
*/
public final IMethod[]
getDeclaredIMethods(String methodName) {
Map dimc = this.declaredIMethodCache;
if (dimc == null) {
IMethod[] dims = this.getDeclaredIMethods();
// Fill the map with "IMethod"s and "List"s.
dimc = new HashMap();
for (IMethod dim : dims) {
String mn = dim.getName();
Object o = dimc.get(mn);
if (o == null) {
dimc.put(mn, dim);
} else
if (o instanceof IMethod) {
List l = new ArrayList();
l.add((IMethod) o);
l.add(dim);
dimc.put(mn, l);
} else {
@SuppressWarnings("unchecked") List tmp = (List) o;
tmp.add(dim);
}
}
// Convert "IMethod"s and "List"s to "IMethod[]"s.
for (Map.Entry*/> me : dimc.entrySet()) {
Object v = me.getValue();
if (v instanceof IMethod) {
me.setValue(new IMethod[] { (IMethod) v });
} else {
@SuppressWarnings("unchecked") List l = (List) v;
me.setValue(l.toArray(new IMethod[l.size()]));
}
}
this.declaredIMethodCache = dimc;
}
IMethod[] methods = (IMethod[]) dimc.get(methodName);
return methods == null ? IClass.NO_IMETHODS : methods;
}
@Nullable private Map*/> declaredIMethodCache;
/**
* Returns all methods declared in the class or interface, its superclasses and its superinterfaces.
*
* @return an array of {@link IMethod}s that must not be modified
*/
public final IMethod[]
getIMethods() throws CompileException {
if (this.iMethodCache != null) return this.iMethodCache;
List iMethods = new ArrayList();
this.getIMethods(iMethods);
return (this.iMethodCache = (IMethod[]) iMethods.toArray(new IMethod[iMethods.size()]));
}
@Nullable private IMethod[] iMethodCache;
private void
getIMethods(List result) throws CompileException {
IMethod[] ms = this.getDeclaredIMethods();
SCAN_DECLARED_METHODS:
for (IMethod candidate : ms) {
MethodDescriptor candidateDescriptor = candidate.getDescriptor();
String candidateName = candidate.getName();
// Check if a method with the same name and descriptor has been added before.
for (IMethod oldMethod : result) {
if (
candidateName.equals(oldMethod.getName())
&& candidateDescriptor.equals(oldMethod.getDescriptor())
) continue SCAN_DECLARED_METHODS;
}
result.add(candidate);
}
IClass sc = this.getSuperclass();
if (sc != null) sc.getIMethods(result);
for (IClass ii : this.getInterfaces()) ii.getIMethods(result);
}
private static final IMethod[] NO_IMETHODS = new IMethod[0];
/**
* @return Whether this {@link IClass} (or its superclass or the interfaces it implements) has an {@link IMethod}
* with the given name and parameter types
*/
public final boolean
hasIMethod(String methodName, IClass[] parameterTypes) throws CompileException {
return this.findIMethod(methodName, parameterTypes) != null;
}
/**
* @return The {@link IMethod} declared in this {@link IClass} (or its superclass or the interfaces it implements)
* with the given name and parameter types, or {@code null} if an applicable method could not be found
*/
@Nullable public final IMethod
findIMethod(String methodName, IClass[] parameterTypes) throws CompileException {
{
IMethod result = null;
for (IMethod im : this.getDeclaredIMethods(methodName)) {
if (
Arrays.equals(im.getParameterTypes(), parameterTypes)
&& (result == null || result.getReturnType().isAssignableFrom(im.getReturnType()))
) result = im;
}
if (result != null) return result;
}
{
IClass superclass = this.getSuperclass();
if (superclass != null) {
IMethod result = superclass.findIMethod(methodName, parameterTypes);
if (result != null) return result;
}
}
{
IClass[] interfaces = this.getInterfaces();
for (IClass interfacE : interfaces) {
IMethod result = interfacE.findIMethod(methodName, parameterTypes);
if (result != null) return result;
}
}
return null;
}
/**
* @return The {@link IConstructor} declared in this {@link IClass} with the given parameter types, or {@code null}
* if an applicable constructor could not be found
*/
@Nullable public final IConstructor
findIConstructor(IClass[] parameterTypes) throws CompileException {
IConstructor[] ics = this.getDeclaredIConstructors();
for (IConstructor ic : ics) {
if (Arrays.equals(ic.getParameterTypes(), parameterTypes)) return ic;
}
return null;
}
/**
* Returns the {@link IField}s declared in this {@link IClass} (but not inherited fields).
*
* @return An empty array for an array, primitive type or {@code void}
*/
public final IField[]
getDeclaredIFields() {
Collection allFields = this.getDeclaredIFieldsCache().values();
return (IField[]) allFields.toArray(new IField[allFields.size()]);
}
/**
* @return {@code String fieldName => IField}
*/
private Map
getDeclaredIFieldsCache() {
if (this.declaredIFieldsCache != null) return this.declaredIFieldsCache;
IField[] fields = this.getDeclaredIFields2();
Map m = new LinkedHashMap();
for (IField f : fields) m.put(f.getName(), f);
return (this.declaredIFieldsCache = m);
}
/**
* Returns the named {@link IField} declared in this {@link IClass} (does not work for inherited fields).
*
* @return {@code null} iff this {@link IClass} does not declare an {@link IField} with that name
*/
@Nullable public final IField
getDeclaredIField(String name) { return (IField) this.getDeclaredIFieldsCache().get(name); }
/**
* Clears the cache of declared fields which this class maintains in order to minimize the invocations of {@link
* #getDeclaredIFields2()}.
*/
protected void
clearIFieldCaches() { this.declaredIFieldsCache = null; }
@Nullable private Map declaredIFieldsCache;
/**
* Uncached version of {@link #getDeclaredIFields()}.
*/
protected abstract IField[] getDeclaredIFields2();
/**
* @return The synthetic fields of an anonymous or local class, in the order in which they are passed to all
* constructors
*/
public IField[]
getSyntheticIFields() { return new IField[0]; }
/**
* Returns the classes and interfaces declared as members of the class (but not inherited classes and interfaces).
*
* Returns an empty array for an array, primitive type or {@code void}.
*
*/
public final IClass[]
getDeclaredIClasses() throws CompileException {
if (this.declaredIClassesCache != null) return this.declaredIClassesCache;
return (this.declaredIClassesCache = this.getDeclaredIClasses2());
}
@Nullable private IClass[] declaredIClassesCache;
/**
* @return The member types of this type
*/
protected abstract IClass[] getDeclaredIClasses2() throws CompileException;
/**
* @return If this class is a member class, the declaring class, otherwise {@code null}
*/
@Nullable public final IClass
getDeclaringIClass() throws CompileException {
if (!this.declaringIClassIsCached) {
this.declaringIClassCache = this.getDeclaringIClass2();
this.declaringIClassIsCached = true;
}
return this.declaringIClassCache;
}
private boolean declaringIClassIsCached;
@Nullable private IClass declaringIClassCache;
/**
* @return If this class is a member class, the declaring class, otherwise {@code null}
*/
@Nullable protected abstract IClass
getDeclaringIClass2() throws CompileException;
/**
* The following types have an "outer class":
*
* - Anonymous classes declared in a non-static method of a class
*
- Local classes declared in a non-static method of a class
*
- Non-static member classes
*
*
* @return The outer class of this type, or {@code null}
*/
@Nullable public final IClass
getOuterIClass() throws CompileException {
if (this.outerIClassIsCached) return this.outerIClassCache;
this.outerIClassIsCached = true;
return (this.outerIClassCache = this.getOuterIClass2());
}
private boolean outerIClassIsCached;
@Nullable private IClass outerIClassCache;
/**
* @see #getOuterIClass()
*/
@Nullable protected abstract IClass
getOuterIClass2() throws CompileException;
/**
* Returns the superclass of the class.
*
* Returns {@code null} for class {@link Object}, interfaces, arrays, primitive types and {@code void}.
*
*/
@Nullable public final IClass
getSuperclass() throws CompileException {
if (this.superclassIsCached) return this.superclassCache;
IClass sc = this.getSuperclass2();
if (sc != null && sc.isSubclassOf(this)) {
throw new CompileException(
"Class circularity detected for \"" + Descriptor.toClassName(this.getDescriptor()) + "\"",
null
);
}
this.superclassIsCached = true;
return (this.superclassCache = sc);
}
private boolean superclassIsCached;
@Nullable private IClass superclassCache;
/**
* @see #getSuperclass()
*/
@Nullable protected abstract IClass
getSuperclass2() throws CompileException;
/**
* @return The accessibility of this type
*/
public abstract Access getAccess();
/**
* Whether subclassing is allowed (JVMS 4.1 access_flags)
*
* @return {@code true} if subclassing is prohibited
*/
public abstract boolean isFinal();
/**
* Returns the interfaces implemented by the class, respectively the superinterfaces of the interface, respectively
* {
{@link Cloneable}{@code ,} {@link Serializable} }
for arrays.
*
* Returns an empty array for primitive types and {@code void}.
*
*/
public final IClass[]
getInterfaces() throws CompileException {
if (this.interfacesCache != null) return this.interfacesCache;
IClass[] is = this.getInterfaces2();
for (IClass ii : is) {
if (ii.implementsInterface(this)) {
throw new CompileException(
"Interface circularity detected for \"" + Descriptor.toClassName(this.getDescriptor()) + "\"",
null
);
}
}
return (this.interfacesCache = is);
}
@Nullable private IClass[] interfacesCache;
/**
* @see #getInterfaces()
*/
protected abstract IClass[] getInterfaces2() throws CompileException;
/**
* Whether the class may be instantiated (JVMS 4.1 access_flags).
*
* @return {@code true} if instantiation is prohibited
*/
public abstract boolean isAbstract();
/**
* Returns the field descriptor for the type as defined by JVMS 4.3.2. This method is fast.
*/
public final String
getDescriptor() {
if (this.descriptorCache != null) return this.descriptorCache;
return (this.descriptorCache = this.getDescriptor2());
}
@Nullable private String descriptorCache;
/**
* @return The field descriptor for the type as defined by JVMS 4.3.2.
*/
protected abstract String getDescriptor2();
/**
* Convenience method that determines the field descriptors of an array of {@link IClass}es.
*
* @see #getDescriptor()
*/
public static String[]
getDescriptors(IClass[] iClasses) {
String[] descriptors = new String[iClasses.length];
for (int i = 0; i < iClasses.length; ++i) descriptors[i] = iClasses[i].getDescriptor();
return descriptors;
}
/**
* @return Whether this type represents an enum
*/
public abstract boolean isEnum();
/**
* @return Whether this type represents an interface
*/
public abstract boolean isInterface();
/**
* @return Whether this type represents an array
*/
public abstract boolean isArray();
/**
* @return Whether this type represents a primitive type or {@code void}
*/
public abstract boolean isPrimitive();
/**
* @return Whether this type represents {@code byte}, {@code short}, {@code int}, {@code long}, {@code char},
* {@code float} or {@code double}
*/
public abstract boolean isPrimitiveNumeric();
/**
* @return The component type of the array, or {@code null} for classes, interfaces, primitive types and {@code
* void}
*/
@Nullable public final IClass
getComponentType() {
if (this.componentTypeIsCached) return this.componentTypeCache;
this.componentTypeCache = this.getComponentType2();
this.componentTypeIsCached = true;
return this.componentTypeCache;
}
private boolean componentTypeIsCached;
@Nullable private IClass componentTypeCache;
/**
* @see #getComponentType()
*/
@Nullable protected abstract IClass
getComponentType2();
@Override public String
toString() {
String className = Descriptor.toClassName(this.getDescriptor());
if (className.startsWith("java.lang.")) className = className.substring(10);
return className;
}
/**
* Determines if {@code this} is assignable from that. This is true if {@code this} is identical with
* that (JLS7 5.1.1), or if that is widening-primitive-convertible to {@code this} (JLS7
* 5.1.2), or if that is widening-reference-convertible to {@code this} (JLS7 5.1.5).
*/
public boolean
isAssignableFrom(IClass that) throws CompileException {
// Identity conversion, JLS7 5.1.1
if (this == that) return true;
// Widening primitive conversion, JLS7 5.1.2
{
String ds = that.getDescriptor() + this.getDescriptor();
if (ds.length() == 2 && IClass.PRIMITIVE_WIDENING_CONVERSIONS.contains(ds)) return true;
}
// Widening reference conversion, JLS7 5.1.5
{
// JLS7 5.1.4.1: Target type is superclass of source class type.
if (that.isSubclassOf(this)) return true;
// JLS7 5.1.4.2: Source class type implements target interface type.
// JLS7 5.1.4.4: Source interface type implements target interface type.
if (that.implementsInterface(this)) return true;
// JLS7 5.1.4.3 Convert "null" literal to any reference type.
if (that == IClass.VOID && !this.isPrimitive()) return true;
// JLS7 5.1.4.5: From any interface to type "Object".
if (that.isInterface() && this.getDescriptor().equals(Descriptor.JAVA_LANG_OBJECT)) return true;
if (that.isArray()) {
// JLS7 5.1.4.6: From any array type to type "Object".
if (this.getDescriptor().equals(Descriptor.JAVA_LANG_OBJECT)) return true;
// JLS7 5.1.4.7: From any array type to type "Cloneable".
if (this.getDescriptor().equals(Descriptor.JAVA_LANG_CLONEABLE)) return true;
// JLS7 5.1.4.8: From any array type to type "java.io.Serializable".
if (this.getDescriptor().equals(Descriptor.JAVA_IO_SERIALIZABLE)) return true;
// JLS7 5.1.4.9: From SC[] to TC[] while SC if widening reference convertible to TC.
if (this.isArray()) {
IClass thisCt = this.getComponentType();
IClass thatCt = that.getComponentType();
assert thisCt != null;
assert thatCt != null;
if (!thisCt.isPrimitive() && thisCt.isAssignableFrom(thatCt)) return true;
}
}
}
return false;
}
private static final Set PRIMITIVE_WIDENING_CONVERSIONS = new HashSet();
static {
String[] pwcs = {
Descriptor.BYTE + Descriptor.SHORT,
Descriptor.BYTE + Descriptor.INT,
Descriptor.SHORT + Descriptor.INT,
Descriptor.CHAR + Descriptor.INT,
Descriptor.BYTE + Descriptor.LONG,
Descriptor.SHORT + Descriptor.LONG,
Descriptor.CHAR + Descriptor.LONG,
Descriptor.INT + Descriptor.LONG,
Descriptor.BYTE + Descriptor.FLOAT,
Descriptor.SHORT + Descriptor.FLOAT,
Descriptor.CHAR + Descriptor.FLOAT,
Descriptor.INT + Descriptor.FLOAT,
Descriptor.LONG + Descriptor.FLOAT,
Descriptor.BYTE + Descriptor.DOUBLE,
Descriptor.SHORT + Descriptor.DOUBLE,
Descriptor.CHAR + Descriptor.DOUBLE,
Descriptor.INT + Descriptor.DOUBLE,
Descriptor.LONG + Descriptor.DOUBLE,
Descriptor.FLOAT + Descriptor.DOUBLE,
};
for (String pwc : pwcs) IClass.PRIMITIVE_WIDENING_CONVERSIONS.add(pwc);
}
/**
* Returns {@code true} if this class is an immediate or non-immediate subclass of {@code that} class.
*/
public boolean
isSubclassOf(IClass that) throws CompileException {
for (IClass sc = this.getSuperclass(); sc != null; sc = sc.getSuperclass()) {
if (sc == that) return true;
}
return false;
}
/**
* If {@code this} represents a class: Return {@code true} if this class directly or indirectly implements {@code
* that} interface.
*
* If {@code this} represents an interface: Return {@code true} if this interface directly or indirectly extends
* {@code that} interface.
*
*/
public boolean
implementsInterface(IClass that) throws CompileException {
for (IClass c = this; c != null; c = c.getSuperclass()) {
IClass[] tis = c.getInterfaces();
for (IClass ti : tis) {
if (ti == that || ti.implementsInterface(that)) return true;
}
}
return false;
}
/**
* Gets an {@link IClass} that represents an n-dimensional array of this type.
*
* @param n dimension count
* @param objectType Required because the superclass of an array class is {@link Object} by definition
*/
public IClass
getArrayIClass(int n, IClass objectType) {
IClass result = this;
for (int i = 0; i < n; ++i) result = result.getArrayIClass(objectType);
return result;
}
/**
* Gets an {@link IClass} that represents an array of this type.
*
* @param objectType Required because the superclass of an array class is {@link Object} by definition
*/
public synchronized IClass
getArrayIClass(IClass objectType) {
if (this.arrayIClass != null) return this.arrayIClass;
return (this.arrayIClass = this.getArrayIClass2(objectType));
}
@Nullable private IClass arrayIClass;
/**
* @param objectType Must pass {@link IClassLoader#TYPE_java_lang_Object} here
*/
private IClass
getArrayIClass2(final IClass objectType) {
final IClass componentType = this;
return new IClass() {
@Override public IClass.IConstructor[] getDeclaredIConstructors2() { return new IClass.IConstructor[0]; }
// Special trickery #17: Arrays override "Object.clone()", but without "throws
// CloneNotSupportedException"!
@Override public IClass.IMethod[]
getDeclaredIMethods2() {
return new IClass.IMethod[] {
new IMethod() {
@Override public IAnnotation[] getAnnotations() { return new IAnnotation[0]; }
@Override public Access getAccess() { return Access.PUBLIC; }
@Override public boolean isStatic() { return false; }
@Override public boolean isAbstract() { return false; }
@Override public IClass getReturnType() { return objectType; }
@Override public String getName() { return "clone"; }
@Override public IClass[] getParameterTypes2() { return new IClass[0]; }
@Override public boolean isVarargs() { return false; }
@Override public IClass[] getThrownExceptions2() { return new IClass[0]; }
}
};
}
// SUPPRESS CHECKSTYLE LineLength:16
@Override public IClass.IField[] getDeclaredIFields2() { return new IClass.IField[0]; }
@Override public IClass[] getDeclaredIClasses2() { return new IClass[0]; }
@Override @Nullable public IClass getDeclaringIClass2() { return null; }
@Override @Nullable public IClass getOuterIClass2() { return null; }
@Override public IClass getSuperclass2() { return objectType; }
@Override public IClass[] getInterfaces2() { return new IClass[0]; }
@Override public String getDescriptor2() { return '[' + componentType.getDescriptor(); }
@Override public Access getAccess() { return componentType.getAccess(); }
@Override public boolean isFinal() { return true; }
@Override public boolean isEnum() { return false; }
@Override public boolean isInterface() { return false; }
@Override public boolean isAbstract() { return false; }
@Override public boolean isArray() { return true; }
@Override public boolean isPrimitive() { return false; }
@Override public boolean isPrimitiveNumeric() { return false; }
@Override public IClass getComponentType2() { return componentType; }
@Override public String toString() { return componentType.toString() + "[]"; }
};
}
/**
* If optionalName is {@code null}, finds all {@link IClass}es visible in the scope of the current
* class.
*
* If optionalName is not {@code null}, finds the member {@link IClass}es that has the given name. If
* the name is ambiguous (i.e. if more than one superclass, interface of enclosing type declares a type with that
* name), then the size of the returned array is greater than one.
*
*
* Examines superclasses, interfaces and enclosing type declarations.
*
*
* @return an array of {@link IClass}es in unspecified order, possibly of length zero
*/
IClass[]
findMemberType(@Nullable String optionalName) throws CompileException {
IClass[] res = (IClass[]) this.memberTypeCache.get(optionalName);
if (res == null) {
// Notice: A type may be added multiply to the result set because we are in its scope
// multiply. E.g. the type is a member of a superclass AND a member of an enclosing type.
Set s = new HashSet();
this.findMemberType(optionalName, s);
res = s.isEmpty() ? IClass.ZERO_ICLASSES : (IClass[]) s.toArray(new IClass[s.size()]);
this.memberTypeCache.put(optionalName, res);
}
return res;
}
private final Map memberTypeCache = new HashMap();
private static final IClass[] ZERO_ICLASSES = new IClass[0];
private void
findMemberType(@Nullable String optionalName, Collection result) throws CompileException {
// Search for a type with the given name in the current class.
IClass[] memberTypes = this.getDeclaredIClasses();
if (optionalName == null) {
result.addAll(Arrays.asList(memberTypes));
} else {
String memberDescriptor = Descriptor.fromClassName(
Descriptor.toClassName(this.getDescriptor())
+ '$'
+ optionalName
);
for (final IClass mt : memberTypes) {
if (mt.getDescriptor().equals(memberDescriptor)) {
result.add(mt);
return;
}
}
}
// Examine superclass.
{
IClass superclass = this.getSuperclass();
if (superclass != null) superclass.findMemberType(optionalName, result);
}
// Examine interfaces.
for (IClass i : this.getInterfaces()) i.findMemberType(optionalName, result);
// Examine enclosing type declarations.
{
IClass declaringIClass = this.getDeclaringIClass();
IClass outerIClass = this.getOuterIClass();
if (declaringIClass != null) {
declaringIClass.findMemberType(optionalName, result);
}
if (outerIClass != null && outerIClass != declaringIClass) {
outerIClass.findMemberType(optionalName, result);
}
}
}
/**
* @return The annotations of this type (possibly the empty array)
*/
public final IAnnotation[]
getIAnnotations() throws CompileException {
if (this.iAnnotationsCache != null) return this.iAnnotationsCache;
return (this.iAnnotationsCache = this.getIAnnotations2());
}
@Nullable private IAnnotation[] iAnnotationsCache;
/**
* @throws CompileException
*/
protected IAnnotation[]
getIAnnotations2() throws CompileException { return IClass.NO_ANNOTATIONS; }
/**
* Array of zero {@link IAnnotation}s.
*/
public static final IAnnotation[] NO_ANNOTATIONS = new IAnnotation[0];
/**
* Base for the members of an {@link IClass}. {@link IMember} are expected to be immutable, i.e. all getter methods
* return constant values.
*/
public
interface IMember {
/**
* @return One of {@link Access#PRIVATE}, {@link Access#PROTECTED}, {@link Access#DEFAULT} and {@link
* Access#PUBLIC}.
*/
Access getAccess();
/**
* @return Modifiers and/or annotations of this member
*/
IAnnotation[] getAnnotations();
/**
* @return The {@link IClass} that declares this {@link IClass.IMember}
*/
IClass getDeclaringIClass();
}
/**
* Base class for {@link IConstructor} and {@link IMethod}.
*/
public abstract
class IInvocable implements IMember {
private boolean argsNeedAdjust;
/**
* TODO
*/
public void
setArgsNeedAdjust(boolean newVal) { this.argsNeedAdjust = newVal; }
/**
* TODO
*/
public boolean
argsNeedAdjust() { return this.argsNeedAdjust; }
/**
* @return Whether this invocable is 'variable arity', i.e. its last parameter has an ellipsis ('...') after
* the type
*/
public abstract boolean isVarargs();
// Implement IMember.
@Override public IClass getDeclaringIClass() { return IClass.this; }
/**
* Returns the types of the parameters of this constructor or method. This method is fast.
*/
public final IClass[]
getParameterTypes() throws CompileException {
if (this.parameterTypesCache != null) return this.parameterTypesCache;
return (this.parameterTypesCache = this.getParameterTypes2());
}
@Nullable private IClass[] parameterTypesCache;
/**
* Opposed to the {@link Constructor}, there is no magic "{@code this$0}" parameter.
*
* Opposed to the {@link Constructor}, {@code enum}s have no magic parameters "{@code String name}" and
* "{@code int ordinal}".
*
*
* However, the "synthetic parameters" ("{@code val$}locvar") are included.
*
*/
public abstract IClass[]
getParameterTypes2() throws CompileException;
/**
* Returns the method descriptor of this constructor or method. This method is fast.
*/
public final MethodDescriptor
getDescriptor() throws CompileException {
if (this.descriptorCache != null) return this.descriptorCache;
return (this.descriptorCache = this.getDescriptor2());
}
@Nullable private MethodDescriptor descriptorCache;
/**
* Uncached implementation of {@link #getDescriptor()}.
*/
public abstract MethodDescriptor
getDescriptor2() throws CompileException;
/**
* Returns the types thrown by this constructor or method. This method is fast.
*/
public final IClass[]
getThrownExceptions() throws CompileException {
if (this.thrownExceptionsCache != null) return this.thrownExceptionsCache;
return (this.thrownExceptionsCache = this.getThrownExceptions2());
}
@Nullable private IClass[] thrownExceptionsCache;
/**
* @return The types thrown by this constructor or method
*/
public abstract IClass[]
getThrownExceptions2() throws CompileException;
/**
* @return Whether this {@link IInvocable} is more specific then that (in the sense of JLS7
* 15.12.2.5)
*/
public boolean
isMoreSpecificThan(IInvocable that) throws CompileException {
IClass.LOGGER.entering(null, "isMoreSpecificThan", that);
// a variable-length argument is always less specific than a fixed arity.
final boolean thatIsVararg;
if ((thatIsVararg = that.isVarargs()) != this.isVarargs()) {
// Only one of the two is varargs.
return thatIsVararg;
} else
if (thatIsVararg) {
// Both are varargs.
final IClass[] thisParameterTypes = this.getParameterTypes();
final IClass[] thatParameterTypes = that.getParameterTypes();
IClass[] t, u;
int n, k;
if (thisParameterTypes.length >= thatParameterTypes.length) {
t = thisParameterTypes;
u = thatParameterTypes;
n = t.length;
k = u.length;
IClass[] s = u;
// this = T | T_n
// that = U | U_k
// n >= k
// ignore generics, for now
// T0, T1, ..., Tn-1, Tn[]
// U0, U1, .., Uk[]
final int kMinus1 = k - 1;
for (int j = 0; j < kMinus1; ++j) {
// expect T[j] <: S[j]
if (!s[j].isAssignableFrom(t[j])) {
return false;
}
}
final IClass sk1 = s[kMinus1].getComponentType();
assert sk1 != null;
final int nMinus1 = n - 1;
for (int j = kMinus1; j < nMinus1; ++j) {
// expect T[j] <: S[k -1]
if (!sk1.isAssignableFrom(t[j])) {
return false;
}
}
if (!sk1.isAssignableFrom(t[nMinus1])) {
return false;
}
} else {
u = thisParameterTypes;
t = thatParameterTypes;
n = t.length;
k = u.length;
IClass[] s = t;
// n >= k
final int kMinus1 = k - 1;
for (int j = 0; j < kMinus1; ++j) {
// expect U[j] <: S[j]
if (!s[j].isAssignableFrom(u[j])) {
return false;
}
}
final IClass uk1 = u[kMinus1].getComponentType();
assert uk1 != null;
final int nMinus1 = n - 1;
for (int j = kMinus1; j < nMinus1; ++j) {
// expect U[k -1] <: S[j]
if (!s[j].isAssignableFrom(uk1)) {
return false;
}
}
IClass snm1ct = s[nMinus1].getComponentType();
assert snm1ct != null;
if (!snm1ct.isAssignableFrom(uk1)) {
return false;
}
}
return true;
}
// both are fixed arity
// The following case is tricky: JLS7 says that the invocation is AMBIGUOUS, but only JAVAC 1.2 issues an
// error; JAVAC 1.4.1, 1.5.0 and 1.6.0 obviously ignore the declaring type and invoke "A.meth(String)".
// JLS7 is not clear about this. For compatibility with JAVA 1.4.1, 1.5.0 and 1.6.0, JANINO also ignores
// the declaring type.
//
// See also JANINO-79 and JlsTests / 15.12.2.2
// if (false) {
// if (!that.getDeclaringIClass().isAssignableFrom(this.getDeclaringIClass())) {
// if (IClass.DEBUG) System.out.println("falsE");
// return false;
// }
// }
IClass[] thisParameterTypes = this.getParameterTypes();
IClass[] thatParameterTypes = that.getParameterTypes();
for (int i = 0; i < thisParameterTypes.length; ++i) {
if (!thatParameterTypes[i].isAssignableFrom(thisParameterTypes[i])) {
IClass.LOGGER.exiting(null, "isMoreSpecificThan", false);
return false;
}
}
boolean result = !Arrays.equals(thisParameterTypes, thatParameterTypes);
IClass.LOGGER.exiting(null, "isMoreSpecificThan", result);
return result;
}
/**
* @return Whether this {@link IInvocable} is less specific then that (in the sense of JLS7
* 15.12.2.5)
*/
public boolean
isLessSpecificThan(IInvocable that) throws CompileException { return that.isMoreSpecificThan(this); }
@Override public abstract String
toString();
}
/**
* Representation of a constructor of an {@link IClass}.
*/
public abstract
class IConstructor extends IInvocable {
@Override public MethodDescriptor
getDescriptor2() throws CompileException {
IClass[] parameterTypes = this.getParameterTypes();
// Iff this is an inner class, prepend the magic "this$0" constructor parameter.
{
IClass outerIClass = IClass.this.getOuterIClass();
if (outerIClass != null) {
IClass[] tmp = new IClass[parameterTypes.length + 1];
tmp[0] = outerIClass;
System.arraycopy(parameterTypes, 0, tmp, 1, parameterTypes.length);
parameterTypes = tmp;
}
}
String[] parameterFds = IClass.getDescriptors(parameterTypes);
// Iff this is an enum, prepend the magic "String name" and "int ordinal" constructor parameters.
if (this.getDeclaringIClass().isEnum()) {
String[] tmp = new String[parameterFds.length + 2];
tmp[0] = Descriptor.JAVA_LANG_STRING;
tmp[1] = Descriptor.INT;
System.arraycopy(parameterFds, 0, tmp, 2, parameterFds.length);
parameterFds = tmp;
}
return new MethodDescriptor(Descriptor.VOID, parameterFds);
}
@Override public String
toString() {
StringBuilder sb = new StringBuilder(this.getDeclaringIClass().toString());
sb.append('(');
try {
IClass[] parameterTypes = this.getParameterTypes();
for (int i = 0; i < parameterTypes.length; ++i) {
if (i > 0) sb.append(", ");
sb.append(parameterTypes[i].toString());
}
} catch (CompileException ex) {
sb.append("");
}
sb.append(')');
return sb.toString();
}
}
/**
* Representation of a method in an {@link IClass}.
*/
public abstract
class IMethod extends IInvocable {
/**
* @return Whether this method is STATIC
*/
public abstract boolean isStatic();
/**
* @return Whether this method is ABSTRACT
*/
public abstract boolean isAbstract();
/**
* @return The return type of this method
*/
public abstract IClass getReturnType() throws CompileException;
/**
* @return The name of this method
*/
public abstract String getName();
@Override public MethodDescriptor
getDescriptor2() throws CompileException {
return new MethodDescriptor(
this.getReturnType().getDescriptor(),
IClass.getDescriptors(this.getParameterTypes())
);
}
@Override public String
toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getAccess().toString()).append(' ');
if (this.isStatic()) sb.append("static ");
if (this.isAbstract()) sb.append("abstract ");
try {
sb.append(this.getReturnType().toString());
} catch (CompileException ex) {
sb.append("");
}
sb.append(' ');
sb.append(this.getDeclaringIClass().toString());
sb.append('.');
sb.append(this.getName());
sb.append('(');
try {
IClass[] parameterTypes = this.getParameterTypes();
for (int i = 0; i < parameterTypes.length; ++i) {
if (i > 0) sb.append(", ");
sb.append(parameterTypes[i].toString());
}
} catch (CompileException ex) {
sb.append("");
}
sb.append(')');
try {
IClass[] tes = this.getThrownExceptions();
if (tes.length > 0) {
sb.append(" throws ").append(tes[0]);
for (int i = 1; i < tes.length; ++i) sb.append(", ").append(tes[i]);
}
} catch (CompileException ex) {
sb.append("");
}
return sb.toString();
}
}
/**
* Representation of a field of this {@link IClass}.
*/
public abstract
class IField implements IMember {
// Implement IMember.
@Override public abstract Access getAccess();
@Override public IClass getDeclaringIClass() { return IClass.this; }
/**
* @return Whether this field is STATIC
*/
public abstract boolean isStatic();
/**
* @return The type of this field
*/
public abstract IClass getType() throws CompileException;
/**
* @return The name this field
*/
public abstract String getName();
/**
* @return The descriptor of this field
*/
public String getDescriptor() throws CompileException { return this.getType().getDescriptor(); }
/**
* Returns the value of the field if it is a compile-time constant value, i.e. the field is FINAL and its
* initializer is a constant expression (JLS7 15.28, bullet 12).
*/
@Nullable public abstract Object getConstantValue() throws CompileException;
@Override public String
toString() { return this.getDeclaringIClass().toString() + "." + this.getName(); }
}
/**
* Representation of a Java "annotation".
*/
public
interface IAnnotation {
/**
* @return The type of the annotation
*/
IClass getAnnotationType() throws CompileException;
/**
* Returns the value of the named element:
*
* - {@link Boolean}
* - {@link Byte}
* - {@link Character}
* - {@link Double}
* - {@link Float}
* - {@link Integer}
* - {@link Long}
* - {@link Short}
* -
* A primitive value
*
* - {@link String}
* -
* A string value
*
* - {@link IField}
* -
* An enum constant
*
* - {@link IClass}
* -
* A class literal
*
* - {@link IAnnotation}
* -
* An annotation
*
* - {@link Object}{@code []}
* -
* An array value
*
*
*
* Notice that {@code null} is not a valid return value.
*
*/
Object getElementValue(String name) throws CompileException;
}
/**
* This class caches the declared methods in order to minimize the invocations of {@link #getDeclaredIMethods2()}.
*/
public void
invalidateMethodCaches() {
this.declaredIMethodsCache = null;
this.declaredIMethodCache = null;
}
}