/*
* 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;
import java.util.*;
import polyglot.main.Reporter;
import polyglot.util.*;
import x10.util.CollectionFactory;
/**
* An <code>ImportTable</code> is a type of <code>ClassResolver</code> that
* corresponds to a particular source file.
* <p>
* It has a set of package and class imports, which caches the results of
* lookups for future reference.
*/
public class ImportTable implements Resolver
{
protected TypeSystem ts;
protected Reporter reporter;
/** Map from names to classes found, or to the NOT_FOUND object. */
protected Map<Object,Option<List<Type>>> map;
/** A list of all on-demand imports. */
protected List<QName> onDemandImports;
/** Parallel list of the positions of the on-demand imports. */
protected List<Position> onDemandImportPositions;
/** List of explicit imports. */
protected List<QName> explicitImports;
/** Parallel list of positions for lazyImports. */
protected List<Position> explicitImportPositions;
/** List of explicitly imported names added to the table or pending in
* the lazyImports list. */
protected String sourceName;
/** Position to use for error reporting */
protected Position sourcePos;
/** Our package */
protected Ref<? extends Package> pkg;
private static final Option<List<Type>> NOT_FOUND = Option.<List<Type>>None();
/**
* Create an import table.
* @param ts The type system
* @param pkg The package of the source we are importing types into.
*/
public ImportTable(TypeSystem ts, Ref<? extends Package> pkg) {
this(ts, pkg, null);
}
/**
* Create an import table.
* @param ts The type system
* @param pkg The package of the source we are importing types into.
* @param src The name of the source file we are importing into.
*/
public ImportTable(TypeSystem ts, Ref<? extends Package> pkg, String src) {
this.ts = ts;
this.sourceName = src;
this.sourcePos = src != null ? new Position(null, src) : null;
this.pkg = pkg;
this.map = CollectionFactory.newHashMap();
this.onDemandImports = new ArrayList<QName>();
this.onDemandImportPositions = new ArrayList<Position>();
this.explicitImports = new ArrayList<QName>();
this.explicitImportPositions = new ArrayList<Position>();
this.reporter = ts.extensionInfo().getOptions().reporter;
}
/**
* The package of the source we are importing types into.
*/
public Ref<? extends Package> package_() {
return pkg;
}
/**
* Add a class import.
*/
public void addExplicitImport(QName name) {
addExplicitImport(name, null);
}
/**
* Add a class import.
*/
public void addExplicitImport(QName name, Position pos) {
if (reporter.should_report(TOPICS, 2))
reporter.report(2, this + ": lazy import " + name);
explicitImports.add(name);
explicitImportPositions.add(pos);
}
/**
* Add a package import.
*/
public void addOnDemandImport(QName containerName, Position pos) {
// don't add the import if it is the same as the current package,
// the same as a default import, or has already been imported
if ((pkg != null && pkg.get().fullName().equals(containerName)) ||
ts.defaultOnDemandImports().contains(containerName) ||
onDemandImports.contains(containerName)) {
return;
}
onDemandImports.add(containerName);
onDemandImportPositions.add(pos);
}
/**
* Add a package import.
*/
public void addOnDemandImport(QName containerName) {
addOnDemandImport(containerName, null);
}
/**
* List the names we import from.
*/
public List<QName> onDemandImports() {
return onDemandImports;
}
/**
* List the classes explicitly imported.
*/
public List<QName> explicitImports() {
return explicitImports;
}
/**
* The name of the source file we are importing into.
*/
public String sourceName() {
return sourceName;
}
/**
* Find a type by name, using the cache and the outer resolver,
* but not the import table.
*/
protected List<Type> cachedFind(Name name) throws SemanticException {
Option<List<Type>> res = map.get(name);
if (res != null && res != NOT_FOUND) {
return res.get();
}
List<Type> t = ts.systemResolver().find(QName.make(null, name)); // NOTE: short name
map.put(name, Option.<List<Type>>Some(t));
return t;
}
/**
* Find a type by name, searching the import table.
*/
public List<Type> find(Matcher<Type> matcher) throws SemanticException {
if (reporter.should_report(TOPICS, 2))
reporter.report(2, this + ".find(" + matcher.signature() + ")");
// The class name is short.
// First see if we have a mapping already.
Option<List<Type>> res = matcher.key() != null ? map.get(matcher.key()) : null;
if (res != null) {
if (res == NOT_FOUND) {
throw new NoClassException(matcher.name().toString(), sourcePos);
}
return res.get();
}
SemanticException ex = null;
List<Type> resolved = null;
try {
resolved = lookupExplicit(matcher);
}
catch (NoClassException e) {
ex = e;
}
if (resolved == null) {
Package p = Types.get(pkg);
// Check if the current package defines it.
// If so, this takes priority over the package imports (or
// "type-import-on-demand" declarations as they are called in
// the JLS), so even if another package defines the same name,
// there is no conflict. See Section 6.5.2 of JLS, 2nd Ed.
QName containerName = p != null ? p.fullName() : null;
Position pos = null;
resolved = findInContainer(matcher, containerName, pos);
}
// It wasn't an explicit import. Maybe it was on-demand?
if (resolved == null) {
try {
resolved = lookupOnDemand(matcher);
}
catch (NoClassException e) {
ex = e;
}
}
if (matcher.key() != null) {
if (resolved != null) {
map.put(matcher.key(), Option.<List<Type>>Some(resolved));
}
else {
map.put(matcher.key(), NOT_FOUND);
}
}
if (resolved != null)
return resolved;
if (ex != null)
throw ex;
throw new NoClassException(matcher.name().toString(), sourcePos);
}
protected List<Type> lookupOnDemand(Matcher<Type> matcher) throws SemanticException, NoClassException {
List<QName> imports = new ArrayList<QName>(onDemandImports.size() + 5);
List<Position> positions = new ArrayList<Position>(onDemandImports.size() + 5);
// Next search the default imports (e.g., java.lang)
imports.addAll(ts.defaultOnDemandImports());
positions.addAll(Arrays.asList(new Position[imports.size()]));
// Then search the explicit p.* imports.
imports.addAll(onDemandImports);
positions.addAll(onDemandImportPositions);
assert imports.size() == positions.size();
List<Type> resolved = null;
Set<QName> tried = CollectionFactory.newHashSet();
for (int i = 0; i < imports.size(); i++) {
QName containerName = imports.get(i);
Position pos = positions.get(i);
if (tried.contains(containerName))
continue;
tried.add(containerName);
List<Type> n = findInContainer(matcher, containerName, pos);
if (n != null) {
if (resolved == null) {
// This is the first occurrence of name we've found
// in a package import.
// Record it, and keep going, to see if there
// are any conflicts.
resolved = n;
}
else {
// This is the 2nd occurrence of name we've found
// in an imported package.
// That's bad.
throw new SemanticException("Reference to " +
matcher.signature() + " is ambiguous; both " +
resolved + " and " + n + " match.");
}
}
}
// // Search the empty package only if not already found.
// if (resolved == null) {
// QName containerName = null;
// Position pos = null;
//
// if (! tried.contains(containerName)) {
//
// Named n = findInContainer(matcher, containerName, pos);
//
// if (n != null) {
// resolved = n;
// }
// }
// }
return resolved;
}
protected List<Type> findInContainer(Matcher<Type> matcher, QName containerName, Position containerPos) throws SemanticException {
List<Type> resolved = null;
if (containerName != null) {
// Find the container, then search the container.
Resolver r = null;
// We don't allow packages and classes to have the same name. So, only look
// for a class if a package is not found.
try {
Package pkgContainer = ts.systemResolver().findPackage(containerName);
r = ts.packageContextResolver(pkgContainer);
}
catch (SemanticException e) {
try {
List<Type> containers = ts.systemResolver().find(containerName);
if (containers != null) {
for (Type container : containers) {
if (container instanceof ClassType) {
r = ts.classContextResolver(container);
break;
}
}
}
}
catch (SemanticException z) {
}
}
if (r == null)
return null;
try {
resolved = r.find(matcher);
}
catch (SemanticException e) {
}
}
if (resolved == null) {
Name name = matcher.name();
try {
resolved = ts.systemResolver().find(QName.make(containerName, name));
// Now verify that what we found actually matches.
if (resolved != null) {
List<Type> newResolved = new ArrayList<Type>();
for (Type t : resolved) {
t = matcher.instantiate(t);
if (t != null)
newResolved.add(t);
}
if (newResolved.isEmpty()) {
resolved = null;
} else {
resolved = newResolved;
}
}
}
catch (SemanticException e) {
return null;
}
}
// Found something. Now make sure we can actually see it.
if (resolved != null) {
List<Type> newResolved = new ArrayList<Type>();
for (Type t : resolved) {
if (isVisibleFromThisPackage(t, containerName))
newResolved.add(t);
}
if (!newResolved.isEmpty())
return newResolved;
}
return null;
}
/**
* Return whether <code>n</code> in package <code>pkgName</code> is visible from within
* package <code>pkg</code>. The empty string may
* be passed in to represent the default package.
*/
protected boolean isVisibleFromThisPackage(Type t, QName containerName) {
boolean isVisible = false;
Package p = Types.get(this.pkg);
boolean inSamePackage =
p != null && p.fullName().equals(containerName)
|| p == null && containerName == null;
if (inSamePackage) {
isVisible = true;
}
else {
if (t.isClass()) {
ClassType ct = t.toClass();
isVisible = ts.classAccessibleFromPackage(ct.def(), Types.get(pkg));
}
else {
// Assume non-class types are always visible.
isVisible = true;
}
}
return isVisible;
}
protected List<Type> lookupExplicit(Matcher<Type> matcher) throws SemanticException {
Set<QName> tried = CollectionFactory.newHashSet();
for (int i = 0; i < explicitImports.size(); i++) {
QName longName = explicitImports.get(i);
Position pos = explicitImportPositions.get(i);
if (tried.contains(longName))
continue;
tried.add(longName);
if (reporter.should_report(TOPICS, 2))
reporter.report(2, this + ": import " + longName);
if (longName.name().equals(matcher.name()))
return findInContainer(matcher, longName.qualifier(), pos);
}
return null;
}
public String toString() {
if (sourceName != null) {
return "(import " + sourceName + ")";
}
else {
return "(import)";
}
}
private static final Collection<String> TOPICS =
CollectionUtil.list(Reporter.types, Reporter.resolver, Reporter.imports);
}