/** * 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.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.cloudsmith.geppetto.common.tracer.ITracer; import org.cloudsmith.geppetto.pp.AppendExpression; import org.cloudsmith.geppetto.pp.AssignmentExpression; import org.cloudsmith.geppetto.pp.BinaryExpression; import org.cloudsmith.geppetto.pp.Definition; import org.cloudsmith.geppetto.pp.DefinitionArgument; import org.cloudsmith.geppetto.pp.Expression; import org.cloudsmith.geppetto.pp.HostClassDefinition; import org.cloudsmith.geppetto.pp.Lambda; import org.cloudsmith.geppetto.pp.NodeDefinition; import org.cloudsmith.geppetto.pp.PPPackage; import org.cloudsmith.geppetto.pp.ResourceBody; import org.cloudsmith.geppetto.pp.VariableExpression; import org.cloudsmith.geppetto.pp.dsl.PPDSLConstants; import org.cloudsmith.geppetto.pp.dsl.adapters.PPImportedNamesAdapter; import org.cloudsmith.geppetto.pp.dsl.linking.NameInScopeFilter.Match; import org.cloudsmith.geppetto.pp.dsl.linking.NameInScopeFilter.SearchStrategy; import org.cloudsmith.geppetto.pp.dsl.linking.PPSearchPath.ISearchPathProvider; import org.cloudsmith.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(); } }