CompilationUnitContext.java
/*
* Copyright (C) 2015-2016 Federico Tomassetti
* Copyright (C) 2017-2024 The JavaParser Team.
*
* This file is part of JavaParser.
*
* JavaParser can be used either under the terms of
* a) the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* b) the terms of the Apache License
*
* You should have received a copy of both licenses in LICENCE.LGPL and
* LICENCE.APACHE. Please refer to those files for details.
*
* JavaParser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*/
package com.github.javaparser.symbolsolver.javaparsermodel.contexts;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.expr.Name;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.resolution.TypeSolver;
import com.github.javaparser.resolution.declarations.*;
import com.github.javaparser.resolution.logic.MethodResolutionLogic;
import com.github.javaparser.resolution.model.SymbolReference;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration;
import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserInterfaceDeclaration;
import com.github.javaparser.symbolsolver.resolution.SymbolSolver;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Federico Tomassetti
*/
public class CompilationUnitContext extends AbstractJavaParserContext<CompilationUnit> {
private static final String DEFAULT_PACKAGE = "java.lang";
// Contains the names of static import declarations with asterisks that have
// already been resolved. The aim is to keep a history of name searches for the
// same resolution attempt in order to avoid a recursive issue leading to a
// stackoverflow exception. See issues 4450 & 2720
private static ThreadLocal<List<String>> resolvedStaticImport =
ThreadLocal.withInitial(() -> new ArrayList<String>());
///
/// Static methods
///
///
/// Constructors
///
public CompilationUnitContext(CompilationUnit wrappedNode, TypeSolver typeSolver) {
super(wrappedNode, typeSolver);
}
///
/// Public methods
///
@Override
public SymbolReference<? extends ResolvedValueDeclaration> solveSymbol(String name) {
// solve absolute references
String itName = name;
while (itName.contains(".")) {
String typeName = getType(itName);
String memberName = getMember(itName);
SymbolReference<ResolvedTypeDeclaration> type = this.solveType(typeName);
if (type.isSolved()) {
return new SymbolSolver(typeSolver).solveSymbolInType(type.getCorrespondingDeclaration(), memberName);
}
itName = typeName;
}
// Look among statically imported values
for (ImportDeclaration importDecl : wrappedNode.getImports()) {
if (importDecl.isStatic()) {
if (importDecl.isAsterisk()) {
String qName = importDecl.getNameAsString();
// Try to resolve the name in from declarations imported with asterisks only if
// they have not already been analysed, otherwise this can lead to an infinite
// loop via circular dependencies.
if (!isAlreadyResolved(qName)) {
resolvedStaticImport.get().add(qName);
ResolvedTypeDeclaration importedType = typeSolver.solveType(qName);
SymbolReference<? extends ResolvedValueDeclaration> ref =
new SymbolSolver(typeSolver).solveSymbolInType(importedType, name);
if (ref.isSolved()) {
resolvedStaticImport.remove(); // clear the search history
return ref;
}
}
} else {
String whole = importDecl.getNameAsString();
// split in field/method name and type name
String memberName = getMember(whole);
String typeName = getType(whole);
if (memberName.equals(name)) {
ResolvedTypeDeclaration importedType = typeSolver.solveType(typeName);
return new SymbolSolver(typeSolver).solveSymbolInType(importedType, memberName);
}
}
}
}
// Clear of the search history because we don't want this context to be reused
// in another search.
resolvedStaticImport.remove();
return SymbolReference.unsolved();
}
private boolean isAlreadyResolved(String qName) {
return resolvedStaticImport.get().contains(qName);
}
@Override
public SymbolReference<ResolvedTypeDeclaration> solveType(String name, List<ResolvedType> typeArguments) {
if (wrappedNode.getTypes() != null) {
// Look for types in this compilation unit. For instance, if the given name is "A", there may be a class or
// interface in this compilation unit called "A".
for (TypeDeclaration<?> type : wrappedNode.getTypes()) {
if (type.getName().getId().equals(name)
|| type.getFullyQualifiedName()
.map(qualified -> qualified.equals(name))
.orElse(false)) {
return SymbolReference.solved(
JavaParserFacade.get(typeSolver).getTypeDeclaration(type));
}
}
// Look for member classes/interfaces of types in this compilation unit. For instance, if the given name is
// "A.B", there may be a class or interface in this compilation unit called "A" which has another member
// class or interface called "B". Since the type that we're looking for can be nested arbitrarily deeply
// ("A.B.C.D"), we look for the outermost type ("A" in the previous example) first, then recursively invoke
// this method for the remaining part of the given name.
if (isCompositeName(name)) {
SymbolReference<ResolvedTypeDeclaration> ref = solveTypeFromOuterMostRef(name);
if (ref != null && ref.isSolved()) {
return ref;
}
}
}
// Inspect imports for matches, prior to inspecting other classes within the package (per issue #1526)
int dotPos = name.indexOf('.');
String prefix = isCompositeName(name) ? name.substring(0, dotPos) : null;
// look into single type imports
for (ImportDeclaration importDecl : wrappedNode.getImports()) {
if (!importDecl.isAsterisk()) {
String qName = importDecl.getNameAsString();
boolean defaultPackage = !importDecl.getName().getQualifier().isPresent();
boolean found =
!defaultPackage && importDecl.getName().getIdentifier().equals(name);
if (!found && prefix != null) {
found = qName.endsWith("." + prefix);
if (found) {
qName = qName + name.substring(dotPos);
}
}
if (found) {
SymbolReference<ResolvedReferenceTypeDeclaration> ref = typeSolver.tryToSolveType(qName);
if (ref != null && ref.isSolved()) {
return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class);
}
}
}
}
// Look in current package
if (this.wrappedNode.getPackageDeclaration().isPresent()) {
String qName = this.wrappedNode.getPackageDeclaration().get().getNameAsString() + "." + name;
SymbolReference<ResolvedReferenceTypeDeclaration> ref = typeSolver.tryToSolveType(qName);
if (ref != null && ref.isSolved()) {
return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class);
}
} else {
// look for classes in the default package
if (isCompositeName(name)) {
SymbolReference<ResolvedTypeDeclaration> ref = solveExternalTypeFromOuterMostRef(name);
if (ref != null && ref.isSolved()) {
return ref;
}
} else {
String qName = name;
SymbolReference<ResolvedReferenceTypeDeclaration> ref = typeSolver.tryToSolveType(qName);
if (ref != null && ref.isSolved()) {
return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class);
}
}
}
// look into asterisk imports on demand
for (ImportDeclaration importDecl : wrappedNode.getImports()) {
if (importDecl.isAsterisk()) {
String qName = importDecl.getNameAsString() + "." + name;
SymbolReference<ResolvedReferenceTypeDeclaration> ref = typeSolver.tryToSolveType(qName);
if (ref != null && ref.isSolved()) {
return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class);
}
}
}
// Look in the java.lang package
SymbolReference<ResolvedReferenceTypeDeclaration> ref = typeSolver.tryToSolveType(DEFAULT_PACKAGE + "." + name);
if (ref != null && ref.isSolved()) {
return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class);
}
if (isCompositeName(name)) {
return SymbolReference.adapt(typeSolver.tryToSolveType(name), ResolvedTypeDeclaration.class);
}
return SymbolReference.unsolved();
}
/*
* Returns true if the name contains 'dot' separator.
*/
protected boolean isCompositeName(String name) {
return name.contains(".");
}
/*
* Look for member classes/interfaces of types in this compilation unit.
*/
private SymbolReference<ResolvedTypeDeclaration> solveTypeFromOuterMostRef(String name) {
SymbolReference<ResolvedTypeDeclaration> ref = null;
SymbolReference<ResolvedTypeDeclaration> outerMostRef = solveType(name.substring(0, name.indexOf(".")));
if (outerMostRef != null
&& outerMostRef.isSolved()
&& outerMostRef.getCorrespondingDeclaration() instanceof JavaParserClassDeclaration) {
ref = ((JavaParserClassDeclaration) outerMostRef.getCorrespondingDeclaration())
.solveType(name.substring(name.indexOf(".") + 1));
} else if (outerMostRef != null
&& outerMostRef.isSolved()
&& outerMostRef.getCorrespondingDeclaration() instanceof JavaParserInterfaceDeclaration) {
ref = ((JavaParserInterfaceDeclaration) outerMostRef.getCorrespondingDeclaration())
.solveType(name.substring(name.indexOf(".") + 1));
}
return ref;
}
/*
* Look for member classes/interfaces of types defined in another compilation unit.
*/
private SymbolReference<ResolvedTypeDeclaration> solveExternalTypeFromOuterMostRef(String name) {
SymbolReference<ResolvedTypeDeclaration> ref = null;
SymbolReference<ResolvedReferenceTypeDeclaration> outerMostRef =
typeSolver.tryToSolveType(name.substring(0, name.indexOf(".")));
if (outerMostRef != null
&& outerMostRef.isSolved()
&& outerMostRef.getCorrespondingDeclaration() instanceof JavaParserClassDeclaration) {
ref = ((JavaParserClassDeclaration) outerMostRef.getCorrespondingDeclaration())
.getContext()
.solveType(name.substring(name.indexOf(".") + 1));
} else if (outerMostRef != null
&& outerMostRef.isSolved()
&& outerMostRef.getCorrespondingDeclaration() instanceof JavaParserInterfaceDeclaration) {
ref = ((JavaParserInterfaceDeclaration) outerMostRef.getCorrespondingDeclaration())
.getContext()
.solveType(name.substring(name.indexOf(".") + 1));
}
return ref;
}
private String qName(ClassOrInterfaceType type) {
if (type.getScope().isPresent()) {
return qName(type.getScope().get()) + "." + type.getName().getId();
}
return type.getName().getId();
}
private String qName(Name name) {
if (name.getQualifier().isPresent()) {
return qName(name.getQualifier().get()) + "." + name.getId();
}
return name.getId();
}
private String toSimpleName(String qName) {
String[] parts = qName.split("\\.");
return parts[parts.length - 1];
}
private String packageName(String qName) {
int lastDot = qName.lastIndexOf('.');
if (lastDot == -1) {
throw new UnsupportedOperationException();
}
return qName.substring(0, lastDot);
}
@Override
public SymbolReference<ResolvedMethodDeclaration> solveMethod(
String name, List<ResolvedType> argumentsTypes, boolean staticOnly) {
for (ImportDeclaration importDecl : wrappedNode.getImports()) {
if (importDecl.isStatic()) {
if (importDecl.isAsterisk()) {
String importString = importDecl.getNameAsString();
if (this.wrappedNode.getPackageDeclaration().isPresent()
&& this.wrappedNode
.getPackageDeclaration()
.get()
.getName()
.getIdentifier()
.equals(packageName(importString))
&& this.wrappedNode.getTypes().stream()
.anyMatch(it -> it.getName().getIdentifier().equals(toSimpleName(importString)))) {
// We are using a static import on a type defined in this file. It means the value was not found
// at a lower level so this will fail
return SymbolReference.unsolved();
}
ResolvedTypeDeclaration ref = typeSolver.solveType(importString);
SymbolReference<ResolvedMethodDeclaration> method =
MethodResolutionLogic.solveMethodInType(ref, name, argumentsTypes, true);
if (method.isSolved()) {
return method;
}
} else {
String qName = importDecl.getNameAsString();
if (qName.equals(name) || qName.endsWith("." + name)) {
String typeName = getType(qName);
ResolvedTypeDeclaration ref = typeSolver.solveType(typeName);
SymbolReference<ResolvedMethodDeclaration> method =
MethodResolutionLogic.solveMethodInType(ref, name, argumentsTypes, true);
if (method.isSolved()) {
return method;
}
return SymbolReference.unsolved();
}
}
}
}
return SymbolReference.unsolved();
}
@Override
public List<ResolvedFieldDeclaration> fieldsExposedToChild(Node child) {
List<ResolvedFieldDeclaration> res = new LinkedList<>();
// Consider the static imports for static fields
for (ImportDeclaration importDeclaration : wrappedNode.getImports()) {
if (importDeclaration.isStatic()) {
Name typeNameAsNode = importDeclaration.isAsterisk()
? importDeclaration.getName()
: importDeclaration.getName().getQualifier().get();
String typeName = typeNameAsNode.asString();
ResolvedReferenceTypeDeclaration typeDeclaration = typeSolver.solveType(typeName);
res.addAll(typeDeclaration.getAllFields().stream()
.filter(f -> f.isStatic())
.filter(f -> importDeclaration.isAsterisk()
|| importDeclaration.getName().getIdentifier().equals(f.getName()))
.collect(Collectors.toList()));
}
}
return res;
}
///
/// Private methods
///
private String getType(String qName) {
int index = qName.lastIndexOf('.');
if (index == -1) {
throw new UnsupportedOperationException();
}
String typeName = qName.substring(0, index);
return typeName;
}
private String getMember(String qName) {
int index = qName.lastIndexOf('.');
if (index == -1) {
throw new UnsupportedOperationException();
}
String memberName = qName.substring(index + 1);
return memberName;
}
}