/**
* Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cloudsmith
*
*/
package org.cloudsmith.geppetto.pp.dsl.linking;
import java.util.EnumSet;
import java.util.Iterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.resource.IEObjectDescription;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
/**
* Iterable filter for searching for a name up the scope chain.
*
*/
public class NameInScopeFilter implements Iterable<IEObjectDescription> {
public static interface Match {
/** Compare using equals, no need to find all matching */
public static final SearchStrategy EQUALS = new SearchStrategy();
/** Compare using equals, find all that match */
public static final SearchStrategy ALL_EQUALS = new SearchStrategy(
EnumSet.complementOf(EnumSet.of(SearchType.EXISTS)));
/** Compare using starts with, find all that match. */
public static final SearchStrategy STARTS_WITH = new SearchStrategy(
SearchType.GLOBAL, SearchType.INHERITED, SearchType.OUTER_SCOPES);
public static final SearchStrategy NO_OUTER = new SearchStrategy(
SearchType.EQUALS, SearchType.GLOBAL, SearchType.INHERITED);
public static final SearchStrategy NO_OUTER_EXISTS = new SearchStrategy(
SearchType.EQUALS, SearchType.EXISTS, SearchType.GLOBAL, SearchType.INHERITED);
public static final SearchStrategy NO_OUTER_STARTS_WITH = new SearchStrategy(
SearchType.GLOBAL, SearchType.INHERITED);
}
public static class NameInScopePredicate implements Predicate<IEObjectDescription> {
final QualifiedName scopeName;
final QualifiedName name;
final boolean absolute;
final SearchStrategy matchStrategy;
final EClass[] eclasses;
public NameInScopePredicate(boolean absolute, SearchStrategy matchingStrategy, QualifiedName name,
QualifiedName scopeName, EClass[] eclasses) {
this.absolute = absolute;
this.scopeName = scopeName == null
? QualifiedName.EMPTY
: scopeName;
this.name = name;
this.eclasses = eclasses;
this.matchStrategy = matchingStrategy;
}
@Override
public boolean apply(IEObjectDescription candidate) {
QualifiedName candidateName = candidate.getQualifiedName();
// error, not a valid name (can not possibly match).
if(candidateName.getSegmentCount() == 0)
return false;
// it is faster to compare exact match as this is a common case, before trying isSuperTypeOf
int found = -1;
for(int i = 0; i < eclasses.length; i++)
if(eclasses[i] == candidate.getEClass() || eclasses[i].isSuperTypeOf(candidate.getEClass())) {
found = i;
break;
}
if(found < 0)
return false; // wrong type
if(absolute)
return candidateName.equals(name);
// Since most references are exact (they are global), this is the fastest for the common case.
if(matches(candidateName, name, matchStrategy))
return true;
if(!matchStrategy.searchOuterScopes())
return false;
// need to find the common outer scope
QualifiedName candidateParent = candidateName.skipLast(1);
// Note: it is not possible to refer to the parent i.e. class foo::bar { bar { }}
// find the common outer scope
int commonCount = 0;
int limit = Math.min(scopeName.getSegmentCount(), candidateParent.getSegmentCount());
for(int i = 0; i < limit; i++)
if(scopeName.getSegment(i).equals(candidateName.getSegment(i)))
commonCount++;
else
break;
{ // TODO: Should not be done for variables
// if no common ancestor, then equality check above should have found it.
if(commonCount == 0)
return false;
// commonPart+requestedName == candidate (i.e. wanted "c::d" in scope "a::b" - check "a::b::c::d"
if(matchStrategy.matchStartsWith())
return candidateName.startsWith(scopeName.skipLast(scopeName.getSegmentCount() - commonCount).append(
name));
return scopeName.skipLast(scopeName.getSegmentCount() - commonCount).append(name).equals(candidateName);
}
}
private boolean matches(QualifiedName candidate, QualifiedName query, SearchStrategy matchingStrategy) {
// equals matching
if(matchingStrategy.matchEquals())
return candidate.equals(query);
// starts with matching
if(query.getSegmentCount() > candidate.getSegmentCount())
return false;
if(query.getSegmentCount() == 0)
return true; // everything starts with nothing
// All segments (except last) in query must be equal to the corresponding segment in candidate
for(int i = 0; i < query.getSegmentCount() - 1; i++)
if(!candidate.getSegment(i).equals(query.getSegment(i)))
return false;
// the last segment in query, must be the start of the corresponding segment in candidate or be empty
if(!("".equals(query.getLastSegment()) || candidate.getSegment(query.getSegmentCount() - 1).startsWith(
query.getLastSegment())))
return false;
return true;
}
}
public static class SearchStrategy {
private EnumSet<SearchType> flags;
/**
* Search equals, inherited, outer (global).
*/
public SearchStrategy() {
flags = EnumSet.allOf(SearchType.class);
}
public SearchStrategy(EnumSet<SearchType> flags) {
this.flags = flags;
}
public SearchStrategy(SearchType first, SearchType... rest) {
flags = EnumSet.of(first, rest);
}
/**
* @return
*/
public boolean isExists() {
return flags.contains(SearchType.EXISTS);
}
public boolean matchEquals() {
return flags.contains(SearchType.EQUALS);
}
public boolean matchStartsWith() {
return !matchEquals();
}
public boolean searchGlobal() {
return flags.contains(SearchType.GLOBAL) || searchOuterScopes();
}
public boolean searchInherited() {
return flags.contains(SearchType.INHERITED);
}
public boolean searchOuterScopes() {
return flags.contains(SearchType.OUTER_SCOPES);
}
}
public static enum SearchType {
/** Compare using equals, find all that match. (Implies Start with otherwise) */
EQUALS,
/** Search up the inheritance chain. */
INHERITED,
/** Search with widening scope */
OUTER_SCOPES,
/** Search global scope (implied when using WIDENING) */
GLOBAL,
/** Stops searching when at least one is found. */
EXISTS, ;
}
private final Iterable<IEObjectDescription> unfiltered;
final private NameInScopePredicate filter;
NameInScopeFilter(SearchStrategy matchingStrategy, Iterable<IEObjectDescription> unfiltered, QualifiedName name,
QualifiedName scope, EClass[] eclasses) {
boolean absolute = name.getSegmentCount() > 0 && "".equals(name.getSegment(0));
this.unfiltered = unfiltered;
filter = new NameInScopePredicate(absolute, matchingStrategy, absolute
? name.skipFirst(1)
: name, scope, eclasses);
}
public Iterator<IEObjectDescription> iterator() {
return Iterators.filter(unfiltered.iterator(), filter);
}
}