CombinedTypeSolver.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.resolution.typesolvers;
import com.github.javaparser.resolution.TypeSolver;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.cache.Cache;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.resolution.model.SymbolReference;
import com.github.javaparser.symbolsolver.cache.InMemoryCache;
import java.util.*;
import java.util.function.Predicate;
/**
* A container for type solvers. All solving is done by the contained type solvers.
* This helps you when an API asks for a single type solver, but you need several.
*
* @author Federico Tomassetti
*/
public class CombinedTypeSolver implements TypeSolver {
private final Cache<String, SymbolReference<ResolvedReferenceTypeDeclaration>> typeCache;
private TypeSolver parent;
private List<TypeSolver> elements = new ArrayList<>();
/**
* A predicate which determines what to do if an exception is raised during the parsing process.
* If it returns {@code true} the exception will be ignored, and solving will continue using the next solver in line.
* If it returns {@code false} the exception will be thrown, stopping the solving process.
*
* Main use case for this is to circumvent bugs or missing functionality in some type solvers.
* If for example solver A has a bug resulting in a {@link NullPointerException}, you could use a {@link ExceptionHandlers#getTypeBasedWhitelist(Class...) whitelist} to ignore that type of exception.
* A secondary solver would then be able to step in when such an error occurs.
*
* @see #CombinedTypeSolver(Predicate, TypeSolver...)
* @see #setExceptionHandler(Predicate)
*/
private Predicate<Exception> exceptionHandler;
public CombinedTypeSolver(TypeSolver... elements) {
this(Arrays.asList(elements));
}
public CombinedTypeSolver(Predicate<Exception> exceptionHandler, TypeSolver... elements) {
this(exceptionHandler, Arrays.asList(elements));
}
public CombinedTypeSolver(Iterable<TypeSolver> elements) {
this(ExceptionHandlers.IGNORE_NONE, elements);
}
/** @see #exceptionHandler */
public CombinedTypeSolver(Predicate<Exception> exceptionHandler, Iterable<TypeSolver> elements) {
this(exceptionHandler, elements, InMemoryCache.create());
}
/**
* Create a new instance of {@link CombinedTypeSolver} with a custom symbol cache.
*
* @param exceptionHandler How exception should be handled.
* @param elements The list of elements to include by default.
* @param typeCache The cache to be used to store symbols.
*
* @see #exceptionHandler
*/
public CombinedTypeSolver(
Predicate<Exception> exceptionHandler,
Iterable<TypeSolver> elements,
Cache<String, SymbolReference<ResolvedReferenceTypeDeclaration>> typeCache) {
Objects.requireNonNull(typeCache, "The typeCache can't be null.");
setExceptionHandler(exceptionHandler);
this.typeCache = typeCache;
for (TypeSolver el : elements) {
add(el, false);
}
}
/** @see #exceptionHandler */
public void setExceptionHandler(Predicate<Exception> exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
@Override
public TypeSolver getParent() {
return parent;
}
@Override
public void setParent(TypeSolver parent) {
Objects.requireNonNull(parent);
if (this.parent != null) {
throw new IllegalStateException("This TypeSolver already has a parent.");
}
if (parent == this) {
throw new IllegalStateException("The parent of this TypeSolver cannot be itself.");
}
this.parent = parent;
}
/**
* Append a type solver to the current solver.
*
* @param typeSolver The type solver to be appended.
* @param resetCache If should reset the cache when the solver is inserted.
*/
public void add(TypeSolver typeSolver, boolean resetCache) {
Objects.requireNonNull(typeSolver, "The type solver can't be null");
this.elements.add(typeSolver);
typeSolver.setParent(this);
// Check if the cache should be reset after inserting
if (resetCache) {
typeCache.removeAll();
}
}
/**
* Append a type solver to the current solver.
* <br>
* By default the cached values will be removed.
*
* @param typeSolver The type solver to be appended.
*/
public void add(TypeSolver typeSolver) {
add(typeSolver, true);
}
@Override
public SymbolReference<ResolvedReferenceTypeDeclaration> tryToSolveType(String name) {
Optional<SymbolReference<ResolvedReferenceTypeDeclaration>> cachedSymbol = typeCache.get(name);
if (cachedSymbol.isPresent()) {
return cachedSymbol.get();
}
// If the symbol is not cached
for (TypeSolver ts : elements) {
try {
SymbolReference<ResolvedReferenceTypeDeclaration> res = ts.tryToSolveType(name);
if (res.isSolved()) {
typeCache.put(name, res);
return res;
}
} catch (Exception e) {
if (!exceptionHandler.test(e)) { // we shouldn't ignore this exception
throw e;
}
}
}
// When unable to solve, cache the value with unsolved symbol
SymbolReference<ResolvedReferenceTypeDeclaration> unsolvedSymbol = SymbolReference.unsolved();
typeCache.put(name, unsolvedSymbol);
return unsolvedSymbol;
}
@Override
public ResolvedReferenceTypeDeclaration solveType(String name) throws UnsolvedSymbolException {
SymbolReference<ResolvedReferenceTypeDeclaration> res = tryToSolveType(name);
if (res.isSolved()) {
return res.getCorrespondingDeclaration();
}
throw new UnsolvedSymbolException(name);
}
/**
* Provides some convenience exception handler implementations
* @see CombinedTypeSolver#setExceptionHandler(Predicate)
*/
public static class ExceptionHandlers {
/** Doesn't ignore any exceptions (default) */
public static final Predicate<Exception> IGNORE_NONE = e -> false;
/** Ignores all exceptions */
public static final Predicate<Exception> IGNORE_ALL = e -> true;
/**
* Ignores any exception that is {@link Class#isAssignableFrom(Class) assignable from}
* {@link UnsupportedOperationException}.
*
* @see #getTypeBasedWhitelist(Class...)
*/
public static final Predicate<Exception> IGNORE_UNSUPPORTED_OPERATION =
getTypeBasedWhitelist(UnsupportedOperationException.class);
/**
* Ignores any exception that is {@link Class#isAssignableFrom(Class) assignable from}
* {@link UnsolvedSymbolException}.
*
* @see #getTypeBasedWhitelist(Class...)
*/
public static final Predicate<Exception> IGNORE_UNSOLVED_SYMBOL =
getTypeBasedWhitelist(UnsolvedSymbolException.class);
/**
* Ignores any exception that is {@link Class#isAssignableFrom(Class) assignable from} either
* {@link UnsolvedSymbolException} or {@link UnsupportedOperationException}.
*
* @see #IGNORE_UNSOLVED_SYMBOL
* @see #IGNORE_UNSUPPORTED_OPERATION
* @see #getTypeBasedWhitelist(Class...)
*/
public static final Predicate<Exception> IGNORE_UNSUPPORTED_AND_UNSOLVED =
getTypeBasedWhitelist(UnsupportedOperationException.class, UnsolvedSymbolException.class);
/**
* @see CombinedTypeSolver#setExceptionHandler(Predicate)
* @see #getTypeBasedWhitelist(Class...)
*
* @return A filter that ignores an exception if <b>none</b> of the listed classes are
* {@link Class#isAssignableFrom(Class) assignable from}
* the thrown exception class.
*/
public static Predicate<Exception> getTypeBasedBlacklist(Class<? extends Exception>... blacklist) {
return e -> {
for (Class<? extends Exception> clazz : blacklist) {
if (clazz.isAssignableFrom(e.getClass())) {
return false;
}
}
return true;
};
}
/**
* @see CombinedTypeSolver#setExceptionHandler(Predicate)
* @see #getTypeBasedBlacklist(Class...)
*
* @return A filter that ignores an exception if <b>any</b> of the listed classes are
* {@link Class#isAssignableFrom(Class) assignable from}
* the thrown exception class.
*/
public static Predicate<Exception> getTypeBasedWhitelist(Class<? extends Exception>... whitelist) {
return e -> {
for (Class<? extends Exception> clazz : whitelist) {
if (clazz.isAssignableFrom(e.getClass())) {
return true;
}
}
return false;
};
}
}
}