1147 lines
46 KiB
Java
1147 lines
46 KiB
Java
|
|
/*
|
|
* 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".
|
|
* <p>
|
|
* 'JLS7' means a reference to the <a href="http://docs.oracle.com/javase/specs/">Java Language Specification, Java SE
|
|
* 7 Edition</a>
|
|
*/
|
|
@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 <i>not</i> 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.
|
|
* <p>
|
|
* 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.
|
|
* <br>
|
|
* 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).
|
|
* <br>
|
|
* 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<IMethod>"s.
|
|
Map<String, Object /*IMethod-or-List<IMethod>*/> m = new HashMap();
|
|
for (IMethod dim : dims) {
|
|
String mn = dim.getName();
|
|
Object o = m.get(mn);
|
|
if (o == null) {
|
|
m.put(mn, dim);
|
|
} else
|
|
if (o instanceof IMethod) {
|
|
List l = new ArrayList();
|
|
l.add(o);
|
|
l.add(dim);
|
|
m.put(mn, l);
|
|
} else {
|
|
((List) o).add(dim);
|
|
}
|
|
}
|
|
|
|
// Convert "IMethod"s and "List"s to "IMethod[]"s.
|
|
for (Map.Entry<String, Object/*IMethod-or-List<IMethod>*/> me : m.entrySet()) {
|
|
Object v = me.getValue();
|
|
if (v instanceof IMethod) {
|
|
me.setValue(new IMethod[] { (IMethod) v });
|
|
} else {
|
|
List<IMethod> l = (List) v;
|
|
me.setValue(l.toArray(new IMethod[l.size()]));
|
|
}
|
|
}
|
|
this.declaredIMethodCache = m;
|
|
}
|
|
|
|
IMethod[] methods = (IMethod[]) this.declaredIMethodCache.get(methodName);
|
|
return methods == null ? IClass.NO_IMETHODS : methods;
|
|
}
|
|
private Map<String /*methodName*/, Object /*IMethod-or-List<IMethod>*/> declaredIMethodCache;
|
|
|
|
/**
|
|
* Returns all methods declared in the class or interface, its superclasses and its
|
|
* superinterfaces.<br>
|
|
*
|
|
* @return an array of {@link IMethod}s that must not be modified
|
|
*/
|
|
public final IMethod[]
|
|
getIMethods() throws CompileException {
|
|
if (this.iMethodCache == null) {
|
|
List<IMethod> iMethods = new ArrayList();
|
|
this.getIMethods(iMethods);
|
|
this.iMethodCache = (IMethod[]) iMethods.toArray(new IMethod[iMethods.size()]);
|
|
}
|
|
return this.iMethodCache;
|
|
}
|
|
private IMethod[] iMethodCache;
|
|
private void
|
|
getIMethods(List<IMethod> result) throws CompileException {
|
|
IMethod[] ms = this.getDeclaredIMethods();
|
|
|
|
SCAN_DECLARED_METHODS:
|
|
for (IMethod candidate : ms) {
|
|
String 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
|
|
*/
|
|
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 constrcutor could not be found
|
|
*/
|
|
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 "void"
|
|
*/
|
|
public final IField[]
|
|
getDeclaredIFields() {
|
|
Collection<IField> allFields = this.getDeclaredIFieldsCache().values();
|
|
return (IField[]) allFields.toArray(new IField[allFields.size()]);
|
|
}
|
|
|
|
/** @return String fieldName => IField */
|
|
private Map<String /*fieldName*/, IField>
|
|
getDeclaredIFieldsCache() {
|
|
if (this.declaredIFieldsCache == null) {
|
|
|
|
IField[] fields = this.getDeclaredIFields2();
|
|
|
|
Map<String /*fieldName*/, IField> m = new HashMap();
|
|
for (IField f : fields) m.put(f.getName(), f);
|
|
this.declaredIFieldsCache = m;
|
|
}
|
|
return this.declaredIFieldsCache;
|
|
}
|
|
|
|
/**
|
|
* Returns the named {@link IField} declared in this {@link IClass} (does not work for inherited fields).
|
|
*
|
|
* @return <code>null</code> 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<String /*fieldName*/, IField> declaredIFieldsCache;
|
|
|
|
/** Uncached version of {@link #getDeclaredIFields()}. */
|
|
protected abstract IField[] getDeclaredIFields2();
|
|
|
|
/**
|
|
* Returns 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).<br>
|
|
* 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":
|
|
* <ul>
|
|
* <li>Anonymous classes declared in a non-static method of a class
|
|
* <li>Local classes declared in a non-static method of a class
|
|
* <li>Non-static member classes
|
|
* </ul>
|
|
*
|
|
* @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.<br>
|
|
* 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 <code>true</code> if subclassing is prohibited
|
|
*/
|
|
public abstract boolean isFinal();
|
|
|
|
/**
|
|
* Returns the interfaces implemented by the class.<br>
|
|
* Returns the superinterfaces of the interface.<br>
|
|
* Returns "Cloneable" and "Serializable" for arrays.<br>
|
|
* 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 <code>true</code> 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 Set<String> PRIMITIVE_WIDENING_CONVERSIONS = new HashSet();
|
|
static {
|
|
String[] pwcs = new String[] {
|
|
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</code> if this class is an immediate or non-immediate
|
|
* subclass of <code>that</code> 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</code> represents a class: Return <code>true</code> if this class
|
|
* directly or indirectly implements <code>that</code> interface.
|
|
* <p>
|
|
* If <code>this</code> represents an interface: Return <code>true</code> if this
|
|
* interface directly or indirectly extends <code>that</code> 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 <code>optionalName</code> is <code>null</code>, find all {@link IClass}es visible in the
|
|
* scope of the current class.
|
|
* <p>
|
|
* If <code>optionalName</code> is not <code>null</code>, 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.
|
|
* <p>
|
|
* 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<IClass> 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<String /*name*/, IClass[]> memberTypeCache = new HashMap();
|
|
private static final IClass[] ZERO_ICLASSES = new IClass[0];
|
|
private void
|
|
findMemberType(String optionalName, Collection<IClass> 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 */
|
|
Annotation[] 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 abstract Access getAccess();
|
|
@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) {
|
|
this.parameterTypesCache = this.getParameterTypes2();
|
|
}
|
|
return this.parameterTypesCache;
|
|
}
|
|
private IClass[] parameterTypesCache;
|
|
|
|
/** @return The types of the parameters of this constructor or method */
|
|
public abstract IClass[]
|
|
getParameterTypes2() throws CompileException;
|
|
|
|
/** Returns the method descriptor of this constructor or method. This method is fast. */
|
|
public final String
|
|
getDescriptor() throws CompileException {
|
|
if (this.descriptorCache == null) {
|
|
this.descriptorCache = this.getDescriptor2();
|
|
}
|
|
return this.descriptorCache;
|
|
}
|
|
private String descriptorCache;
|
|
|
|
/** Uncached implementation of {@link #getDescriptor()}. */
|
|
public abstract String
|
|
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) {
|
|
this.thrownExceptionsCache = this.getThrownExceptions2();
|
|
}
|
|
return this.thrownExceptionsCache;
|
|
}
|
|
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 {@code that} (in the sense of JLS7 15.12.2.5)
|
|
*/
|
|
public boolean
|
|
isMoreSpecificThan(IInvocable that) throws CompileException {
|
|
if (IClass.DEBUG) System.out.print("\"" + this + "\".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();
|
|
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();
|
|
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;
|
|
}
|
|
}
|
|
if (!s[nMinus1].getComponentType().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])) {
|
|
if (IClass.DEBUG) System.out.println("false");
|
|
return false;
|
|
}
|
|
}
|
|
if (IClass.DEBUG) System.out.println("true");
|
|
return !Arrays.equals(thisParameterTypes, thatParameterTypes);
|
|
}
|
|
|
|
/**
|
|
* @return Whether this {@link IInvocable} is less specific then {@code 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 {
|
|
|
|
/**
|
|
* Opposed to {@link java.lang.reflect.Constructor#getParameterTypes()}, the
|
|
* return value of this method does not include the optionally leading "synthetic
|
|
* parameters".
|
|
*/
|
|
@Override public abstract IClass[] getParameterTypes2() throws CompileException;
|
|
|
|
/**
|
|
* Opposed to {@link #getParameterTypes()}, the method descriptor returned by this method does include the
|
|
* optionally leading synthetic parameters.
|
|
*/
|
|
@Override public String
|
|
getDescriptor2() throws CompileException {
|
|
IClass[] parameterTypes = this.getParameterTypes();
|
|
|
|
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;
|
|
}
|
|
|
|
return new MethodDescriptor(IClass.getDescriptors(parameterTypes), Descriptor.VOID).toString();
|
|
}
|
|
|
|
@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("<invalid type>");
|
|
}
|
|
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 String
|
|
getDescriptor2() throws CompileException {
|
|
return new MethodDescriptor(
|
|
IClass.getDescriptors(this.getParameterTypes()),
|
|
this.getReturnType().getDescriptor()
|
|
).toString();
|
|
}
|
|
|
|
@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("<invalid type>");
|
|
}
|
|
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("<invalid type>");
|
|
}
|
|
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("<invalid thrown exception type>");
|
|
}
|
|
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).
|
|
*/
|
|
public abstract Object getConstantValue() throws CompileException;
|
|
|
|
@Override public String
|
|
toString() { return this.getDeclaringIClass().toString() + "." + this.getName(); }
|
|
}
|
|
|
|
/**
|
|
* This class caches the declared methods in order to minimize the invocations of {@link #getDeclaredIMethods2()}.
|
|
*/
|
|
public void
|
|
invalidateMethodCaches() {
|
|
this.declaredIMethodsCache = null;
|
|
this.declaredIMethodCache = null;
|
|
}
|
|
}
|