/******************************************************************************* * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ package org.eclipse.xtext.scoping.impl; import static com.google.common.collect.Iterables.*; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.scoping.IScope; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.inject.Provider; /** * <p>Base class for custom scope implementations. It supports nesting of scopes * into each other, appropriate shadowing semantics and case sensitive and insensitive * lookup.</p> * * <p>Implementors have to provide {@link #getAllLocalElements()}. However, it is recommended * to customize {@link #getLocalElementsByEObject(EObject, URI)} and {@link #getLocalElementsByName(QualifiedName)} * as well.</p> * * @author Sven Efftinge - Initial contribution and API * @author Sebastian Zarnekow */ public abstract class AbstractScope implements IScope { /** * Lazy iterable with a reasonable {@link #toString()} implementation that supports * shadowing of parents elements by means of filtering. */ protected static class ParentIterable implements Iterable<IEObjectDescription>, Predicate<IEObjectDescription> { private final AbstractScope scope; private final Provider<Iterable<IEObjectDescription>> provider; private Iterable<IEObjectDescription> parentElements; protected ParentIterable(AbstractScope scope, Provider<Iterable<IEObjectDescription>> provider) { this.scope = scope; this.provider = provider; } @Override public Iterator<IEObjectDescription> iterator() { if (parentElements == null) { parentElements = provider.get(); } Iterator<IEObjectDescription> parentIterator = parentElements.iterator(); Iterator<IEObjectDescription> filteredIterator = Iterators.filter(parentIterator, this); return filteredIterator; } @Override public boolean apply(IEObjectDescription input) { return !scope.isShadowed(input); } @Override public String toString() { return Iterables.toString(this); } } private final boolean ignoreCase; private final IScope parent; /** * Creates a new scope with a given parent. * @param parent the parent scope. May not be <code>null</code>. Use {@link IScope#NULLSCOPE NULLSCOPE} instead. * @param ignoreCase whether name lookup and shadowing should be case insensitive or not. */ protected AbstractScope(IScope parent, boolean ignoreCase) { if (parent == null) throw new IllegalArgumentException("parent may not be null. Use IScope.NULLSCOPE instead."); this.parent = parent; this.ignoreCase = ignoreCase; } public IScope getParent() { return parent; } public boolean isIgnoreCase() { return ignoreCase; } @Override public IEObjectDescription getSingleElement(QualifiedName name) { IEObjectDescription result = getSingleLocalElementByName(name); if (result != null) return result; return getParent().getSingleElement(name); } protected IEObjectDescription getSingleLocalElementByName(QualifiedName name) { Iterable<IEObjectDescription> result = getLocalElementsByName(name); Iterator<IEObjectDescription> iterator = result.iterator(); if (iterator.hasNext()) return iterator.next(); return null; } @Override public Iterable<IEObjectDescription> getAllElements() { Iterable<IEObjectDescription> localElements = getAllLocalElements(); Iterable<IEObjectDescription> parentElements = getParentElements(new Provider<Iterable<IEObjectDescription>>() { @Override public Iterable<IEObjectDescription> get() { return getParent().getAllElements(); } }); Iterable<IEObjectDescription> result = Iterables.concat(localElements, parentElements); return result; } @Override public Iterable<IEObjectDescription> getElements(final QualifiedName name) { Iterable<IEObjectDescription> localElements = getLocalElementsByName(name); if (localElements instanceof Collection) { if (((Collection<?>) localElements).isEmpty()) return getParent().getElements(name); } Iterable<IEObjectDescription> parentElements = getParentElements(new Provider<Iterable<IEObjectDescription>>() { @Override public Iterable<IEObjectDescription> get() { return getParent().getElements(name); } }); Iterable<IEObjectDescription> result = Iterables.concat(localElements, parentElements); return result; } @Override public IEObjectDescription getSingleElement(EObject object) { Iterable<IEObjectDescription> elements = getElements(object); Iterator<IEObjectDescription> iterator = elements.iterator(); if (iterator.hasNext()) { IEObjectDescription result = iterator.next(); return result; } return null; } @Override public Iterable<IEObjectDescription> getElements(final EObject object) { final URI uri = EcoreUtil2.getPlatformResourceOrNormalizedURI(object); Iterable<IEObjectDescription> localElements = getLocalElementsByEObject(object, uri); Iterable<IEObjectDescription> parentElements = getParentElements(new Provider<Iterable<IEObjectDescription>>() { @Override public Iterable<IEObjectDescription> get() { return getParent().getElements(object); } }); Iterable<IEObjectDescription> result = Iterables.concat(localElements, parentElements); return result; } protected abstract Iterable<IEObjectDescription> getAllLocalElements(); protected Iterable<IEObjectDescription> getLocalElementsByName(final QualifiedName name) { Iterable<IEObjectDescription> localElements = getAllLocalElements(); Iterable<IEObjectDescription> result = Iterables.filter(localElements, new Predicate<IEObjectDescription>() { @Override public boolean apply(IEObjectDescription input) { if (isIgnoreCase()) { return name.equalsIgnoreCase(input.getName()); } else { return name.equals(input.getName()); } } }); return result; } protected Iterable<IEObjectDescription> getLocalElementsByEObject(final EObject object, final URI uri) { Iterable<IEObjectDescription> localElements = getAllLocalElements(); Iterable<IEObjectDescription> result = Iterables.filter(localElements, new Predicate<IEObjectDescription>() { @Override public boolean apply(IEObjectDescription input) { if (input.getEObjectOrProxy() == object) return canBeFoundByName(input); if (uri.equals(input.getEObjectURI())) { return canBeFoundByName(input); } return false; } public boolean canBeFoundByName(IEObjectDescription input) { IEObjectDescription lookUp = getSingleLocalElementByName(input.getName()); if (lookUp != null) { if (lookUp == input) return true; if (lookUp.getEObjectOrProxy() == object) return true; if (uri.equals(lookUp.getEObjectURI())) return true; } return false; } }); return result; } protected Iterable<IEObjectDescription> getParentElements(Provider<Iterable<IEObjectDescription>> provider) { if (getParent() == IScope.NULLSCOPE) return Collections.emptyList(); return new ParentIterable(this, provider); } /** * Returns <code>true</code> if the given description {@code input} from the parent scope is * shadowed by local elements. * @return <code>true</code> if the given description {@code input} from the parent scope is * shadowed by local elements. */ protected boolean isShadowed(IEObjectDescription input) { final Iterable<IEObjectDescription> localElements = getLocalElementsByName(input.getName()); final boolean isEmpty = isEmpty(localElements); return !isEmpty; } @Override public String toString() { String parentString = null; try { final IScope parent = getParent(); parentString = parent.toString(); } catch (Throwable t) { parentString = t.getClass().getSimpleName() + " : " + t.getMessage(); } return getClass().getSimpleName() + (ignoreCase? "[ignore case]":"") + getAllLocalElements() + " -> " + parentString; } }