/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.symboltable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
import net.sourceforge.pmd.lang.symboltable.Applier;
import net.sourceforge.pmd.lang.symboltable.ImageFinderFunction;
import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.lang.symboltable.Scope;
/**
* This scope is the outer most scope of a Java file. A Source File can contain
* one ore more classes.
*/
public class SourceFileScope extends AbstractJavaScope {
private final String packageImage;
private final TypeSet types;
private Map<String, Node> qualifiedTypeNames;
public SourceFileScope(final ClassLoader classLoader) {
this(classLoader, "");
}
public SourceFileScope(final ClassLoader classLoader, final String packageImage) {
this.types = new TypeSet(classLoader);
this.packageImage = packageImage;
types.setASTCompilationUnitPackage(packageImage);
}
/**
* Configures the type resolution for the symbol table.
*
* @param imports
* the import declarations
*/
public void configureImports(final List<ASTImportDeclaration> imports) {
for (ASTImportDeclaration i : imports) {
if (i.isImportOnDemand()) {
types.addImport(i.getImportedName() + ".*");
} else {
types.addImport(i.getImportedName());
}
}
}
public Set<String> getExplicitImports() {
return types.getExplicitImports();
}
/**
* Whether an auxclasspath has been configured or not. This can be used to
* enable/disable more detailed symbol table analysis and type resolution
* can be used - or to fall back to more simple implementation.
*
* @return <code>true</code> if the auxclasspath is configured and types can
* be resolved reliably.
* @see #resolveType(String)
*/
public boolean hasAuxclasspath() {
return types.hasAuxclasspath();
}
/**
* Tries to resolve a class by name.
*
* @param name
* the name of the class
* @return the class or <code>null</code> if no class could be found
*/
public Class<?> resolveType(String name) {
return types.findClass(name);
}
public String getPackageName() {
return packageImage;
}
/**
* {@inheritDoc}
*
* @throws IllegalArgumentException
* if declaration is not a {@link ClassNameDeclaration}
*/
@Override
public void addDeclaration(NameDeclaration declaration) {
if (!(declaration instanceof ClassNameDeclaration)) {
throw new IllegalArgumentException("A SourceFileScope can only contain classes.");
}
super.addDeclaration(declaration);
}
/**
* Convenience method that casts the declarations to
* {@link ClassNameDeclaration}s.
*
* @see #getDeclarations()
* @return all class name declarations
*/
public Map<ClassNameDeclaration, List<NameOccurrence>> getClassDeclarations() {
return getDeclarations(ClassNameDeclaration.class);
}
public String toString() {
return "SourceFileScope: " + glomNames(getClassDeclarations().keySet());
}
public ClassNameDeclaration findClassNameDeclaration(String name) {
ImageFinderFunction finder = new ImageFinderFunction(name);
Applier.apply(finder, getClassDeclarations().keySet().iterator());
return (ClassNameDeclaration) finder.getDecl();
}
protected Set<NameDeclaration> findVariableHere(JavaNameOccurrence occ) {
ImageFinderFunction finder = new ImageFinderFunction(occ.getImage());
Applier.apply(finder, getDeclarations().keySet().iterator());
if (finder.getDecl() != null) {
return Collections.singleton(finder.getDecl());
}
return Collections.emptySet();
}
/**
* Returns a set of all types defined within this source file. This includes
* all top-level types and nested types.
*
* @return set of all types in this source file.
*/
public Map<String, Node> getQualifiedTypeNames() {
if (qualifiedTypeNames != null) {
return qualifiedTypeNames;
}
qualifiedTypeNames = getSubTypes(null, this);
return qualifiedTypeNames;
}
private Map<String, Node> getSubTypes(String qualifyingName, Scope subType) {
Set<ClassNameDeclaration> classDeclarations = subType.getDeclarations(ClassNameDeclaration.class).keySet();
if (classDeclarations.isEmpty()) {
return Collections.emptyMap();
}
Map<String, Node> types = new HashMap<>((int) (classDeclarations.size() / 0.75f) + 1);
for (ClassNameDeclaration c : classDeclarations) {
String typeName = c.getName();
if (qualifyingName != null) {
typeName = qualifyingName + "." + typeName;
}
types.put(typeName, c.getNode());
types.putAll(getSubTypes(typeName, c.getScope()));
}
return types;
}
}