/**
* Copyright (c) 2013 Puppet Labs, 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:
* Puppet Labs
*/
package com.puppetlabs.geppetto.pp.dsl.linking;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import com.puppetlabs.geppetto.common.tracer.ITracer;
import com.puppetlabs.geppetto.pp.AppendExpression;
import com.puppetlabs.geppetto.pp.AssignmentExpression;
import com.puppetlabs.geppetto.pp.BinaryExpression;
import com.puppetlabs.geppetto.pp.Definition;
import com.puppetlabs.geppetto.pp.DefinitionArgument;
import com.puppetlabs.geppetto.pp.Expression;
import com.puppetlabs.geppetto.pp.HostClassDefinition;
import com.puppetlabs.geppetto.pp.Lambda;
import com.puppetlabs.geppetto.pp.NodeDefinition;
import com.puppetlabs.geppetto.pp.PPPackage;
import com.puppetlabs.geppetto.pp.ResourceBody;
import com.puppetlabs.geppetto.pp.VariableExpression;
import com.puppetlabs.geppetto.pp.dsl.PPDSLConstants;
import com.puppetlabs.geppetto.pp.dsl.adapters.PPImportedNamesAdapter;
import com.puppetlabs.geppetto.pp.dsl.linking.NameInScopeFilter.Match;
import com.puppetlabs.geppetto.pp.dsl.linking.NameInScopeFilter.SearchStrategy;
import com.puppetlabs.geppetto.pp.dsl.linking.PPSearchPath.ISearchPathProvider;
import com.puppetlabs.geppetto.pp.pptp.PPTPPackage;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.resource.EObjectDescription;
import org.eclipse.xtext.resource.IContainer;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.resource.IResourceDescription;
import org.eclipse.xtext.resource.IResourceDescriptions;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* Utility class for finding references.
*
*/
public class PPFinder {
public static class SearchResult {
private List<IEObjectDescription> adjusted;
private List<IEObjectDescription> raw;
private SearchResult() {
this.adjusted = Collections.emptyList();
this.raw = this.adjusted;
}
private SearchResult(List<IEObjectDescription> rawAndAdjusted) {
this.adjusted = this.raw = rawAndAdjusted;
}
private SearchResult(List<IEObjectDescription> pathAdjusted, List<IEObjectDescription> raw) {
this.adjusted = pathAdjusted;
this.raw = raw;
}
private SearchResult addAll(SearchResult other) {
this.adjusted.addAll(other.adjusted);
this.raw.addAll(other.raw);
return this;
}
public List<IEObjectDescription> getAdjusted() {
return adjusted;
}
public List<IEObjectDescription> getRaw() {
return raw;
}
}
private final static EClass[] CLASSES_FOR_VARIABLES = { //
PPPackage.Literals.DEFINITION_ARGUMENT, //
PPTPPackage.Literals.TP_VARIABLE, //
// PPTPPackage.Literals.TYPE_ARGUMENT, //
PPPackage.Literals.VARIABLE_EXPRESSION };
private final static EClass[] DEF_AND_TYPE_ARGUMENTS = {
PPPackage.Literals.DEFINITION_ARGUMENT, PPTPPackage.Literals.TYPE_ARGUMENT };
// Note that order is important
private final static EClass[] DEF_AND_TYPE = { PPTPPackage.Literals.TYPE, PPPackage.Literals.DEFINITION };
private static final EClass[] FUNC = { PPTPPackage.Literals.FUNCTION };
private final static EClass[] CLASS_AND_TYPE = { PPPackage.Literals.HOST_CLASS_DEFINITION,
// PPTPPackage.Literals.TYPE
};
private Resource resource;
@Inject
private ISearchPathProvider searchPathProvider;
/**
* Access to container manager for PP language
*/
@Inject
private IContainer.Manager manager;
private PPSearchPath searchPath;
private Multimap<String, IEObjectDescription> exportedPerLastSegment;
/**
* Access to the 'pp' services (container management and more).
*/
@Inject
private IResourceServiceProvider resourceServiceProvider;
/**
* Access to naming of model elements.
*/
@Inject
private IQualifiedNameProvider fqnProvider;
/**
* PP FQN to/from Xtext QualifiedName converter.
*/
@Inject
private IQualifiedNameConverter converter;
/**
* Access to the global index maintained by Xtext, is made via a special (non guice) provider
* that is aware of the context (builder, dirty editors, etc.). It is used to obtain the
* index for a particular resource. This special provider is obtained here.
*/
@Inject
private org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider indexProvider;
/**
* Access to runtime configurable debug trace.
*/
@Inject
@Named(PPDSLConstants.PP_DEBUG_LINKER)
private ITracer tracer;
private Map<String, IEObjectDescription> metaCache;
private Map<String, IEObjectDescription> metaVarCache;
private List<IEObjectDescription> exportedPatternVariables;
private void buildExportedObjectsIndex(IResourceDescription descr, IResourceDescriptions descriptionIndex) {
// The current (possibly dirty) exported resources
IResourceDescription dirty = resourceServiceProvider.getResourceDescriptionManager().getResourceDescription(
resource);
String pathToCurrent = resource.getURI().path();
Multimap<String, IEObjectDescription> map = ArrayListMultimap.create();
List<IEObjectDescription> patternedVariables = Lists.newArrayList();
// add all (possibly dirty in global index)
// check for empty qualified names which may be present in case of syntax errors / while editing etc.
// empty names are simply skipped (they can not be found anyway).
//
for(IEObjectDescription d : dirty.getExportedObjects())
if(d.getQualifiedName().getSegmentCount() >= 1)
map.put(d.getQualifiedName().getLastSegment(), d);
// add all from global index, except those for current resource
for(IEObjectDescription d : getExportedObjects(descr, descriptionIndex))
if(!d.getEObjectURI().path().equals(pathToCurrent) && d.getQualifiedName().getSegmentCount() >= 1) {
// patterned based names are exceptional
if(d.getUserData(PPDSLConstants.VARIABLE_PATTERN) != null) {
patternedVariables.add(d);
}
else {
map.put(d.getQualifiedName().getLastSegment(), d);
}
}
exportedPerLastSegment = map;
exportedPatternVariables = patternedVariables;
}
private void cacheMetaParameters(EObject scopeDetermeningObject) {
metaCache = Maps.newHashMap();
metaVarCache = Maps.newHashMap();
Resource scopeDetermeningResource = scopeDetermeningObject.eResource();
IResourceDescriptions descriptionIndex = indexProvider.getResourceDescriptions(scopeDetermeningResource);
IResourceDescription descr = descriptionIndex.getResourceDescription(scopeDetermeningResource.getURI());
if(descr == null)
return; // give up - some sort of clean build
EClass wantedType = PPTPPackage.Literals.TYPE_ARGUMENT;
for(IContainer visibleContainer : manager.getVisibleContainers(descr, descriptionIndex)) {
for(IEObjectDescription objDesc : visibleContainer.getExportedObjects()) {
QualifiedName q = objDesc.getQualifiedName();
if("Type".equals(q.getFirstSegment())) {
if(wantedType == objDesc.getEClass() || wantedType.isSuperTypeOf(objDesc.getEClass()))
metaCache.put(q.getLastSegment(), objDesc);
}
else if(objDesc.getEClass() == PPTPPackage.Literals.META_VARIABLE) {
metaVarCache.put(q.getLastSegment(), objDesc);
}
}
}
}
public void configure(EObject o) {
configure(o.eResource());
}
public void configure(Resource r) {
resource = r;
IResourceDescriptions descriptionIndex = indexProvider.getResourceDescriptions(resource);
IResourceDescription descr = descriptionIndex.getResourceDescription(resource.getURI());
// Happens during start/clean in some state
if(descr == null)
return;
manager = resourceServiceProvider.getContainerManager();
buildExportedObjectsIndex(descr, descriptionIndex);
searchPath = searchPathProvider.get(r);
}
/**
* Find an attribute being a DefinitionArgument, Property, or Parameter for the given type, or a
* meta Property or Parameter defined for the type 'Type'.
*
* @param scopeDetermeningObject
* @param fqn
* @return
*/
public SearchResult findAttributes(EObject scopeDetermeningObject, QualifiedName fqn,
PPImportedNamesAdapter importedNames) {
SearchResult result = null;
// do meta lookup first as this is made fast via a cache and these are used more frequent
// than other parameters (measured).
if(metaCache == null)
cacheMetaParameters(scopeDetermeningObject);
IEObjectDescription d = metaCache.get(fqn.getLastSegment());
if(d == null)
result = findInherited(
scopeDetermeningObject, fqn, importedNames, Lists.<QualifiedName> newArrayList(), Match.EQUALS,
DEF_AND_TYPE_ARGUMENTS);
else
result = new SearchResult(Lists.newArrayList(d));
return result;
}
/**
* @param resourceBody
* @param fqn
* @return
*/
public SearchResult findAttributesWithPrefix(ResourceBody resourceBody, QualifiedName fqn) {
// Must be configured for the resource containing resourceBody
List<IEObjectDescription> result = Lists.newArrayList();
// do meta lookup first as this is made fast via a cache and these are used more frequent
// than other parameters (measured).
// TODO: VERIFY that empty last segment matches ok
// TODO: Make sure that length of match is same number of segments
if(metaCache == null)
cacheMetaParameters(resourceBody);
String fqnLast = fqn.getLastSegment();
for(String name : metaCache.keySet())
if(name.startsWith(fqnLast))
result.add(metaCache.get(name));
result.addAll(findInherited(
resourceBody, fqn, null, Lists.<QualifiedName> newArrayList(), Match.STARTS_WITH, DEF_AND_TYPE_ARGUMENTS).getAdjusted());
return new SearchResult(result);
}
public SearchResult findDefinitions(EObject scopeDetermeningResource, PPImportedNamesAdapter importedNames) {
// make all segments initial char lower case (if references is to the type itself - eg. 'File' instead of
// 'file', or 'Aa::Bb' instead of 'aa::bb'
QualifiedName fqn2 = QualifiedName.EMPTY;
// TODO: Note that order is important, TYPE has higher precedence and should be used for linking
// This used to work when list was iterated per type, not it is iterated once with type check
// first - thus if a definition is found before a type, it is earlier in the list.
return findExternal(scopeDetermeningResource, fqn2, importedNames, Match.STARTS_WITH, DEF_AND_TYPE);
}
public SearchResult findDefinitions(EObject scopeDetermeningResource, String name,
PPImportedNamesAdapter importedNames) {
if(name == null)
throw new IllegalArgumentException("name is null");
QualifiedName fqn = converter.toQualifiedName(name);
// make all segments initial char lower case (if references is to the type itself - eg. 'File' instead of
// 'file', or 'Aa::Bb' instead of 'aa::bb'
QualifiedName fqn2 = QualifiedName.EMPTY;
for(int i = 0; i < fqn.getSegmentCount(); i++)
fqn2 = fqn2.append(toInitialLowerCase(fqn.getSegment(i)));
// fqn2 = fqn.skipLast(1).append(toInitialLowerCase(fqn.getLastSegment()));
// TODO: Note that order is important, TYPE has higher precedence and should be used for linking
// This used to work when list was iterated per type, not it is iterated once with type check
// first - thus if a definition is found before a type, it is earlier in the list.
return findExternal(scopeDetermeningResource, fqn2, importedNames, Match.EQUALS, DEF_AND_TYPE);
}
private SearchResult findExternal(EObject scopeDetermeningObject, QualifiedName fqn,
PPImportedNamesAdapter importedNames, SearchStrategy matchingStrategy, EClass... eClasses) {
if(scopeDetermeningObject == null)
throw new IllegalArgumentException("scope determening object is null");
if(fqn == null)
throw new IllegalArgumentException("name is null");
if(eClasses == null || eClasses.length < 1)
throw new IllegalArgumentException("eClass is null or empty");
// if(fqn.getSegmentCount() == 1 && "".equals(fqn.getSegment(0)))
// throw new IllegalArgumentException("FQN has one empty segment");
List<IEObjectDescription> targets = Lists.newArrayList();
Resource scopeDetermeningResource = scopeDetermeningObject.eResource();
// Not meaningful to continue if the name is empty (looking up nothing)
if(fqn.getSegmentCount() == 0)
return new SearchResult(targets, targets);
// Not meaningful to record the fact that an Absolute reference was used as nothing
// is named with an absolute FQN (i.e. it is only used to do lookup).
final boolean absoluteFQN = fqn.getSegmentCount() > 0 && "".equals(fqn.getSegment(0));
if(importedNames != null)
importedNames.add(absoluteFQN
? fqn.skipFirst(1)
: fqn);
if(scopeDetermeningResource != resource) {
// This is a lookup in the perspective of some other resource
// GIVE UP (the system is cleaning / is in bad state).
if(resource == null || scopeDetermeningResource == null)
return new SearchResult(targets, targets);
IResourceDescriptions descriptionIndex = indexProvider.getResourceDescriptions(scopeDetermeningResource);
IResourceDescription descr = descriptionIndex.getResourceDescription(scopeDetermeningResource.getURI());
// GIVE UP (the system is performing a build clean).
if(descr == null)
return new SearchResult(targets, targets);
QualifiedName nameOfScope = getNameOfScope(scopeDetermeningObject);
// for(IContainer visibleContainer : manager.getVisibleContainers(descr, descriptionIndex)) {
// for(EClass aClass : eClasses)
for(IEObjectDescription objDesc : new NameInScopeFilter(matchingStrategy, getExportedObjects(
descr, descriptionIndex),
// visibleContainer.getExportedObjects(),
fqn, nameOfScope, eClasses))
targets.add(objDesc);
}
else {
// This is lookup from the main resource perspective
QualifiedName nameOfScope = getNameOfScope(scopeDetermeningObject);
for(IEObjectDescription objDesc : new NameInScopeFilter(matchingStrategy, //
matchingStrategy.matchStartsWith()
? exportedPerLastSegment.values()
: exportedPerLastSegment.get(fqn.getLastSegment()), //
fqn, nameOfScope, eClasses))
targets.add(objDesc);
if(targets.size() == 0) {
// check the pattern variables
for(IEObjectDescription objDesc : exportedPatternVariables) {
String n = fqn.getLastSegment();
String on = objDesc.getName().getLastSegment();
if(n.startsWith(on) &&
Pattern.matches(
objDesc.getUserData(PPDSLConstants.VARIABLE_PATTERN), n.substring(on.length())))
targets.add(objDesc);
}
}
}
if(tracer.isTracing()) {
for(IEObjectDescription d : targets)
tracer.trace(" : ", converter.toString(d.getName()), " in: ", d.getEObjectURI().path());
}
return new SearchResult(searchPathAdjusted(targets), targets);
}
public SearchResult findFunction(EObject scopeDetermeningObject, QualifiedName fqn,
PPImportedNamesAdapter importedNames) {
return findExternal(scopeDetermeningObject, fqn, importedNames, Match.EQUALS, FUNC);
}
public SearchResult findFunction(EObject scopeDetermeningObject, String name, PPImportedNamesAdapter importedNames) {
return findFunction(scopeDetermeningObject, converter.toQualifiedName(name), importedNames);
}
public SearchResult findHostClasses(EObject scopeDetermeningResource, String name,
PPImportedNamesAdapter importedNames) {
if(name == null)
throw new IllegalArgumentException("name is null");
QualifiedName fqn = converter.toQualifiedName(name);
// make last segments initial char lower case (for references to the type itself - eg. 'File' instead of
// 'file'.
if(fqn.getSegmentCount() == 0)
return new SearchResult(); // can happen while editing
fqn = fqn.skipLast(1).append(toInitialLowerCase(fqn.getLastSegment()));
return findExternal(scopeDetermeningResource, fqn, importedNames, Match.EQUALS, CLASS_AND_TYPE);
}
private SearchResult findInherited(EObject scopeDetermeningObject, QualifiedName fqn,
PPImportedNamesAdapter importedNames, List<QualifiedName> stack, SearchStrategy matchingStrategy,
EClass[] classes) {
// Protect against circular inheritance
QualifiedName containerName = fqn.skipLast(1);
if(stack.contains(containerName))
return new SearchResult();
stack.add(containerName);
// find using the given name
SearchResult searchResult = findExternal(scopeDetermeningObject, fqn, importedNames, matchingStrategy, classes);
final List<IEObjectDescription> result = searchResult.getAdjusted();
// Collect raw results to enable better error reporting on path errors
List<IEObjectDescription> rawResult = Lists.newArrayList();
rawResult.addAll(searchResult.getRaw());
// Search up the inheritance chain if no match (on exact match), or if a prefix search
if(result.isEmpty() || !matchingStrategy.isExists()) {
// find the parent type
if(containerName.getSegmentCount() > 0) {
// there was a parent
List<IEObjectDescription> parentResult = findExternal(
scopeDetermeningObject, containerName, importedNames, Match.EQUALS, DEF_AND_TYPE).getAdjusted();
if(!parentResult.isEmpty()) {
IEObjectDescription firstFound = parentResult.get(0);
String parentName = firstFound.getUserData(PPDSLConstants.PARENT_NAME_DATA);
if(parentName != null && parentName.length() > 0) {
// find attributes for parent
QualifiedName attributeFqn = converter.toQualifiedName(parentName);
attributeFqn = attributeFqn.append(fqn.getLastSegment());
SearchResult inheritedSearchResult = findInherited(
scopeDetermeningObject, attributeFqn, importedNames, stack, matchingStrategy, classes);
result.addAll(inheritedSearchResult.getAdjusted());
rawResult.addAll(inheritedSearchResult.getRaw());
}
}
}
}
return new SearchResult(result, rawResult);
}
public IEObjectDescription findLocalVariableInLambda(Lambda lambda, String name,
PPImportedNamesAdapter importedNames, SearchStrategy matchingStrategy) {
TreeIterator<EObject> itor = lambda.eAllContents();
while(itor.hasNext()) {
EObject o = itor.next();
if(o instanceof AssignmentExpression || o instanceof AppendExpression) {
Expression lhs = ((BinaryExpression) o).getLeftExpr();
if(lhs instanceof VariableExpression) {
String vname = ((VariableExpression) lhs).getVarName();
if(vname.startsWith("$"))
if(vname.length() > 1)
vname = name.substring(1);
else
vname = "";
if(name.equals(vname))
return EObjectDescription.create(name, lhs);
}
}
else if(o instanceof DefinitionArgument) {
DefinitionArgument arg = (DefinitionArgument) o;
String argName = arg.getArgName();
if(argName.startsWith("$") && name.equals(argName.substring(1)) || name.equals(argName))
return EObjectDescription.create(name, o);
}
else if(o instanceof Lambda) {
itor.prune(); // do not visit nested Lambdas
}
}
return null;
}
public IEObjectDescription findLocalVariables(EObject scopeDetermeningObject, QualifiedName fqn,
PPImportedNamesAdapter importedNames, SearchStrategy matchingStrategy) {
if(fqn.getSegmentCount() != 1)
return null;
String name = fqn.getFirstSegment();
if(name == null || name.length() < 1)
return null;
// find enclosing lambda
for(EObject container = scopeDetermeningObject; container != null; container = container.eContainer()) {
if(container instanceof Lambda) {
IEObjectDescription desc = findLocalVariableInLambda(
(Lambda) container, name, importedNames, matchingStrategy);
if(desc != null)
return desc;
}
if(container instanceof HostClassDefinition)
break;
if(container instanceof Definition)
break;
if(container instanceof NodeDefinition)
break;
}
return null;
}
/**
* Finds a parameter or variable with the given name. More than one may be returned if the definition
* is ambiguous.
*
* @param scopeDetermeningResource
* @param name
* @param importedNames
* @return
*/
public SearchResult findVariable(EObject scopeDetermeningResource, QualifiedName fqn,
PPImportedNamesAdapter importedNames) {
if(fqn == null)
throw new IllegalArgumentException("fqn is null");
return findVariables(scopeDetermeningResource, fqn, importedNames, Match.NO_OUTER);
}
/**
* Finds a parameter or variable with the given name. More than one may be returned if the definition
* is ambiguous.
*
* @param scopeDetermeningResource
* @param name
* @param importedNames
* @return
*/
public SearchResult findVariable(EObject scopeDetermeningResource, String name, PPImportedNamesAdapter importedNames) {
if(name == null)
throw new IllegalArgumentException("name is null");
QualifiedName fqn = converter.toQualifiedName(name);
return findVariables(scopeDetermeningResource, fqn, importedNames, Match.NO_OUTER_EXISTS);
}
/**
* Finds all matching variables in current and inherited scopes.
*
* @param scopeDetermeningResource
* @param name
* @param importedNames
* @return
*/
public SearchResult findVariables(EObject scopeDetermeningResource, QualifiedName fqn,
PPImportedNamesAdapter importedNames) {
if(fqn == null)
throw new IllegalArgumentException("name is null");
return findVariables(scopeDetermeningResource, fqn, importedNames, Match.NO_OUTER);
}
/**
* Fins parameters and/or variables with matching name using the given matching/search strategy.
*
* @param scopeDetermeningObject
* @param fqn
* @param importedNames
* @param matchingStrategy
* @return
*/
public SearchResult findVariables(EObject scopeDetermeningObject, QualifiedName fqn,
PPImportedNamesAdapter importedNames, SearchStrategy matchingStrategy) {
if(metaCache == null)
cacheMetaParameters(scopeDetermeningObject);
final boolean singleSegment = fqn.getSegmentCount() == 1;
// If variable is a meta var, it is always found
if(singleSegment) {
IEObjectDescription metaVar = metaVarCache.get(fqn.getLastSegment());
if(metaVar != null)
return new SearchResult(Lists.newArrayList(metaVar), Lists.newArrayList(metaVar)); // what a waste...
// if inside a define, all meta parameters are available
if(isContainedInDefinition(scopeDetermeningObject)) {
IEObjectDescription metaParam = metaCache.get(fqn.getLastSegment());
if(metaParam != null)
return new SearchResult(Lists.newArrayList(metaParam), Lists.newArrayList(metaParam)); // what a waste...
}
IEObjectDescription desc = findLocalVariables(scopeDetermeningObject, fqn, importedNames, matchingStrategy);
if(desc != null)
return new SearchResult(Lists.newArrayList(desc));
}
SearchResult result = findExternal(
scopeDetermeningObject, fqn, importedNames, matchingStrategy, CLASSES_FOR_VARIABLES);
if(result.getAdjusted().size() > 0 && matchingStrategy.isExists())
return result;
QualifiedName scopeName = getNameOfScope(scopeDetermeningObject);
if(!scopeName.isEmpty()) {
fqn = scopeName.append(fqn);
return result.addAll(findInherited(
scopeDetermeningObject, fqn, importedNames, Lists.<QualifiedName> newArrayList(), matchingStrategy,
CLASSES_FOR_VARIABLES));
}
return result;
}
/**
* Finds all matching variables in current and inherited scopes.
*
* @param scopeDetermeningResource
* @param name
* @param importedNames
* @return
*/
public SearchResult findVariables(EObject scopeDetermeningResource, String name,
PPImportedNamesAdapter importedNames) {
if(name == null)
throw new IllegalArgumentException("name is null");
QualifiedName fqn = converter.toQualifiedName(name);
return findVariables(scopeDetermeningResource, fqn, importedNames, Match.NO_OUTER);
}
public SearchResult findVariablesPrefixed(EObject scopeDetermeningObject, QualifiedName fqn,
PPImportedNamesAdapter importedNames) {
return findVariables(scopeDetermeningObject, fqn, importedNames, Match.NO_OUTER_STARTS_WITH);
}
/**
* Produces an unmodifiable list of everything visible to the resource.
*
* @return
*/
public Collection<IEObjectDescription> getExportedDescriptions() {
return Collections.unmodifiableCollection(exportedPerLastSegment.values());
}
/**
* Produces iterable over all visible exports from all visible containers.
*
* @param descr
* @param descriptionIndex
* @return
*/
private Iterable<IEObjectDescription> getExportedObjects(IResourceDescription descr,
IResourceDescriptions descriptionIndex) {
return Iterables.concat(Iterables.transform(
manager.getVisibleContainers(descr, descriptionIndex),
new Function<IContainer, Iterable<IEObjectDescription>>() {
@Override
public Iterable<IEObjectDescription> apply(IContainer from) {
return from.getExportedObjects();
}
}));
}
public Collection<IEObjectDescription> getExportedPatternVariableDescriptions() {
return Collections.unmodifiableCollection(exportedPatternVariables);
}
/**
* Produces an unmodifiable Multimap mapping from last segment to list of visible exports
* ending with that value.
*
* @return
*/
public Multimap<String, IEObjectDescription> getExportedPerLastSegement() {
return Multimaps.unmodifiableMultimap(exportedPerLastSegment);
}
/**
* Produces the name of the scope where the given object 'o' is contained.
*
* @param o
* @return
*/
public QualifiedName getNameOfScope(EObject o) {
QualifiedName result = null;
for(; o != null; o = o.eContainer()) {
result = fqnProvider.getFullyQualifiedName(o);
if(result != null)
return result;
}
return QualifiedName.EMPTY;
}
private boolean isContainedInDefinition(EObject scoped) {
for(EObject o = scoped; o != null; o = o.eContainer())
if(o.eClass() == PPPackage.Literals.DEFINITION)
return true;
return false;
}
/**
* Adjusts the list of found targets in accordance with the search path for the resource being
* linked. This potentially resolves ambiguities (if found result is further away on the path).
* May return more than one result, if more than one resolution exist with the same path index.
*
* @param targets
* @return list of descriptions with lowest index.
*/
private List<IEObjectDescription> searchPathAdjusted(List<IEObjectDescription> targets) {
int minIdx = Integer.MAX_VALUE;
List<IEObjectDescription> result = Lists.newArrayList();
for(IEObjectDescription d : targets) {
int idx = searchPath.searchIndexOf(d);
if(idx < 0)
continue; // not found, skip
if(idx < minIdx) {
minIdx = idx;
result.clear(); // forget worse result
}
// only remember if equal to best found so far
if(idx <= minIdx)
result.add(d);
}
return result;
}
private String toInitialLowerCase(String s) {
if(s.length() < 1 || Character.isLowerCase(s.charAt(0)))
return s;
StringBuilder buf = new StringBuilder(s);
buf.setCharAt(0, Character.toLowerCase(buf.charAt(0)));
return buf.toString();
}
}