/*
* 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.io.File;
import java.io.InvalidClassException;
import java.util.*;
import polyglot.frontend.*;
import polyglot.main.Reporter;
import polyglot.main.Version;
import polyglot.types.BadSerializationException;
import polyglot.types.ClassType;
import polyglot.types.ConstructorInstance;
import polyglot.types.FieldInstance;
import polyglot.types.NoClassException;
import polyglot.types.Package;
import polyglot.types.QName;
import polyglot.types.SemanticException;
import polyglot.types.TopLevelResolver;
import polyglot.types.Type;
import polyglot.types.TypeObject;
import polyglot.types.TypeSystem;
import polyglot.util.*;
import x10.types.MethodInstance;
import x10.util.CollectionFactory;
/**
* Loads class information from class files, or serialized class information
* from within class files. It does not load from source files.
* TODO: dead, remove
*/
public class LoadedClassResolver implements TopLevelResolver
{
protected final static int NOT_COMPATIBLE = -1;
protected final static int MINOR_NOT_COMPATIBLE = 1;
protected final static int COMPATIBLE = 0;
protected TypeSystem ts;
protected Reporter reporter;
protected TypeEncoder te;
protected ClassFileLoader loader;
protected ClassPathResourceLoader pathloader;
protected Version version;
protected Set<QName> nocache;
protected boolean allowRawClasses;
protected final static Collection<String> report_topics = CollectionUtil.list(
Reporter.types, Reporter.resolver, Reporter.loader);
/**
* Create a loaded class resolver.
* @param ts The type system
* @param classpath The class path
* @param loader The class file loader to use.
* @param version The version of classes to load.
* @param allowRawClasses allow class files without encoded type information
*/
public LoadedClassResolver(TypeSystem ts, String classpath,
ClassFileLoader loader, Version version,
boolean allowRawClasses)
{
this.ts = ts;
this.reporter = ts.extensionInfo().getOptions().reporter;
this.te = new TypeEncoder(ts);
this.loader = loader;
this.pathloader = new ClassPathResourceLoader(classpath, this.reporter);
this.version = version;
this.nocache = CollectionFactory.newHashSet();
this.allowRawClasses = allowRawClasses;
}
public boolean allowRawClasses() {
return allowRawClasses;
}
public Package findPackage(QName name) throws SemanticException {
if (packageExists(name))
return ts.createPackage(name);
throw new SemanticException("Package "+name+" not found");
}
public boolean packageExists(QName name) {
return pathloader.dirExists(name.toString().replace('.', File.separatorChar));
}
/**
* Load a class file for class <code>name</code>.
*/
protected ClassFile loadFile(QName name) {
if (nocache.contains(name)) {
return null;
}
try {
String classFileName = name.toString().replace('.', File.separatorChar) + ".class";
Resource r = pathloader.loadResource(classFileName);
ClassFile clazz = loader.loadClass(r);
if (clazz == null) {
if (reporter.should_report(report_topics, 4)) {
reporter.report(4, "Class " + name + " not found in classpath "
+ pathloader.classpath());
}
}
else {
if (reporter.should_report(report_topics, 4)) {
reporter.report(4, "Class " + name + " found in classpath "
+ pathloader.classpath());
}
return clazz;
}
}
catch (ClassFormatError e) {
if (reporter.should_report(report_topics, 4))
reporter.report(4, "Class " + name + " format error");
}
nocache.add(name);
return null;
}
/**
* Find a type by name.
*/
public List<Type> find(QName name) throws SemanticException {
if (reporter.should_report(report_topics, 3))
reporter.report(3, "LoadedCR.find(" + name + ")");
Type result = null;
// First try the class file.
ClassFile clazz = loadFile(name);
if (clazz == null) {
throw new NoClassException(name.toString());
}
// Check for encoded type information.
if (clazz.encodedClassType(version.name()) != null) {
if (reporter.should_report(report_topics, 4))
reporter.report(4, "Using encoded class type for " + name);
result = getEncodedType(clazz, name);
}
if (allowRawClasses) {
if (reporter.should_report(report_topics, 4))
reporter.report(4, "Using raw class file for " + name);
result = new ClassFileLazyClassInitializer(clazz, ts).type().asType();
}
// Verify that the type we loaded has the right name. This prevents,
// for example, requesting a type through its mangled (class file) name.
if (result != null) {
if (name.equals(result.fullName())) {
return CollectionUtil.<Type>list(result);
}
if (result instanceof ClassType && name.equals(ts.getTransformedClassName(((ClassType) result).def()))) {
return CollectionUtil.<Type>list(result);
}
}
// We have a raw class, but are not allowed to use it, and
// cannot find appropriate encoded info.
throw new SemanticException("Unable to find a suitable definition of \""
+ name +"\". A class file was found,"
+ " but it did not contain appropriate information for this"
+ " language extension. If the source for this file is written"
+ " in the language extension, try recompiling the source code.");
}
/**
* Find a single type by name.
*/
public Type findOne(QName name) throws SemanticException {
List<Type> res = find(name);
if (res == null || res.size() != 1)
throw new InternalCompilerError("Unexpected result when looking up "+name+": "+res);
return res.get(0);
}
protected boolean recursive = false;
/**
* Extract an encoded type from a class file.
*/
protected ClassType getEncodedType(ClassFile clazz, QName name)
throws SemanticException
{
// At this point we've decided to go with the Class. So if something
// goes wrong here, we have only one choice, to throw an exception.
// Check to see if it has serialized info. If so then check the
// version.
int comp = checkCompilerVersion(clazz.compilerVersion(version.name()));
if (comp == NOT_COMPATIBLE) {
throw new SemanticException("Unable to find a suitable definition of "
+ clazz.name()
+ ". Try recompiling or obtaining "
+ " a newer version of the class file.");
}
// Alright, go with it!
TypeObject dt;
try {
if (reporter.should_report(Reporter.serialize, 1))
reporter.report(1, "Decoding " + name + " in " + clazz);
dt = te.decode(clazz.encodedClassType(version.name()), name);
if (dt == null) {
if (reporter.should_report(Reporter.serialize, 1))
reporter.report(1, "* Decoding " + name + " failed");
// Deserialization failed because one or more types could not
// be resolved. Abort this pass. Dependencies have already
// been set up so that this goal will be reattempted after
// the types are resolved.
throw new SchedulerException("Could not decode " + name);
}
}
catch (InternalCompilerError e) {
throw e;
}
catch (InvalidClassException e) {
throw new BadSerializationException(clazz.name());
}
if (dt instanceof ClassType) {
ClassType ct = (ClassType) dt;
// Install the decoded type into the *new* system resolver.
// It will be installed into the old resolver below by putAll.
ts.systemResolver().addNamed(name, ct);
if (reporter.should_report(Reporter.serialize, 1))
reporter.report(1, "* Decoding " + name + " succeeded");
if (reporter.should_report("typedump", 1)) {
new ObjectDumper(new SimpleCodeWriter(System.out, 72)).dump(dt);
}
if (reporter.should_report(Reporter.serialize, 2)) {
for (MethodInstance mi : ct.methods()) {
reporter.report(2, "* " + mi);
}
for (Iterator<FieldInstance> i = ct.fields().iterator(); i.hasNext(); ) {
FieldInstance fi = (FieldInstance) i.next();
reporter.report(2, "* " + fi);
}
for (Iterator<ConstructorInstance> i = ct.constructors().iterator(); i.hasNext(); ) {
ConstructorInstance ci = (ConstructorInstance) i.next();
reporter.report(2, "* " + ci);
}
}
if (reporter.should_report(report_topics, 2))
reporter.report(2, "Returning serialized ClassType for " +
clazz.name() + ".");
return ct;
}
else {
throw new SemanticException("Class " + name + " not found in " + clazz.name() + ".");
}
}
/**
* Compare the encoded type's version against the loader's version.
*/
protected int checkCompilerVersion(String clazzVersion) {
if (clazzVersion == null) {
return NOT_COMPATIBLE;
}
StringTokenizer st = new StringTokenizer(clazzVersion, ".");
try {
int v;
v = Integer.parseInt(st.nextToken());
Version version = this.version;
if (v != version.major()) {
// Incompatible.
return NOT_COMPATIBLE;
}
v = Integer.parseInt(st.nextToken());
if (v != version.minor()) {
// Not the best option, but will work if its the only one.
return MINOR_NOT_COMPATIBLE;
}
}
catch (NumberFormatException e) {
return NOT_COMPATIBLE;
}
// Everything is way cool.
return COMPATIBLE;
}
}