/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* This file was originally derived from the Polyglot extensible compiler framework.
*
* (C) Copyright 2000-2007 Polyglot project group, Cornell University
* (C) Copyright IBM Corporation 2007-2012.
*/
package polyglot.types.reflect;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import polyglot.main.Reporter;
import polyglot.types.ClassDef;
import polyglot.types.ClassType;
import polyglot.types.ConstructorDef;
import polyglot.types.FieldDef;
import polyglot.types.Flags;
import polyglot.types.LazyRef;
import polyglot.types.MethodDef;
import polyglot.types.Name;
import polyglot.types.QName;
import polyglot.types.Ref;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.types.Types;
import polyglot.types.reflect.InnerClasses.Info;
import polyglot.util.InternalCompilerError;
import polyglot.util.Position;
import polyglot.util.StringUtil;
import x10.types.X10ClassDef;
import x10.types.X10ClassType;
import x10.types.constants.ConstantValue;
import x10.util.CollectionFactory;
/**
* A lazy initializer for classes loaded from a .class file.
*
* @author nystrom
*
* IMPORTANT: to avoid infinite loops, this code should not call any code that
* might load a class (in particular, this one). ts.Object() should not be
* called.
*/
public class ClassFileLazyClassInitializer {
protected ClassFile clazz;
protected TypeSystem ts;
protected Reporter reporter;
protected ClassDef ct;
protected boolean init;
protected boolean constructorsInitialized;
protected boolean fieldsInitialized;
protected boolean interfacesInitialized;
protected boolean memberClassesInitialized;
protected boolean methodsInitialized;
protected boolean superclassInitialized;
public ClassFileLazyClassInitializer(ClassFile file, TypeSystem ts) {
this.clazz = file;
this.ts = ts;
this.reporter = ts.extensionInfo().getOptions().reporter;
}
public void setClass(ClassDef ct) {
this.ct = ct;
}
public boolean fromClassFile() {
return true;
}
/**
* Create a position for the class file.
*/
public Position position() {
String path = clazz.classFileSource().getAbsolutePath();
if (path.endsWith(".jar")){
path += ":" + clazz.name() + ".class";
}
return new Position(null, path);
}
/**
* Create the type for this class file.
*/
protected ClassDef createType() throws SemanticException {
// The name is of the form "p.q.C$I$J".
String name = clazz.classNameCP(clazz.getThisClass());
if (reporter.should_report(Reporter.loader, 2))
reporter.report(2, "creating ClassType for " + name);
// Create the ClassType.
ClassDef ct = ts.createClassDef();
this.setClass(ct);
ct.setFromJavaClassFile();
ct.flags(ts.flagsForBits(clazz.getModifiers()));
ct.position(position());
// This is the "p.q" part.
String packageName = StringUtil.getPackageComponent(name);
// Set the ClassType's package.
if (!packageName.equals("")) {
ct.setPackage(Types.ref(ts.packageForName(QName.make(packageName))));
}
// This is the "C$I$J" part.
String className = StringUtil.getShortNameComponent(name);
InnerClasses.Info innerClassInfo = clazz.getInnerClassInfo();
String outerName; // This will be "p.q.C$I"
String innerName; // This will be "J"
outerName = name;
innerName = null;
//int dollar = outerName.lastIndexOf('$');
//
//if (dollar >= 0) {
// outerName = name.substring(0, dollar);
// innerName = name.substring(dollar + 1);
//
// // Lazily load the outer class.
// ct.outer(defForName(outerName));
//}
//else {
// outerName = name;
// innerName = null;
//}
if (innerClassInfo != null) {
outerName = clazz.classNameCP(innerClassInfo.outerClassIndex);
innerName = clazz.className(innerClassInfo.nameIndex);
// Lazily load the outer class.
ct.outer(defForName(outerName));
}
ClassDef.Kind kind = ClassDef.TOP_LEVEL;
if (innerName != null) {
// A nested class. Parse the class name to determine what kind.
StringTokenizer st = new StringTokenizer(className, "$");
while (st.hasMoreTokens()) {
String s = st.nextToken();
if (Character.isDigit(s.charAt(0))) {
// Example: C$1
kind = ClassDef.ANONYMOUS;
}
else if (kind == ClassDef.ANONYMOUS) {
// Example: C$1$D
kind = ClassDef.LOCAL;
}
else {
// Example: C$D
kind = ClassDef.MEMBER;
}
}
}
if (reporter.should_report(Reporter.loader, 3))
reporter.report(3, name + " is " + kind);
ct.kind(kind);
if (ct.isTopLevel()) {
ct.name(Name.make(className));
}
else if (ct.isMember() || ct.isLocal()) {
ct.name(Name.make(innerName));
}
initSuperclass();
initInterfaces();
initMemberClasses();
initFields();
initMethods();
initConstructors();
return ct;
}
/**
* Create the type for this class file.
*/
public ClassDef type() throws SemanticException {
if (ct == null) {
ct = createType();
}
return ct;
}
/**
* Return an array type.
* @param t The array base type.
* @param dims The number of dimensions of the array.
* @return An array type.
*/
protected Ref<? extends Type> arrayOf(Type t, int dims) {
return arrayOf(Types.<Type>ref(t), dims);
}
/**
* Return an array type.
* @param t The array base type.
* @param dims The number of dimensions of the array.
* @return An array type.
*/
protected Ref<? extends Type> arrayOf(Ref<? extends Type> t, int dims) {
if (dims == 0) {
return t;
}
else {
return Types.<Type>ref(ts.arrayOf(t, dims));
}
}
/**
* Return a list of types based on a JVM type descriptor string.
* @param str The type descriptor.
* @param bounds Generic type bounds in scope (may be null).
* @return The corresponding list of types.
*/
protected List<Ref<? extends Type>> typeListForString(String str, Map<String,Ref<? extends Type>> bounds) {
List<Ref<? extends Type>> types = new ArrayList<Ref<? extends Type>>(1);
updateTypeListFromString(types, str, 0, bounds);
if (reporter.should_report(Reporter.loader, 4))
reporter.report(4, "parsed \"" + str + "\" -> " + types);
return types;
}
private int updateTypeListFromString(List<Ref<? extends Type>> types, String str, int begin, Map<String,Ref<? extends Type>> bounds) {
int i = begin;
while (i < str.length()) {
if (str.charAt(i) == '>') return i;
i = updateTypeFromString(types, str, i, bounds);
i++;
}
return i;
}
private int updateTypeFromString(List<Ref<? extends Type>> types, String str, int begin, Map<String,Ref<? extends Type>> bounds) {
int i = begin;
int dims = 0;
while (str.charAt(i) == '[') {
dims++;
i++;
}
switch (str.charAt(i)) {
case 'Z':
types.add(arrayOf(ts.Boolean(), dims));
break;
case 'B':
types.add(arrayOf(ts.Byte(), dims));
break;
case 'S':
types.add(arrayOf(ts.Short(), dims));
break;
case 'C':
types.add(arrayOf(ts.Char(), dims));
break;
case 'I':
types.add(arrayOf(ts.Int(), dims));
break;
case 'J':
types.add(arrayOf(ts.Long(), dims));
break;
case 'F':
types.add(arrayOf(ts.Float(), dims));
break;
case 'D':
types.add(arrayOf(ts.Double(), dims));
break;
case 'V':
types.add(arrayOf(ts.Void(), dims));
break;
case 'T': { // Type parameter - use the erased bound
int start = ++i;
while (i < str.length()) {
if (str.charAt(i) == ';') {
break;
}
i++;
}
Ref<? extends Type> t = bounds.get(str.substring(start, i));
if (t == null) {
t = typeForName("x10.lang.Any");
}
types.add(arrayOf(t, dims));
break;
}
case 'L': {
Ref<? extends Type> t = null;
String parent = null;
do {
int start = ++i;
while (i < str.length()) {
char c = str.charAt(i);
if (c == ';' || c == '<') {
String s = str.substring(start, i);
s = s.replace('/', '.');
if (t == null) {
t = typeForName(s);
parent = s;
} else {
String fullName = parent + "$" + s;
parent = fullName;
t = typeForName(fullName); // FIXME: forgets generic info on container -- should look up member class instead
}
break;
}
i++;
}
if (str.charAt(i) == '<') {
++i;
assert (t != null);
List<Ref<? extends Type>> targs = new ArrayList<Ref<? extends Type>>();
i = updateTypeListFromString(targs, str, i, bounds);
assert (i < str.length() && str.charAt(i) == '>');
i++;
X10ClassType cType = t.get().toClass();
List<Type> typeArgs = new ArrayList<Type>(targs.size());
for (Ref<? extends Type> a : targs) {
typeArgs.add(a.get());
}
// [DC] hacky way of turning off type arguments here -- we don't want them in X10
//t = Types.ref(cType.typeArguments(typeArgs));
}
} while (str.charAt(i) == '.'); // Some signatures have generic info for containers
assert (i < str.length() && str.charAt(i) == ';');
types.add(arrayOf(t, dims));
break;
}
case '*': { // wildcard
types.add(arrayOf(Types.ref(ts.Any()), dims));
break;
}
case '+': { // wildcard extends
List<Ref<? extends Type>> t = new ArrayList<Ref<? extends Type>>();
i = updateTypeFromString(t, str, i+1, bounds);
assert (t.size() == 1);
types.add(arrayOf(t.get(0), dims));
break;
}
case '-': { // wildcard super
List<Ref<? extends Type>> t = new ArrayList<Ref<? extends Type>>();
i = updateTypeFromString(t, str, i+1, bounds);
assert (t.size() == 1);
types.add(arrayOf(Types.ref(ts.Any()), dims));
break;
}
default:
return i;
}
return i;
}
/**
* Return a type based on a JVM type descriptor string.
* @param str The type descriptor.
* @param bounds Generic type bounds in scope (may be null).
* @return The corresponding type.
*/
protected Ref<? extends Type> typeForString(String str, Map<String,Ref<? extends Type>> bounds) {
List<Ref<? extends Type>> l = typeListForString(str, bounds);
if (l.size() == 1) {
return l.get(0);
}
throw new InternalCompilerError("Bad type string: \"" + str + "\"");
}
/**
* Looks up a class by name, assuming the class exists.
* @param name Name of the class to find.
* @return A ClassType with the given name.
* @throws InternalCompilerError if the class does not exist.
*/
protected Ref<X10ClassDef> defForName(String name) {
return defForName(name, null);
}
protected Ref<X10ClassDef> defForName(String name, Flags flags) {
if (reporter.should_report(Reporter.loader, 2))
reporter.report(2, "resolving " + name);
LazyRef<X10ClassDef> sym = Types.lazyRef(ts.unknownClassDef(), null);
if (flags == null) {
sym.setResolver(ts.extensionInfo().scheduler().LookupGlobalTypeDef(sym, QName.make(name)));
}
else {
sym.setResolver(ts.extensionInfo().scheduler().LookupGlobalTypeDefAndSetFlags(sym, QName.make(name), flags));
}
return sym;
}
/**
* Looks up a class by name, assuming the class exists.
* @param name Name of the class to find.
* @return A Ref to a Type with the given name.
*/
protected Ref<? extends Type> typeForName(String name) {
return typeForName(name, null);
}
private static final HashMap<String,String> boxedPrimitives = new HashMap<String,String>();
static {
boxedPrimitives.put("x10.core.Boolean", "x10.lang.Boolean");
boxedPrimitives.put("x10.core.Char", "x10.lang.Char");
boxedPrimitives.put("x10.core.Byte", "x10.lang.Byte");
boxedPrimitives.put("x10.core.Short", "x10.lang.Short");
boxedPrimitives.put("x10.core.Int", "x10.lang.Int");
boxedPrimitives.put("x10.core.Long", "x10.lang.Long");
boxedPrimitives.put("x10.core.Float", "x10.lang.Float");
boxedPrimitives.put("x10.core.Double", "x10.lang.Double");
boxedPrimitives.put("x10.core.UByte", "x10.lang.UByte");
boxedPrimitives.put("x10.core.UShort", "x10.lang.UShort");
boxedPrimitives.put("x10.core.UInt", "x10.lang.UInt");
boxedPrimitives.put("x10.core.ULong", "x10.lang.ULong");
}
protected Ref<? extends Type> typeForName(String name, Flags flags) {
String unboxed = boxedPrimitives.get(name);
if (unboxed != null) {
name = unboxed;
}
//name = name.replace('$', '.'); // keep the name with the '$' to make sure the system finds the class
Name shortName = Name.make(name.substring(name.lastIndexOf('.')+1));
return Types.<Type>ref(ts.createClassType(position(), position(), defForName(name, flags)).name(shortName));
}
public void initTypeObject() {
this.init = true;
}
public boolean isTypeObjectInitialized() {
return this.init;
}
public void initSuperclass() {
if (superclassInitialized) {
return;
}
if (clazz.name().equals("java/lang/Object")) {
ct.superType(null);
}
else {
String superName = clazz.classNameCP(clazz.getSuperClass());
if (superName != null) {
ct.superType(typeForName(superName));
}
else {
ct.superType(typeForName("java.lang.Object"));
}
}
superclassInitialized = true;
if (initialized()) {
clazz = null;
}
}
public void initInterfaces() {
if (interfacesInitialized) {
return;
}
int[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
String name = clazz.classNameCP(interfaces[i]);
ct.addInterface(typeForName(name));
// ### should be a lazy ref that rather than eager
}
interfacesInitialized = true;
if (initialized()) {
clazz = null;
}
}
public void initMemberClasses() {
if (memberClassesInitialized) {
return;
}
InnerClasses innerClasses = clazz.getInnerClasses();
if (innerClasses != null) {
for (int i = 0; i < innerClasses.getClasses().length; i++) {
Info c = innerClasses.getClasses()[i];
if (c.outerClassIndex == clazz.getThisClass() && c.classIndex != 0) {
String name = clazz.classNameCP(c.classIndex);
int index = name.lastIndexOf('$');
// Skip local and anonymous classes.
if (index >= 0 && Character.isDigit(name.charAt(index + 1))) {
continue;
}
// A member class of this class
Ref<? extends Type> t = typeForName(name, ts.flagsForBits(c.modifiers));
if (reporter.should_report(Reporter.loader, 3))
reporter.report(3, "adding member " + t + " to " + ct);
ct.addMemberClass((Ref<? extends ClassType>) t);
}
}
}
memberClassesInitialized = true;
if (initialized()) {
clazz = null;
}
}
public void canonicalFields() {
initFields();
}
public void canonicalMethods() {
initMethods();
}
public void canonicalConstructors() {
initConstructors();
}
public void initFields() {
if (fieldsInitialized) {
return;
}
Field[] fields = clazz.getFields();
for (int i = 0; i < fields.length; i++) {
if (!fields[i].name().startsWith("jlc$")
&& !fields[i].isSynthetic()) {
FieldDef fi = this.fieldInstance(fields[i], ct);
if (reporter.should_report(Reporter.loader, 3))
reporter.report(3, "adding " + fi + " to " + ct);
ct.addField(fi);
}
}
fieldsInitialized = true;
if (initialized()) {
clazz = null;
}
}
public void initMethods() {
if (methodsInitialized) {
return;
}
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
if (!methods[i].name().equals("<init>")
&& !methods[i].name().equals("<clinit>")
&& !methods[i].isSynthetic()) {
MethodDef mi = this.methodInstance(methods[i], ct);
if (reporter.should_report(Reporter.loader, 3))
reporter.report(3, "adding " + mi + " to " + ct);
ct.addMethod(mi);
}
}
methodsInitialized = true;
if (initialized()) {
clazz = null;
}
}
public void initConstructors() {
if (constructorsInitialized) {
return;
}
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].name().equals("<init>")
&& !methods[i].isSynthetic()) {
ConstructorDef ci = this.constructorInstance(methods[i],
ct,
clazz.getFields());
if (reporter.should_report(Reporter.loader, 3))
reporter.report(3, "adding " + ci + " to " + ct);
ct.addConstructor(ci);
}
}
constructorsInitialized = true;
if (initialized()) {
clazz = null;
}
}
protected boolean initialized() {
return superclassInitialized && interfacesInitialized
&& memberClassesInitialized && methodsInitialized
&& fieldsInitialized && constructorsInitialized;
}
private int parseBounds(String str, Map<String,Ref<? extends Type>> bounds) {
if (str.charAt(0) != '<') return 0;
int i = 1;
while (i < str.length()) {
if (str.charAt(i) == '>') return i+1;
int start = i;
int colon = str.indexOf(':', start);
String name = str.substring(start, colon);
while (str.charAt(colon+1) == ':') colon++; // Sometimes the separator is two colons
List<Ref<? extends Type>> types = new ArrayList<Ref<? extends Type>>(1);
i = updateTypeFromString(types, str, colon+1, bounds);
assert (types.size() == 1);
bounds.put(name, types.get(0));
i++;
}
assert (str.charAt(i) == '>');
return i;
}
/**
* Create a MethodInstance.
* @param method The JVM Method data structure.
* @param ct The class containing the method.
*/
protected MethodDef methodInstance(Method method, ClassDef ct) {
Constant[] constants = clazz.getConstants();
Map<String,Ref<? extends Type>> bounds = CollectionFactory.<String,Ref<? extends Type>>newHashMap();
if (clazz.getSignature() != null) {
parseBounds((String) constants[clazz.getSignature().getSignature()].value(), bounds);
// TODO: [IP] also find and process bounds of the outer classes
}
String name = (String) constants[method.getName()].value();
int sig = method.getSignature() != null ? method.getSignature().getSignature() : method.getType();
String type = (String) constants[sig].value();
int start = parseBounds(type, bounds);
if (type.charAt(start) != '(') {
throw new ClassFormatError("Bad method type descriptor.");
}
int index = type.indexOf(')', start+1);
List<Ref<? extends Type>> argTypes = typeListForString(type.substring(start+1, index), bounds);
Ref<? extends Type> returnType = typeForString(type.substring(index+1), bounds);
List<Ref<? extends Type>> excTypes = new ArrayList<Ref<? extends Type>>();
Exceptions exceptions = method.getExceptions();
if (exceptions != null) {
int[] throwTypes = exceptions.getThrowTypes();
for (int i = 0; i < throwTypes.length; i++) {
String s = clazz.classNameCP(throwTypes[i]);
excTypes.add(typeForName(s));
}
}
return ts.methodDef(ct.position(), ct.errorPosition(), Types.ref(ct.asType()),
ts.flagsForBits(method.getModifiers()),
returnType, Name.make(name), argTypes, excTypes);
}
/**
* Create a ConstructorInstance.
* @param method The JVM Method data structure for the constructor.
* @param ct The class containing the method.
* @param fields The constructor's fields, needed to remove parameters
* passed to initialize synthetic fields.
*/
protected ConstructorDef constructorInstance(Method method, ClassDef ct, Field[] fields) {
// Get a method instance for the <init> method.
MethodDef mi = methodInstance(method, ct);
List<Ref<? extends Type>> formals = mi.formalTypes();
if (ct.isInnerClass()) {
// If an inner class, the first argument may be a reference to an
// enclosing class used to initialize a synthetic field.
// Count the number of synthetic fields.
int numSynthetic = 0;
for (int i = 0; i < fields.length; i++) {
if (fields[i].isSynthetic()) {
numSynthetic++;
}
}
// Ignore a number of parameters equal to the number of synthetic
// fields.
if (numSynthetic <= formals.size()) {
formals = formals.subList(numSynthetic, formals.size());
}
}
return ts.constructorDef(mi.position(), mi.errorPosition(), Types.ref(ct.asType()), mi.flags(),
formals, mi.throwTypes());
}
/**
* Create a FieldInstance.
* @param field The JVM Field data structure for the field.
* @param ct The class containing the field.
*/
protected FieldDef fieldInstance(Field field, ClassDef ct) {
Constant[] constants = clazz.getConstants();
Map<String,Ref<? extends Type>> bounds = CollectionFactory.<String,Ref<? extends Type>>newHashMap();
if (clazz.getSignature() != null) {
parseBounds((String) constants[clazz.getSignature().getSignature()].value(), bounds);
// TODO: [IP] also find and process bounds of the outer classes
}
String name = (String) constants[field.getName()].value();
int sig = field.getSignature() != null ? field.getSignature().getSignature() : field.getType();
String type = (String) constants[sig].value();
int start = parseBounds(type, bounds);
FieldDef fi = ts.fieldDef(ct.position(), Types.ref(ct.asType()),
ts.flagsForBits(field.getModifiers()),
typeForString(type.substring(start), bounds), Name.make(name));
if (field.isConstant()) {
Constant c = field.constantValue();
ConstantValue o = null;
try {
switch (c.tag()) {
case Constant.STRING: o = ConstantValue.makeString(field.getString()); break;
case Constant.INTEGER: o = ConstantValue.makeInt(field.getInt()); break;
case Constant.LONG: o = ConstantValue.makeLong(field.getLong()); break;
case Constant.FLOAT: o = ConstantValue.makeFloat(field.getFloat()); break;
case Constant.DOUBLE: o = ConstantValue.makeDouble(field.getDouble()); break;
}
}
catch (SemanticException e) {
throw new ClassFormatError("Unexpected constant pool entry.");
}
fi.setConstantValue(o);
return fi;
}
else {
fi.setNotConstant();
}
return fi;
}
}