/*
* ******************************************************************************
* MontiCore Language Workbench
* Copyright (c) 2015, MontiCore, All rights reserved.
*
* This project is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
* This library 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this project. If not, see <http://www.gnu.org/licenses/>.
* ******************************************************************************
*/
package de.monticore.symboltable;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import de.monticore.ast.ASTNode;
import de.monticore.symboltable.modifiers.AccessModifier;
import de.monticore.symboltable.resolving.ResolvedSeveralEntriesException;
import de.monticore.symboltable.resolving.ResolvingFilter;
import de.monticore.symboltable.resolving.ResolvingInfo;
import de.monticore.symboltable.visibility.IsShadowedBySymbol;
import de.se_rwth.commons.Joiners;
import de.se_rwth.commons.Splitters;
import de.se_rwth.commons.logging.Log;
/**
* Default implementation of {@link Scope} and {@link MutableScope}.
* Usually, all other scopes should be sub classes of this class.
*
* @author Pedram Mir Seyed Nazari
*/
public class CommonScope implements MutableScope {
private final Map<String, Collection<Symbol>> symbols = new LinkedHashMap<>();
private final List<MutableScope> subScopes = new ArrayList<>();
private Boolean exportsSymbols = null;
private Boolean isShadowingScope;
private String name;
protected MutableScope enclosingScope;
private ScopeSpanningSymbol spanningSymbol;
private ASTNode astNode;
private Set<ResolvingFilter<? extends Symbol>> resolvingFilters = new LinkedHashSet<>();
public CommonScope() {
}
public CommonScope(boolean isShadowingScope) {
this(Optional.empty(), isShadowingScope);
}
public CommonScope(Optional<MutableScope> enclosingScope, boolean isShadowingScope) {
Log.errorIfNull(enclosingScope);
this.isShadowingScope = isShadowingScope;
if (enclosingScope.isPresent()) {
setEnclosingScope(enclosingScope.get());
}
}
public CommonScope(Optional<MutableScope> enclosingScope) {
this(enclosingScope, false);
}
@Override
public void setName(final String name) {
this.name = nullToEmpty(name);
}
@Override
public Optional<MutableScope> getEnclosingScope() {
return Optional.ofNullable(enclosingScope);
}
@Override
public List<MutableScope> getSubScopes() {
return ImmutableList.copyOf(subScopes);
}
public void addSubScope(MutableScope subScope) {
if (!subScopes.contains(subScope)) {
subScopes.add(subScope);
subScope.setEnclosingScope(this);
}
}
/**
* Removes the sub scope <code>subScope</code>.
* @param subScope the sub scope to be removed
*
*/
public void removeSubScope(MutableScope subScope) {
if (subScopes.contains(subScope)) {
subScopes.remove(subScope);
subScope.setEnclosingScope(null);
}
}
/**
* @deprecated use {@link #add(Symbol)} instead
*/
@Deprecated
public void define(Symbol symbol) {
add(symbol);
}
public void add(Symbol symbol) {
Log.errorIfNull(symbol);
final String symbolName = symbol.getName();
if (!symbols.containsKey(symbolName)) {
symbols.put(symbolName, new ArrayList<>());
}
symbols.get(symbolName).add(symbol);
symbol.setEnclosingScope(this);
}
@Override
public void remove(Symbol symbol) {
if (symbols.containsKey(symbol.getName())) {
final boolean symbolRemoved = symbols.get(symbol.getName()).remove(symbol);
if (symbolRemoved) {
symbol.setEnclosingScope(null);
}
}
}
@Override
public <T extends Symbol> Optional<T> resolve(ResolvingInfo resolvingInfo, String name, SymbolKind kind, AccessModifier modifier) {
return getResolvedOrThrowException(resolveMany(resolvingInfo, name, kind, modifier));
}
@Override
public <T extends Symbol> Collection<T> resolveMany(ResolvingInfo resolvingInfo, String name, SymbolKind kind, AccessModifier modifier) {
return resolveMany(resolvingInfo, name, kind, modifier, x -> true);
}
public <T extends Symbol> Collection<T> resolveMany(ResolvingInfo resolvingInfo, String name, SymbolKind kind, AccessModifier modifier,
Predicate<Symbol> predicate) {
final Set<T> resolvedSymbols = this.resolveManyLocally(resolvingInfo, name, kind, modifier, predicate);
final Collection<T> resolvedFromEnclosing = continueWithEnclosingScope(resolvingInfo, name, kind, modifier, predicate);
resolvedSymbols.addAll(resolvedFromEnclosing);
return resolvedSymbols;
}
protected <T extends Symbol> Set<T> filterSymbolsByAccessModifier(AccessModifier modifier, Set<T> resolvedUnfiltered) {
return Scopes.filterSymbolsByAccessModifier(modifier, resolvedUnfiltered);
}
/**
* Continues resolving with the enclosing scope.
*/
protected <T extends Symbol> Collection<T> continueWithEnclosingScope(ResolvingInfo resolvingInfo, String name, SymbolKind kind, AccessModifier modifier,
Predicate<Symbol> predicate) {
if (checkIfContinueWithEnclosingScope(resolvingInfo.areSymbolsFound()) && (getEnclosingScope().isPresent())) {
return getEnclosingScope().get().resolveMany(resolvingInfo, name, kind, modifier, predicate);
}
return Collections.emptySet();
}
@Override
public <T extends Symbol> Optional<T> resolve(String name, SymbolKind kind, AccessModifier modifier) {
return getResolvedOrThrowException(resolveMany(name, kind, modifier));
}
@Override
public <T extends Symbol> Optional<T> resolve(String name, SymbolKind kind, AccessModifier modifier, Predicate<Symbol> predicate) {
return getResolvedOrThrowException(resolveMany(name, kind, modifier, predicate));
}
@Override
public <T extends Symbol> Optional<T> resolveImported(String name, SymbolKind kind, AccessModifier modifier) {
return this.resolveLocally(name, kind);
}
@Override
public <T extends Symbol> Collection<T> resolveMany(String name, SymbolKind kind, AccessModifier modifier) {
return resolveMany(name, kind, modifier,x -> true);
}
@Override
public <T extends Symbol> Collection<T> resolveMany(String name, SymbolKind kind, Predicate<Symbol> predicate) {
return resolveMany(new ResolvingInfo(getResolvingFilters()), name, kind, AccessModifier.ALL_INCLUSION, predicate);
}
@Override
public <T extends Symbol> Collection<T> resolveMany(String name, SymbolKind kind, AccessModifier modifier, Predicate<Symbol> predicate) {
return resolveMany(new ResolvingInfo(getResolvingFilters()), name, kind, modifier, predicate);
}
@Override
public <T extends Symbol> Optional<T> resolve(String symbolName, SymbolKind kind) {
return getResolvedOrThrowException(resolveMany(symbolName, kind));
}
protected <T extends Symbol> boolean isNotSymbolShadowed(Collection<T> shadowingSymbols, T symbol) {
// Does any local symbol shadow the symbol of the enclosing scope?
return shadowingSymbols.stream().noneMatch(createIsShadowingByPredicate(symbol));
}
protected <T extends Symbol> Collection<T> getNotShadowedSymbols(Collection<T> shadowingSymbols, Collection<T> symbols) {
final Collection<T> result = new LinkedHashSet<>();
for (T resolvedSymbol : symbols) {
if (isNotSymbolShadowed(shadowingSymbols, resolvedSymbol)) {
result.add(resolvedSymbol);
}
}
return result;
}
/**
* Creates the predicate that checks whether the <code>shadowedSymbol</code> (usually a symbol of
* the enclosing scope) is shadowed by the (applied) symbols (usually the symbols of the current
* scope).
*
* @return the predicate that checks symbol hiding.
*/
protected IsShadowedBySymbol createIsShadowingByPredicate(Symbol shadowedSymbol) {
return new IsShadowedBySymbol(shadowedSymbol);
}
/**
*
* @param targetKind the symbol targetKind
* @return all resolvers of this scope that can resolve symbols of <code>targetKind</code>.
*/
protected Collection<ResolvingFilter<? extends Symbol>> getResolvingFiltersForTargetKind
(final Collection<ResolvingFilter<? extends Symbol>> resolvingFilters, final SymbolKind
targetKind) {
final Collection<ResolvingFilter<? extends Symbol>> resolversForKind = ResolvingFilter
.getFiltersForTargetKind(resolvingFilters, targetKind);
if (resolversForKind.isEmpty()) {
Log.debug("No resolver found for symbol targetKind \"" + targetKind.getName() + "\" in scope \"" +
getName() + "\"", CommonScope.class.getSimpleName());
}
return resolversForKind;
}
protected <T extends Symbol> Optional<T> getResolvedOrThrowException(final Collection<T> resolved) {
return ResolvingFilter.getResolvedOrThrowException(resolved);
}
/**
* @see Scope#resolve(SymbolPredicate)
* @deprecated use {@link #resolveMany(String, SymbolKind, Predicate)} instead
*/
@Deprecated
@Override
public Optional<? extends Symbol> resolve(SymbolPredicate predicate) {
final Collection<Symbol> allSymbols = Scopes.getLocalSymbolsAsCollection(this);
Set<Symbol> result = new LinkedHashSet<>(allSymbols.stream().filter(predicate).collect(Collectors.toSet()));
final Optional<? extends Symbol> resolvedFromEnclosing = continueWithEnclosingScope(predicate, result);
if (resolvedFromEnclosing.isPresent()) {
result.add(resolvedFromEnclosing.get());
}
return getResolvedOrThrowException(result);
}
protected Optional<? extends Symbol> continueWithEnclosingScope(SymbolPredicate predicate, Set<Symbol> result) {
if (getEnclosingScope().isPresent()) {
return continueWithScope(getEnclosingScope().get(), predicate, result);
}
return Optional.empty();
}
protected Optional<? extends Symbol> continueWithScope(MutableScope scope, SymbolPredicate predicate, Set<Symbol> result) {
if (checkIfContinueWithEnclosingScope(!result.isEmpty())) {
Optional<? extends Symbol> resolvedFromParent = scope.resolve(predicate);
if (resolvedFromParent.isPresent() && isNotSymbolShadowed(result, resolvedFromParent.get())) {
return resolvedFromParent;
}
}
return Optional.empty();
}
/**
* Returns true, if current scope should continue with resolving. By default,
* if symbols are already found and the current scope is a shadowing scope,
* the resolving process is not continued.
*
* @param foundSymbols states whether symbols have already been found during
* the current resolving process.
* @return true, if resolving should continue
*/
protected boolean checkIfContinueWithEnclosingScope(boolean foundSymbols) {
// If this scope shadows its enclosing scope and already some symbols are found,
// there is no need to continue searching.
return !(foundSymbols && isShadowingScope());
}
/**
* @deprecated use {@link #checkIfContinueWithEnclosingScope(boolean)} instead
*/
@Deprecated
protected boolean checkIfContinueWithEnclosing(boolean foundSymbols) {
return checkIfContinueWithEnclosingScope(foundSymbols);
}
@Override
public Map<String, Collection<Symbol>> getLocalSymbols() {
return ImmutableMap.copyOf(symbols);
}
@Override
@Deprecated
public Map<String, Collection<Symbol>> getSymbols() {
return getLocalSymbols();
}
@Override
public int getSymbolsSize() {
int size = 0;
for (Entry<String, Collection<Symbol>> entry: symbols.entrySet()) {
size += entry.getValue().size();
}
return size;
}
@Override
public boolean isShadowingScope() {
if (isShadowingScope == null) {
return getName().isPresent();
}
return isShadowingScope;
}
@Override
public Optional<String> getName() {
if (!isNullOrEmpty(name)) {
return Optional.of(name);
}
if (getSpanningSymbol().isPresent()) {
return Optional.of(getSpanningSymbol().get().getName());
}
return Optional.empty();
}
public void setEnclosingScope(MutableScope newEnclosingScope) {
if ((this.enclosingScope != null) && (newEnclosingScope != null)) {
if (this.enclosingScope == newEnclosingScope) {
return;
}
Log.warn("0xA1042 Scope \"" + getName() + "\" has already an enclosing scope.");
}
// remove this scope from current (old) enclosing scope, if exists.
if (this.enclosingScope != null) {
this.enclosingScope.removeSubScope(this);
}
// add this scope to new enclosing scope, if exists.
if (newEnclosingScope != null) {
newEnclosingScope.addSubScope(this);
}
// set new enclosing scope (or null)
this.enclosingScope = newEnclosingScope;
}
@Override
public Optional<ASTNode> getAstNode() {
return Optional.ofNullable(astNode);
}
@Override
public MutableScope getAsMutableScope() {
return this;
}
public void setAstNode(ASTNode astNode) {
this.astNode = astNode;
}
@Override
public Optional<? extends ScopeSpanningSymbol> getSpanningSymbol() {
return Optional.ofNullable(spanningSymbol);
}
@Override
public boolean isSpannedBySymbol() {
return getSpanningSymbol().isPresent();
}
public void setSpanningSymbol(ScopeSpanningSymbol symbol) {
this.spanningSymbol = symbol;
}
@Override
public boolean exportsSymbols() {
if (exportsSymbols == null) {
return getName().isPresent();
}
return exportsSymbols;
}
public void setExportsSymbols(boolean exportsSymbols) {
this.exportsSymbols = exportsSymbols;
}
public void setResolvingFilters(Collection<ResolvingFilter<? extends Symbol>> resolvingFilters) {
this.resolvingFilters = new LinkedHashSet<>(resolvingFilters);
}
public void addResolver(ResolvingFilter<? extends Symbol> resolvingFilter) {
this.resolvingFilters.add(resolvingFilter);
}
@Override
public Set<ResolvingFilter<? extends Symbol>> getResolvingFilters() {
return ImmutableSet.copyOf(resolvingFilters);
}
@Override
public String toString() {
return symbols.toString();
}
@Override
public <T extends Symbol> Collection<T> resolveMany(final String name, final SymbolKind kind) {
return resolveMany(name, kind, AccessModifier.ALL_INCLUSION);
}
@Override
public <T extends Symbol> Optional<T> resolveLocally(String name, SymbolKind kind) {
return getResolvedOrThrowException(
this.<T>resolveManyLocally(new ResolvingInfo(getResolvingFilters()), name, kind, AccessModifier.ALL_INCLUSION, x -> true));
}
protected <T extends Symbol> Set<T> resolveManyLocally(ResolvingInfo resolvingInfo, String name, SymbolKind kind, AccessModifier modifier,
Predicate<Symbol> predicate) {
Log.errorIfNull(resolvingInfo);
resolvingInfo.addInvolvedScope(this);
Collection<ResolvingFilter<? extends Symbol>> resolversForKind =
getResolvingFiltersForTargetKind(resolvingInfo.getResolvingFilters(), kind);
final Set<T> resolvedSymbols = new LinkedHashSet<>();
for (ResolvingFilter<? extends Symbol> resolvingFilter : resolversForKind) {
try {
Optional<T> resolvedSymbol = (Optional<T>) resolvingFilter.filter(resolvingInfo, name, symbols);
if (resolvedSymbol.isPresent()) {
if (resolvedSymbols.contains(resolvedSymbol.get())) {
Log.debug("The symbol " + resolvedSymbol.get().getName() + " has already been resolved.",
CommonScope.class.getSimpleName());
}
resolvedSymbols.add(resolvedSymbol.get());
}
}
catch(ResolvedSeveralEntriesException e) {
resolvedSymbols.addAll((Collection<? extends T>) e.getSymbols());
}
}
// filter out symbols that are not included within the access modifier
Set<T> filteredSymbols = filterSymbolsByAccessModifier(modifier, resolvedSymbols);
filteredSymbols = new LinkedHashSet<>(filteredSymbols.stream().filter(predicate).collect(Collectors.toSet()));
resolvingInfo.updateSymbolsFound(!filteredSymbols.isEmpty());
return filteredSymbols;
}
/**
* @see Scope#resolveLocally(SymbolKind)
*/
@Override
public <T extends Symbol> List<T> resolveLocally(SymbolKind kind) {
final Collection<ResolvingFilter<? extends Symbol>> resolversForKind =
getResolvingFiltersForTargetKind(resolvingFilters, kind);
final Collection<T> resolvedSymbols = new LinkedHashSet<>();
final Collection<Symbol> symbolsAsList = Scopes.getLocalSymbolsAsCollection(this);
for (ResolvingFilter<? extends Symbol> resolvingFilter : resolversForKind) {
final ResolvingInfo resolvingInfo = new ResolvingInfo(getResolvingFilters());
resolvingInfo.addInvolvedScope(this);
Collection<T> filtered = (Collection<T>) resolvingFilter.filter(resolvingInfo, symbolsAsList);
resolvedSymbols.addAll(filtered);
}
return ImmutableList.copyOf(resolvedSymbols);
}
@Override
public <T extends Symbol> Optional<T> resolveDown(String name, SymbolKind kind, AccessModifier modifier) {
return getResolvedOrThrowException(resolveDownMany(name, kind, modifier));
}
@Override
public <T extends Symbol> Optional<T> resolveDown(String name, SymbolKind kind, AccessModifier modifier, Predicate<Symbol> predicate) {
return getResolvedOrThrowException(resolveDownMany(name, kind, modifier, predicate));
}
@Override
public <T extends Symbol> Collection<T> resolveDownMany(String name, SymbolKind kind, AccessModifier modifier) {
return resolveDownMany(new ResolvingInfo(getResolvingFilters()), name, kind, modifier, x -> true);
}
@Override
public <T extends Symbol> Collection<T> resolveDownMany(String name, SymbolKind kind, AccessModifier modifier, Predicate<Symbol> predicate) {
return resolveDownMany(new ResolvingInfo(getResolvingFilters()), name, kind, modifier, predicate);
}
@Override
public <T extends Symbol> Collection<T> resolveDownMany(ResolvingInfo resolvingInfo, String name, SymbolKind kind, AccessModifier modifier,
Predicate<Symbol> predicate) {
// 1. Conduct search locally in the current scope
final Set<T> resolved = this.resolveManyLocally(resolvingInfo, name, kind, modifier, predicate);
final String resolveCall = "resolveDownMany(\"" + name + "\", \"" + kind.getName()
+ "\") in scope \"" + getName() + "\"";
Log.trace("START " + resolveCall + ". Found #" + resolved.size() + " (local)", "");
// If no matching symbols have been found...
if (resolved.isEmpty()) {
// 2. Continue search in sub scopes and ...
for (MutableScope subScope : getSubScopes()) {
final Collection<T> resolvedFromSub = subScope.continueAsSubScope(resolvingInfo, name, kind, modifier, predicate);
// 3. unify results
resolved.addAll(resolvedFromSub);
}
}
Log.trace("END " + resolveCall + ". Found #" + resolved.size() , "");
return resolved;
}
/**
* @see MutableScope#resolveDown(java.lang.String, SymbolKind)
*/
@Override
public <T extends Symbol> Optional<T> resolveDown(String name, SymbolKind kind) {
return getResolvedOrThrowException(this.resolveDownMany(name, kind));
}
@Override
public <T extends Symbol> Collection<T> resolveDownMany(String name, SymbolKind kind) {
return this.resolveDownMany(new ResolvingInfo(getResolvingFilters()), name, kind, AccessModifier.ALL_INCLUSION, x -> true);
}
/**
* Continues (top-down) resolving with this sub scope
*
* @param resolvingInfo contains resolving information, such as, the already involved scopes.
* @param symbolName the name of the searched symbol
* @param kind the kind of the searched symbol
*/
@Override
public <T extends Symbol> Collection<T> continueAsSubScope(ResolvingInfo resolvingInfo,
String symbolName, SymbolKind kind, AccessModifier modifier, Predicate<Symbol> predicate) {
if (checkIfContinueAsSubScope(symbolName, kind)) {
final String remainingSymbolName = getRemainingNameForResolveDown(symbolName);
return this.resolveDownMany(resolvingInfo, remainingSymbolName, kind, modifier, predicate);
}
return Collections.emptySet();
}
protected boolean checkIfContinueAsSubScope(String symbolName, SymbolKind kind) {
if(this.exportsSymbols()) {
final List<String> nameParts = getNameParts(symbolName).toList();
if (nameParts.size() > 1) {
final String firstNamePart = nameParts.get(0);
// A scope that exports symbols usually has a name.
return firstNamePart.equals(this.getName().orElse(""));
}
}
return false;
}
protected String getRemainingNameForResolveDown(String symbolName) {
final FluentIterable<String> nameParts = getNameParts(symbolName);
return (nameParts.size() > 1) ? Joiners.DOT.join(nameParts.skip(1)) : symbolName;
}
protected FluentIterable<String> getNameParts(String symbolName) {
return FluentIterable.from(Splitters.DOT.split(symbolName));
}
}