/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.symboltable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTExtendsList;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType;
import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameter;
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.JavaParserTreeConstants;
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 represents one Java class. It can have variable declarations,
* method declarations and inner class declarations.
*/
public class ClassScope extends AbstractJavaScope {
private static final Set<String> PRIMITIVE_TYPES;
static {
PRIMITIVE_TYPES = new HashSet<>();
PRIMITIVE_TYPES.add("boolean");
PRIMITIVE_TYPES.add("char");
PRIMITIVE_TYPES.add("byte");
PRIMITIVE_TYPES.add("short");
PRIMITIVE_TYPES.add("int");
PRIMITIVE_TYPES.add("long");
PRIMITIVE_TYPES.add("float");
PRIMITIVE_TYPES.add("double");
}
// FIXME - this breaks given sufficiently nested code
private static ThreadLocal<Integer> anonymousInnerClassCounter = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return Integer.valueOf(1);
}
};
private final String className;
private boolean isEnum;
/**
* The current class scope declaration. Technically it belongs to out parent scope,
* but knowing it we can better resolve this, super and direct class references such as Foo.X
*/
private final ClassNameDeclaration classDeclaration;
public ClassScope(final String className, final ClassNameDeclaration classNameDeclaration) {
this.className = Objects.requireNonNull(className);
anonymousInnerClassCounter.set(Integer.valueOf(1));
this.classDeclaration = classNameDeclaration;
}
/**
* This is only for anonymous inner classes.
*
* <p>FIXME - should have name like Foo$1, not Anonymous$1 to get this working
* right, the parent scope needs to be passed in when instantiating a
* ClassScope</p>
*
* @param classNameDeclaration The declaration of this class, as known to the parent scope.
*/
public ClassScope(final ClassNameDeclaration classNameDeclaration) {
// this.className = getParent().getEnclosingClassScope().getClassName()
// + "$" + String.valueOf(anonymousInnerClassCounter);
int v = anonymousInnerClassCounter.get().intValue();
this.className = "Anonymous$" + v;
anonymousInnerClassCounter.set(v + 1);
classDeclaration = classNameDeclaration;
}
public void setIsEnum(boolean isEnum) {
this.isEnum = isEnum;
}
public Map<ClassNameDeclaration, List<NameOccurrence>> getClassDeclarations() {
return getDeclarations(ClassNameDeclaration.class);
}
public Map<MethodNameDeclaration, List<NameOccurrence>> getMethodDeclarations() {
return getDeclarations(MethodNameDeclaration.class);
}
public Map<VariableNameDeclaration, List<NameOccurrence>> getVariableDeclarations() {
return getDeclarations(VariableNameDeclaration.class);
}
public Set<NameDeclaration> addNameOccurrence(NameOccurrence occurrence) {
JavaNameOccurrence javaOccurrence = (JavaNameOccurrence) occurrence;
Set<NameDeclaration> declarations = findVariableHere(javaOccurrence);
if (!declarations.isEmpty()
&& (javaOccurrence.isMethodOrConstructorInvocation() || javaOccurrence.isMethodReference())) {
for (NameDeclaration decl : declarations) {
List<NameOccurrence> nameOccurrences = getMethodDeclarations().get(decl);
if (nameOccurrences == null) {
// TODO may be a class name: Foo.this.super();
// search inner classes
for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
Scope innerClassScope = innerClass.getScope();
if (innerClassScope.contains(javaOccurrence)) {
innerClassScope.addNameOccurrence(javaOccurrence);
}
}
} else {
nameOccurrences.add(javaOccurrence);
Node n = javaOccurrence.getLocation();
if (n instanceof ASTName) {
((ASTName) n).setNameDeclaration(decl);
} // TODO what to do with PrimarySuffix case?
}
}
} else if (!declarations.isEmpty() && !javaOccurrence.isThisOrSuper()) {
for (NameDeclaration decl : declarations) {
List<NameOccurrence> nameOccurrences = getVariableDeclarations().get(decl);
if (nameOccurrences == null) {
// TODO may be a class name
// search inner classes
for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
Scope innerClassScope = innerClass.getScope();
if (innerClassScope.contains(javaOccurrence)) {
innerClassScope.addNameOccurrence(javaOccurrence);
}
}
} else {
nameOccurrences.add(javaOccurrence);
Node n = javaOccurrence.getLocation();
if (n instanceof ASTName) {
((ASTName) n).setNameDeclaration(decl);
} // TODO what to do with PrimarySuffix case?
}
}
}
return declarations;
}
public String getClassName() {
return this.className;
}
protected Set<NameDeclaration> findVariableHere(JavaNameOccurrence occurrence) {
if (occurrence.isThisOrSuper() || className.equals(occurrence.getImage())) {
// Reference to ourselves!
return Collections.<NameDeclaration>singleton(classDeclaration);
}
Map<MethodNameDeclaration, List<NameOccurrence>> methodDeclarations = getMethodDeclarations();
Set<NameDeclaration> result = new HashSet<>();
if (occurrence.isMethodOrConstructorInvocation()) {
final boolean hasAuxclasspath = getEnclosingScope(SourceFileScope.class).hasAuxclasspath();
matchMethodDeclaration(occurrence, methodDeclarations.keySet(), hasAuxclasspath, result);
if (isEnum && "valueOf".equals(occurrence.getImage())) {
result.add(createBuiltInMethodDeclaration("valueOf", "String"));
}
if (result.isEmpty()) {
for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
matchMethodDeclaration(occurrence, innerClass.getScope().getDeclarations(MethodNameDeclaration.class).keySet(), hasAuxclasspath, result);
}
}
return result;
}
if (occurrence.isMethodReference()) {
for (MethodNameDeclaration mnd : methodDeclarations.keySet()) {
if (mnd.getImage().equals(occurrence.getImage())) {
result.add(mnd);
}
}
return result;
}
List<String> images = new ArrayList<>();
if (occurrence.getImage() != null) {
images.add(occurrence.getImage());
if (occurrence.getImage().startsWith(className)) {
images.add(clipClassName(occurrence.getImage()));
}
}
Map<VariableNameDeclaration, List<NameOccurrence>> variableDeclarations = getVariableDeclarations();
ImageFinderFunction finder = new ImageFinderFunction(images);
Applier.apply(finder, variableDeclarations.keySet().iterator());
if (finder.getDecl() != null) {
result.add(finder.getDecl());
}
// search inner classes
Map<ClassNameDeclaration, List<NameOccurrence>> classDeclarations = getClassDeclarations();
if (result.isEmpty() && !classDeclarations.isEmpty()) {
for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
Applier.apply(finder, innerClass.getScope().getDeclarations(VariableNameDeclaration.class).keySet().iterator());
if (finder.getDecl() != null) {
result.add(finder.getDecl());
}
}
}
return result;
}
private void matchMethodDeclaration(JavaNameOccurrence occurrence,
Set<MethodNameDeclaration> methodDeclarations, final boolean hasAuxclasspath,
Set<NameDeclaration> result) {
for (MethodNameDeclaration mnd : methodDeclarations) {
if (mnd.getImage().equals(occurrence.getImage())) {
List<TypedNameDeclaration> parameterTypes = determineParameterTypes(mnd);
List<TypedNameDeclaration> argumentTypes = determineArgumentTypes(occurrence, parameterTypes);
if (!mnd.isVarargs() && occurrence.getArgumentCount() == mnd.getParameterCount()
&& (!hasAuxclasspath || parameterTypes.equals(argumentTypes))) {
result.add(mnd);
} else if (mnd.isVarargs()) {
int varArgIndex = parameterTypes.size() - 1;
TypedNameDeclaration varArgType = parameterTypes.get(varArgIndex);
// first parameter is varArg, calling method might have
// 0 or more arguments
// or the calling method has enough arguments to fill in
// the parameters before the vararg
if ((varArgIndex == 0 || argumentTypes.size() >= varArgIndex)
&& (!hasAuxclasspath || parameterTypes
.subList(0, varArgIndex).equals(argumentTypes.subList(0, varArgIndex)))) {
if (!hasAuxclasspath) {
result.add(mnd);
continue;
}
boolean sameType = true;
for (int i = varArgIndex; i < argumentTypes.size(); i++) {
if (!varArgType.equals(argumentTypes.get(i))) {
sameType = false;
break;
}
}
if (sameType) {
result.add(mnd);
}
}
}
}
}
}
/**
* Creates a fake method name declaration for built-in methods from Java
* like the Enum Method "valueOf".
*
* @param methodName
* the method name
* @param parameterTypes
* the reference types of each parameter of the method
* @return a method name declaration
*/
private MethodNameDeclaration createBuiltInMethodDeclaration(final String methodName,
final String... parameterTypes) {
ASTMethodDeclaration methodDeclaration = new ASTMethodDeclaration(JavaParserTreeConstants.JJTMETHODDECLARATION);
methodDeclaration.setPublic(true);
methodDeclaration.setScope(this);
ASTMethodDeclarator methodDeclarator = new ASTMethodDeclarator(JavaParserTreeConstants.JJTMETHODDECLARATOR);
methodDeclarator.setImage(methodName);
methodDeclarator.setScope(this);
ASTFormalParameters formalParameters = new ASTFormalParameters(JavaParserTreeConstants.JJTFORMALPARAMETERS);
formalParameters.setScope(this);
methodDeclaration.jjtAddChild(methodDeclarator, 0);
methodDeclarator.jjtSetParent(methodDeclaration);
methodDeclarator.jjtAddChild(formalParameters, 0);
formalParameters.jjtSetParent(methodDeclarator);
/*
* jjtAddChild resizes it's child node list according to known indexes.
* Going backwards makes sure the first time it gets the right size avoiding copies.
*/
for (int i = parameterTypes.length - 1; i >= 0; i--) {
ASTFormalParameter formalParameter = new ASTFormalParameter(JavaParserTreeConstants.JJTFORMALPARAMETER);
formalParameters.jjtAddChild(formalParameter, i);
formalParameter.jjtSetParent(formalParameters);
ASTVariableDeclaratorId variableDeclaratorId = new ASTVariableDeclaratorId(
JavaParserTreeConstants.JJTVARIABLEDECLARATORID);
variableDeclaratorId.setImage("arg" + i);
formalParameter.jjtAddChild(variableDeclaratorId, 1);
variableDeclaratorId.jjtSetParent(formalParameter);
ASTType type = new ASTType(JavaParserTreeConstants.JJTTYPE);
formalParameter.jjtAddChild(type, 0);
type.jjtSetParent(formalParameter);
if (PRIMITIVE_TYPES.contains(parameterTypes[i])) {
ASTPrimitiveType primitiveType = new ASTPrimitiveType(JavaParserTreeConstants.JJTPRIMITIVETYPE);
primitiveType.setImage(parameterTypes[i]);
type.jjtAddChild(primitiveType, 0);
primitiveType.jjtSetParent(type);
} else {
ASTReferenceType referenceType = new ASTReferenceType(JavaParserTreeConstants.JJTREFERENCETYPE);
type.jjtAddChild(referenceType, 0);
referenceType.jjtSetParent(type);
// TODO : this could actually be a primitive array...
ASTClassOrInterfaceType classOrInterfaceType = new ASTClassOrInterfaceType(
JavaParserTreeConstants.JJTCLASSORINTERFACETYPE);
classOrInterfaceType.setImage(parameterTypes[i]);
referenceType.jjtAddChild(classOrInterfaceType, 0);
classOrInterfaceType.jjtSetParent(referenceType);
}
}
return new MethodNameDeclaration(methodDeclarator);
}
/**
* Provide a list of types of the parameters of the given method
* declaration. The types are simple type images.
*
* @param mnd
* the method declaration.
* @return List of types
*/
private List<TypedNameDeclaration> determineParameterTypes(MethodNameDeclaration mnd) {
List<ASTFormalParameter> parameters = mnd.getMethodNameDeclaratorNode()
.findDescendantsOfType(ASTFormalParameter.class);
if (parameters.isEmpty()) {
return Collections.emptyList();
}
List<TypedNameDeclaration> parameterTypes = new ArrayList<>(parameters.size());
SourceFileScope fileScope = getEnclosingScope(SourceFileScope.class);
Map<String, Node> qualifiedTypeNames = fileScope.getQualifiedTypeNames();
for (ASTFormalParameter p : parameters) {
String typeImage = p.getTypeNode().getTypeImage();
// typeImage might be qualified/unqualified. If it refers to a type,
// defined in the same toplevel class,
// we should normalize the name here.
// It might also refer to a type, that is imported.
typeImage = qualifyTypeName(typeImage);
Node declaringNode = qualifiedTypeNames.get(typeImage);
Class<?> resolvedType = fileScope.resolveType(typeImage);
if (resolvedType == null) {
resolvedType = resolveGenericType(p, typeImage);
}
parameterTypes.add(new SimpleTypedNameDeclaration(typeImage, resolvedType, determineSuper(declaringNode)));
}
return parameterTypes;
}
private String qualifyTypeName(String typeImage) {
if (typeImage == null) {
return null;
}
final SourceFileScope fileScope = getEnclosingScope(SourceFileScope.class);
// Is it an inner class being accessed?
String qualified = findQualifiedName(typeImage, fileScope.getQualifiedTypeNames().keySet());
if (qualified != null) {
return qualified;
}
// Is it an explicit import?
qualified = findQualifiedName(typeImage, fileScope.getExplicitImports());
if (qualified != null) {
return qualified;
}
// Is it an inner class of an explicit import?
int dotIndex = typeImage.indexOf('.');
if (dotIndex != -1) {
qualified = findQualifiedName(typeImage.substring(0, dotIndex), fileScope.getExplicitImports());
if (qualified != null) {
return qualified.concat(typeImage.substring(dotIndex));
}
}
return typeImage;
}
private String findQualifiedName(String typeImage, Set<String> candidates) {
int nameLength = typeImage.length();
for (String qualified : candidates) {
int fullLength = qualified.length();
if (qualified.endsWith(typeImage)
&& (fullLength == nameLength || qualified.charAt(fullLength - nameLength - 1) == '.')) {
return qualified;
}
}
return null;
}
/**
* Provide a list of types of the arguments of the given method call. The
* types are simple type images. If the argument type cannot be determined
* (e.g. because it is itself the result of a method call), the parameter
* type is used - so it is assumed, it is of the correct type. This might
* cause confusion when methods are overloaded.
*
* @param occurrence
* the method call
* @param parameterTypes
* the parameter types of the called method
* @return the list of argument types
*/
private List<TypedNameDeclaration> determineArgumentTypes(JavaNameOccurrence occurrence,
List<TypedNameDeclaration> parameterTypes) {
ASTArgumentList arguments = null;
Node nextSibling;
if (occurrence.getLocation() instanceof ASTPrimarySuffix) {
nextSibling = getNextSibling(occurrence.getLocation());
} else {
nextSibling = getNextSibling(occurrence.getLocation().jjtGetParent());
}
if (nextSibling != null) {
arguments = nextSibling.getFirstDescendantOfType(ASTArgumentList.class);
}
if (arguments == null) {
return Collections.emptyList();
}
List<TypedNameDeclaration> argumentTypes = new ArrayList<>(arguments.jjtGetNumChildren());
Map<String, Node> qualifiedTypeNames = getEnclosingScope(SourceFileScope.class).getQualifiedTypeNames();
for (int i = 0; i < arguments.jjtGetNumChildren(); i++) {
Node argument = arguments.jjtGetChild(i);
Node child = null;
boolean isMethodCall = false;
if (argument.jjtGetNumChildren() > 0 && argument.jjtGetChild(0).jjtGetNumChildren() > 0
&& argument.jjtGetChild(0).jjtGetChild(0).jjtGetNumChildren() > 0) {
child = argument.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0);
isMethodCall = argument.jjtGetChild(0).jjtGetNumChildren() > 1;
}
TypedNameDeclaration type = null;
if (child instanceof ASTName && !isMethodCall) {
ASTName name = (ASTName) child;
Scope s = name.getScope();
final JavaNameOccurrence nameOccurrence = new JavaNameOccurrence(name, name.getImage());
while (s != null) {
if (s.contains(nameOccurrence)) {
break;
}
s = s.getParent();
}
if (s != null) {
Map<VariableNameDeclaration, List<NameOccurrence>> vars = s
.getDeclarations(VariableNameDeclaration.class);
for (VariableNameDeclaration d : vars.keySet()) {
// in case of simple lambda expression, the type
// might be unknown
if (d.getImage().equals(name.getImage()) && d.getTypeImage() != null) {
String typeName = d.getTypeImage();
typeName = qualifyTypeName(typeName);
Node declaringNode = qualifiedTypeNames.get(typeName);
type = new SimpleTypedNameDeclaration(typeName,
this.getEnclosingScope(SourceFileScope.class).resolveType(typeName),
determineSuper(declaringNode));
break;
}
}
}
} else if (child instanceof ASTLiteral) {
ASTLiteral literal = (ASTLiteral) child;
if (literal.isCharLiteral()) {
type = new SimpleTypedNameDeclaration("char", literal.getType());
} else if (literal.isStringLiteral()) {
type = new SimpleTypedNameDeclaration("String", literal.getType());
} else if (literal.isFloatLiteral()) {
type = new SimpleTypedNameDeclaration("float", literal.getType());
} else if (literal.isDoubleLiteral()) {
type = new SimpleTypedNameDeclaration("double", literal.getType());
} else if (literal.isIntLiteral()) {
type = new SimpleTypedNameDeclaration("int", literal.getType());
} else if (literal.isLongLiteral()) {
type = new SimpleTypedNameDeclaration("long", literal.getType());
} else if (literal.jjtGetNumChildren() == 1
&& literal.jjtGetChild(0) instanceof ASTBooleanLiteral) {
type = new SimpleTypedNameDeclaration("boolean", Boolean.TYPE);
}
} else if (child instanceof ASTAllocationExpression
&& child.jjtGetChild(0) instanceof ASTClassOrInterfaceType) {
ASTClassOrInterfaceType classInterface = (ASTClassOrInterfaceType) child.jjtGetChild(0);
type = convertToSimpleType(classInterface);
}
if (type == null && !parameterTypes.isEmpty()) {
// replace the unknown type with the correct parameter type
// of the method.
// in case the argument is itself a method call, we can't
// determine the result type of the called
// method. Therefore the parameter type is used.
// This might cause confusion, if method overloading is
// used.
// the method might be vararg, so, there might be more
// arguments than parameterTypes
if (parameterTypes.size() > i) {
type = parameterTypes.get(i);
} else {
// last parameter is the vararg type
type = parameterTypes.get(parameterTypes.size() - 1);
}
}
if (type != null && type.getType() == null) {
Class<?> typeBound = resolveGenericType(argument, type.getTypeImage());
if (typeBound != null) {
type = new SimpleTypedNameDeclaration(type.getTypeImage(), typeBound);
}
}
argumentTypes.add(type);
}
return argumentTypes;
}
private SimpleTypedNameDeclaration determineSuper(Node declaringNode) {
SimpleTypedNameDeclaration result = null;
if (declaringNode instanceof ASTClassOrInterfaceDeclaration) {
ASTClassOrInterfaceDeclaration classDeclaration = (ASTClassOrInterfaceDeclaration) declaringNode;
ASTImplementsList implementsList = classDeclaration.getFirstChildOfType(ASTImplementsList.class);
if (implementsList != null) {
List<ASTClassOrInterfaceType> types = implementsList.findChildrenOfType(ASTClassOrInterfaceType.class);
SimpleTypedNameDeclaration type = convertToSimpleType(types);
result = type;
}
ASTExtendsList extendsList = classDeclaration.getFirstChildOfType(ASTExtendsList.class);
if (extendsList != null) {
List<ASTClassOrInterfaceType> types = extendsList.findChildrenOfType(ASTClassOrInterfaceType.class);
SimpleTypedNameDeclaration type = convertToSimpleType(types);
if (result == null) {
result = type;
} else {
result.addNext(type);
}
}
}
return result;
}
private SimpleTypedNameDeclaration convertToSimpleType(List<ASTClassOrInterfaceType> types) {
SimpleTypedNameDeclaration result = null;
for (ASTClassOrInterfaceType t : types) {
SimpleTypedNameDeclaration type = convertToSimpleType(t);
if (result == null) {
result = type;
} else {
result.addNext(type);
}
}
return result;
}
private SimpleTypedNameDeclaration convertToSimpleType(ASTClassOrInterfaceType t) {
String typeImage = t.getImage();
typeImage = qualifyTypeName(typeImage);
Node declaringNode = getEnclosingScope(SourceFileScope.class).getQualifiedTypeNames().get(typeImage);
return new SimpleTypedNameDeclaration(typeImage,
this.getEnclosingScope(SourceFileScope.class).resolveType(typeImage), determineSuper(declaringNode));
}
public Class<?> resolveType(final String name) {
return this.getEnclosingScope(SourceFileScope.class).resolveType(qualifyTypeName(name));
}
/**
* Tries to resolve a given typeImage as a generic Type. If the Generic Type
* is found, any defined ClassOrInterfaceType below this type declaration is
* used (this is typically a type bound, e.g. {@code <T extends List>}.
*
* @param argument
* the node, from where to start searching.
* @param typeImage
* the type as string
* @return the resolved class or <code>null</code> if nothing was found.
*/
private Class<?> resolveGenericType(Node argument, String typeImage) {
List<ASTTypeParameter> types = new ArrayList<>();
// first search only within the same method
ASTClassOrInterfaceBodyDeclaration firstParentOfType = argument
.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
if (firstParentOfType != null) {
types.addAll(firstParentOfType.findDescendantsOfType(ASTTypeParameter.class));
}
// then search class level types, from inner-most to outer-most
List<ASTClassOrInterfaceDeclaration> enclosingClasses = argument
.getParentsOfType(ASTClassOrInterfaceDeclaration.class);
for (ASTClassOrInterfaceDeclaration enclosing : enclosingClasses) {
ASTTypeParameters classLevelTypeParameters = enclosing.getFirstChildOfType(ASTTypeParameters.class);
if (classLevelTypeParameters != null) {
types.addAll(classLevelTypeParameters.findDescendantsOfType(ASTTypeParameter.class));
}
}
return resolveGenericType(typeImage, types);
}
private Class<?> resolveGenericType(String typeImage, List<ASTTypeParameter> types) {
for (ASTTypeParameter type : types) {
if (typeImage.equals(type.getImage())) {
ASTClassOrInterfaceType bound = type.getFirstDescendantOfType(ASTClassOrInterfaceType.class);
if (bound != null) {
if (bound.getType() != null) {
return bound.getType();
} else {
return this.getEnclosingScope(SourceFileScope.class).resolveType(bound.getImage());
}
} else {
// type parameter found, but no binding.
return Object.class;
}
}
}
return null;
}
private Node getNextSibling(Node current) {
if (current.jjtGetParent().jjtGetNumChildren() > current.jjtGetChildIndex() + 1) {
return current.jjtGetParent().jjtGetChild(current.jjtGetChildIndex() + 1);
}
return null;
}
public String toString() {
StringBuilder res = new StringBuilder("ClassScope (").append(className).append("): ");
Map<ClassNameDeclaration, List<NameOccurrence>> classDeclarations = getClassDeclarations();
if (classDeclarations.isEmpty()) {
res.append("Inner Classes ").append(glomNames(classDeclarations.keySet())).append("; ");
}
Map<MethodNameDeclaration, List<NameOccurrence>> methodDeclarations = getMethodDeclarations();
if (!methodDeclarations.isEmpty()) {
for (MethodNameDeclaration mnd : methodDeclarations.keySet()) {
res.append(mnd.toString());
int usages = methodDeclarations.get(mnd).size();
res.append("(begins at line ").append(mnd.getNode().getBeginLine()).append(", ").append(usages)
.append(" usages)");
res.append(", ");
}
}
Map<VariableNameDeclaration, List<NameOccurrence>> variableDeclarations = getVariableDeclarations();
if (!variableDeclarations.isEmpty()) {
res.append("Variables ").append(glomNames(variableDeclarations.keySet()));
}
return res.toString();
}
private String clipClassName(String s) {
return s.substring(s.indexOf('.') + 1);
}
}