/* * Janino - An embedded Java[TM] compiler * * Copyright (c) 2001-2010, Arno Unkrig * 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. The name of the author may not be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.codehaus.commons.compiler.CompileException; import org.codehaus.janino.Java.Annotation; /** * A simplified equivalent to "java.lang.reflect". *
* 'JLS7' means a reference to the Java Language Specification, Java SE * 7 Edition */ @SuppressWarnings({ "rawtypes", "unchecked" }) public abstract class IClass { private static final boolean DEBUG = false; /** * 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; public PrimitiveIClass(String fieldDescriptor) { this.fieldDescriptor = fieldDescriptor; } @Override protected IClass getComponentType2() { return null; } @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 protected IClass getDeclaringIClass2() { return null; } @Override protected String getDescriptor2() { return this.fieldDescriptor; } @Override protected IClass[] getInterfaces2() { return new IClass[0]; } @Override protected IClass getOuterIClass2() { return null; } @Override 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 isInterface() { return false; } @Override public boolean isPrimitive() { return true; } @Override public boolean isPrimitiveNumeric() { return Descriptor.isPrimitiveNumeric(this.fieldDescriptor); } // SUPPRESS CHECKSTYLE LineLength @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
* "void".
*/
public final IConstructor[]
getDeclaredIConstructors() {
if (this.declaredIConstructorsCache == null) {
this.declaredIConstructorsCache = this.getDeclaredIConstructors2();
}
return this.declaredIConstructorsCache;
}
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.
*
* If
* If
* Examines superclasses, interfaces and enclosing type declarations.
* @return an array of {@link IClass}es in unspecified order, possibly of length zero
*/
IClass[]
findMemberType(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
* Returns an empty array for an array, primitive type or "void".
*/
public final IMethod[]
getDeclaredIMethods() {
if (this.declaredIMethodsCache == null) {
this.declaredIMethodsCache = this.getDeclaredIMethods2();
}
return this.declaredIMethodsCache;
}
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) {
if (this.declaredIMethodCache == null) {
IMethod[] dims = this.getDeclaredIMethods();
// Fill the map with "IMethod"s and "List
*
* @return an array of {@link IMethod}s that must not be modified
*/
public final IMethod[]
getIMethods() throws CompileException {
if (this.iMethodCache == null) {
Listnull
iff this {@link IClass} does not declare an {@link IField} with that name
*/
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; }
private Map
* Returns an empty array for an array, primitive type or "void".
*/
public final IClass[]
getDeclaredIClasses() throws CompileException {
if (this.declaredIClassesCache == null) {
this.declaredIClassesCache = this.getDeclaredIClasses2();
}
return this.declaredIClassesCache;
}
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} */
public final IClass
getDeclaringIClass() throws CompileException {
if (!this.declaringIClassIsCached) {
this.declaringIClassCache = this.getDeclaringIClass2();
this.declaringIClassIsCached = true;
}
return this.declaringIClassCache;
}
private boolean declaringIClassIsCached;
private IClass declaringIClassCache;
/** @return If this class is a member class, the declaring class, otherwise {@code null} */
protected abstract IClass getDeclaringIClass2() throws CompileException;
/**
* The following types have an "outer class":
*
*
*
* @return The outer class of this type, or {@code null}
*/
public final IClass
getOuterIClass() throws CompileException {
if (!this.outerIClassIsCached) {
this.outerIClassCache = this.getOuterIClass2();
this.outerIClassIsCached = true;
}
return this.outerIClassCache;
}
private boolean outerIClassIsCached;
private IClass outerIClassCache;
/** @see #getOuterIClass() */
protected abstract IClass getOuterIClass2() throws CompileException;
/**
* Returns the superclass of the class.
* Returns "null" for class "Object", interfaces, arrays, primitive types
* and "void".
*/
public final IClass
getSuperclass() throws CompileException {
if (!this.superclassIsCached) {
this.superclassCache = this.getSuperclass2();
this.superclassIsCached = true;
if (this.superclassCache != null && this.superclassCache.isSubclassOf(this)) {
throw new CompileException(
"Class circularity detected for \"" + Descriptor.toClassName(this.getDescriptor()) + "\"",
null
);
}
}
return this.superclassCache;
}
private boolean superclassIsCached;
private IClass superclassCache;
/** @see #getSuperclass() */
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 true
if subclassing is prohibited
*/
public abstract boolean isFinal();
/**
* Returns the interfaces implemented by the class.
* Returns the superinterfaces of the interface.
* Returns "Cloneable" and "Serializable" for arrays.
* Returns an empty array for primitive types and "void".
*/
public final IClass[]
getInterfaces() throws CompileException {
if (this.interfacesCache == null) {
this.interfacesCache = this.getInterfaces2();
for (IClass ii : this.interfacesCache) {
if (ii.implementsInterface(this)) {
throw new CompileException(
"Interface circularity detected for \"" + Descriptor.toClassName(this.getDescriptor()) + "\"",
null
);
}
}
}
return this.interfacesCache;
}
private IClass[] interfacesCache;
/** @see #getInterfaces() */
protected abstract IClass[] getInterfaces2() throws CompileException;
/**
* Whether the class may be instantiated (JVMS 4.1 access_flags)
* @return 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) {
this.descriptorCache = this.getDescriptor2();
}
return this.descriptorCache;
}
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 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 "void" */
public abstract boolean isPrimitive();
/** @return Whether this type represents "byte", "short", "int", "long", "char", "float" or "double" */
public abstract boolean isPrimitiveNumeric();
/**
* @return The component type of the array, or {@code null} for classes, interfaces, primitive types and {@code
* void}
*/
public final IClass
getComponentType() {
if (!this.componentTypeIsCached) {
this.componentTypeCache = this.getComponentType2();
this.componentTypeIsCached = true;
}
return this.componentTypeCache;
}
private boolean componentTypeIsCached;
private IClass componentTypeCache;
/** @see #getComponentType() */
protected abstract IClass getComponentType2();
@Override public String toString() { return Descriptor.toClassName(this.getDescriptor()); }
/**
* Determine if "this" is assignable from "that". This is true if "this" is identical with "that" (JLS7 5.1.1), or
* if "that" is widening-primitive-convertible to "this" (JLS7 5.1.2), or if "that" is
* widening-reference-convertible to "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();
if (!thisCt.isPrimitive() && thisCt.isAssignableFrom(thatCt)) return true;
}
}
}
return false;
}
private static final Settrue
if this class is an immediate or non-immediate
* subclass of 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 this
represents a class: Return true
if this class
* directly or indirectly implements that
interface.
* this
represents an interface: Return true
if this
* interface directly or indirectly extends 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;
}
/**
* Get 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;
}
/**
* Get 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) {
this.arrayIClass = this.getArrayIClass2(objectType);
}
return this.arrayIClass;
}
private IClass arrayIClass;
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 String getName() { return "clone"; }
@Override public IClass getReturnType() { return objectType; }
@Override public boolean isAbstract() { return false; }
@Override public boolean isStatic() { return false; }
@Override public Access getAccess() { return Access.PUBLIC; }
@Override public boolean isVarargs() { return false; }
@Override public IClass[] getParameterTypes2() { return new IClass[0]; }
@Override public IClass[] getThrownExceptions2() { return new IClass[0]; }
@Override public Annotation[] getAnnotations() { return new Annotation[0]; }
}
};
}
// CHECKSTYLE LineLength:OFF
@Override public IClass.IField[] getDeclaredIFields2() { return new IClass.IField[0]; }
@Override public IClass[] getDeclaredIClasses2() { return new IClass[0]; }
@Override public IClass getDeclaringIClass2() { return null; }
@Override 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 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; }
// CHECKSTYLE LineLength:ON
@Override public String toString() { return componentType.toString() + "[]"; }
};
}
/**
* If optionalName
is null
, find all {@link IClass}es visible in the
* scope of the current class.
* optionalName
is not null
, find 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.
*