Package com.puppetlabs.geppetto.pp.dsl.linking

Source Code of com.puppetlabs.geppetto.pp.dsl.linking.PPResourceLinker

/**
* 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 static com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter.COLLECTOR_IS_REGULAR;
import static com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter.RESOURCE_IS_CLASSPARAMS;
import static com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter.RESOURCE_IS_OVERRIDE;

import java.util.Collection;
import java.util.List;
import java.util.ListIterator;

import com.puppetlabs.geppetto.common.tracer.ITracer;
import com.puppetlabs.geppetto.pp.AtExpression;
import com.puppetlabs.geppetto.pp.AttributeOperation;
import com.puppetlabs.geppetto.pp.AttributeOperations;
import com.puppetlabs.geppetto.pp.Case;
import com.puppetlabs.geppetto.pp.CaseExpression;
import com.puppetlabs.geppetto.pp.CollectExpression;
import com.puppetlabs.geppetto.pp.Definition;
import com.puppetlabs.geppetto.pp.ElseExpression;
import com.puppetlabs.geppetto.pp.ElseIfExpression;
import com.puppetlabs.geppetto.pp.ExprList;
import com.puppetlabs.geppetto.pp.Expression;
import com.puppetlabs.geppetto.pp.ExpressionTE;
import com.puppetlabs.geppetto.pp.FunctionCall;
import com.puppetlabs.geppetto.pp.HostClassDefinition;
import com.puppetlabs.geppetto.pp.IfExpression;
import com.puppetlabs.geppetto.pp.Lambda;
import com.puppetlabs.geppetto.pp.LiteralExpression;
import com.puppetlabs.geppetto.pp.LiteralName;
import com.puppetlabs.geppetto.pp.LiteralNameOrReference;
import com.puppetlabs.geppetto.pp.MethodCall;
import com.puppetlabs.geppetto.pp.NodeDefinition;
import com.puppetlabs.geppetto.pp.PPPackage;
import com.puppetlabs.geppetto.pp.ParameterizedExpression;
import com.puppetlabs.geppetto.pp.ParenthesisedExpression;
import com.puppetlabs.geppetto.pp.PuppetManifest;
import com.puppetlabs.geppetto.pp.ResourceBody;
import com.puppetlabs.geppetto.pp.ResourceExpression;
import com.puppetlabs.geppetto.pp.SelectorEntry;
import com.puppetlabs.geppetto.pp.UnlessExpression;
import com.puppetlabs.geppetto.pp.UnquotedString;
import com.puppetlabs.geppetto.pp.VariableExpression;
import com.puppetlabs.geppetto.pp.VariableTE;
import com.puppetlabs.geppetto.pp.adapters.ClassifierAdapter;
import com.puppetlabs.geppetto.pp.adapters.ClassifierAdapterFactory;
import com.puppetlabs.geppetto.pp.dsl.PPDSLConstants;
import com.puppetlabs.geppetto.pp.dsl.adapters.CrossReferenceAdapter;
import com.puppetlabs.geppetto.pp.dsl.adapters.PPImportedNamesAdapter;
import com.puppetlabs.geppetto.pp.dsl.adapters.PPImportedNamesAdapterFactory;
import com.puppetlabs.geppetto.pp.dsl.adapters.ResourcePropertiesAdapter;
import com.puppetlabs.geppetto.pp.dsl.adapters.ResourcePropertiesAdapterFactory;
import com.puppetlabs.geppetto.pp.dsl.contentassist.PPProposalsGenerator;
import com.puppetlabs.geppetto.pp.dsl.eval.PPStringConstantEvaluator;
import com.puppetlabs.geppetto.pp.dsl.linking.PPFinder.SearchResult;
import com.puppetlabs.geppetto.pp.dsl.linking.PPSearchPath.ISearchPathProvider;
import com.puppetlabs.geppetto.pp.dsl.validation.IPPDiagnostics;
import com.puppetlabs.geppetto.pp.dsl.validation.IValidationAdvisor;
import com.puppetlabs.geppetto.pp.dsl.validation.PPPatternHelper;
import com.puppetlabs.geppetto.pp.dsl.validation.ValidationPreference;
import com.puppetlabs.geppetto.pp.pptp.PPTPPackage;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.resource.IResourceDescription;
import org.eclipse.xtext.resource.IResourceDescriptions;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;

/**
* Handles special linking of ResourceExpression, ResourceBody and Function references.
*/
public class PPResourceLinker implements IPPDiagnostics {

  /**
   * Access to runtime configurable debug trace.
   */
  @Inject
  @Named(PPDSLConstants.PP_DEBUG_LINKER)
  private ITracer tracer;

  /**
   * Access to precompiled regular expressions
   */
  @Inject
  private PPPatternHelper patternHelper;

  /**
   * 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;

  /**
   * Classifies ResourceExpression based on its content (regular, override, etc).
   */
  @Inject
  private PPClassifier classifier;

  /**
   * PP FQN to/from Xtext QualifiedName converter.
   */
  @Inject
  private IQualifiedNameConverter converter;

  @Inject
  private PPProposalsGenerator proposer;

  @Inject
  private PPFinder ppFinder;

  @Inject
  private PPStringConstantEvaluator stringConstantEvaluator;

  // 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 static String proposalIssue(String issue, String[] proposals) {
    if(proposals == null || proposals.length == 0)
      return issue;
    return issue + IPPDiagnostics.ISSUE_PROPOSAL_SUFFIX;
  }

  private Resource resource;

  @Inject
  private ISearchPathProvider searchPathProvider;

  private PPSearchPath searchPath;

  @Inject
  private Provider<IValidationAdvisor> validationAdvisorProvider;

  private void _link(CollectExpression o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    classifier.classify(o);
    ClassifierAdapter adapter = ClassifierAdapterFactory.eINSTANCE.adapt(o);
    int resourceType = adapter.getClassifier();
    String resourceTypeName = adapter.getResourceTypeName();

    // Should not really happen, but if a workspace state is maintained with old things...
    if(resourceTypeName == null || resourceType != COLLECTOR_IS_REGULAR)
      return;

    internalLinkTypeExpression(o, o.getClassReference(), true, importedNames, acceptor);
    IEObjectDescription desc = adapter.getTargetObjectDescription(IEObjectDescription.class);
    if(desc != null)
      internalLinkAttributeOperations(o.getAttributes(), desc, importedNames, acceptor);
  }

  /**
   * Links an arbitrary interpolation expression. Handles the special case of a literal name expression e.g. "${literalName}" as
   * if it was "${$literalname}"
   *
   * @param o
   * @param importedNames
   * @param acceptor
   */
  private void _link(ExpressionTE o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    Expression expr = o.getExpression();
    if(expr instanceof ParenthesisedExpression)
      expr = ((ParenthesisedExpression) expr).getExpr();
    String varName = null;
    if(expr instanceof LiteralNameOrReference)
      varName = ((LiteralNameOrReference) expr).getValue();
    if(varName == null)
      return; // it is some other type of expression - it is validated as expression
    StringBuilder varName2 = new StringBuilder();
    if(!varName.startsWith("$"))
      varName2.append("$");
    varName2.append(varName);
    if(patternHelper.isVARIABLE(varName2.toString()))
      internalLinkVariable(
        expr, PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE, varName, importedNames, acceptor);
    else
      acceptor.acceptError(
        "Not a valid variable name", expr, PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE,
        IPPDiagnostics.ISSUE__NOT_VARNAME);
  }

  /**
   * polymorph {@link #link(EObject, IMessageAcceptor)}
   */
  private void _link(FunctionCall o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {

    // if not a name, then there is nothing to link, and this error is handled
    // elsewhere
    if(!(o.getLeftExpr() instanceof LiteralNameOrReference))
      return;
    final String name = ((LiteralNameOrReference) o.getLeftExpr()).getValue();
    internalLinkFunctionCall(o, o.getLeftExpr(), name, importedNames, acceptor);
  }

  private void _link(HostClassDefinition o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    final LiteralExpression parent = o.getParent();
    if(parent == null)
      return;
    String parentString = null;
    if(parent.eClass() == PPPackage.Literals.LITERAL_DEFAULT)
      parentString = "default";
    else if(parent.eClass() == PPPackage.Literals.LITERAL_NAME_OR_REFERENCE)
      parentString = ((LiteralNameOrReference) parent).getValue();
    if(parentString == null || parentString.length() < 1)
      return;

    SearchResult searchResult = ppFinder.findHostClasses(o, parentString, importedNames);
    List<IEObjectDescription> descs = searchResult.getAdjusted();
    if(descs.size() > 0) {
      // make list only contain unique references
      descs = Lists.newArrayList(Sets.newHashSet(descs));

      CrossReferenceAdapter.set(parent, descs);
      // record resolution at resource level
      importedNames.addResolved(descs);

      if(descs.size() > 1) {
        // this is an ambiguous link - multiple targets available and order depends on the
        // order at runtime (may not be the same).
        importedNames.addAmbiguous(descs);
        acceptor.acceptWarning(
          "Ambiguous reference to: '" + parentString + "' found in: " +
              visibleResourceList(o.eResource(), descs), o,
          PPPackage.Literals.HOST_CLASS_DEFINITION__PARENT,
          IPPDiagnostics.ISSUE__RESOURCE_AMBIGUOUS_REFERENCE,
          proposer.computeDistinctProposals(parentString, descs));
      }
      // must check for circularity
      List<QualifiedName> visited = Lists.newArrayList();
      visited.add(converter.toQualifiedName(o.getClassName()));
      checkCircularInheritence(o, descs, visited, acceptor, importedNames);
    }
    else if(searchResult.getRaw().size() > 0) {
      List<IEObjectDescription> raw = searchResult.getRaw();
      CrossReferenceAdapter.set(parent, raw);

      // Sort of ok, it is not on the current path
      // record resolution at resource level, so recompile knows about the dependencies
      importedNames.addResolved(raw);
      acceptor.acceptWarning(
        "Found outside current search path: '" + parentString + "'", o,
        PPPackage.Literals.HOST_CLASS_DEFINITION__PARENT, IPPDiagnostics.ISSUE__NOT_ON_PATH);

    }
    else {
      // record unresolved name at resource level
      addUnresolved(
        importedNames, converter.toQualifiedName(parentString), NodeModelUtils.findActualNodeFor(parent));
      // importedNames.addUnresolved(converter.toQualifiedName(parentString));
      CrossReferenceAdapter.clear(parent);

      // ... and finally, if there was neither a type nor a definition reference
      String[] proposals = proposer.computeProposals(
        parentString, ppFinder.getExportedDescriptions(), searchPath, CLASS_AND_TYPE);
      acceptor.acceptError(
        "Unknown class: '" + parentString + "'", o, //
        PPPackage.Literals.HOST_CLASS_DEFINITION__PARENT,
        proposalIssue(IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE, proposals), //
        proposals);
    }

    if(!advisor().allowInheritanceFromParameterizedClass()) {
      List<IEObjectDescription> targets = descs.size() > 0
          ? descs
          : searchResult.getRaw();
      if(targets.size() > 0) {
        IEObjectDescription target = targets.get(0);
        if(target.getUserData(PPDSLConstants.CLASS_ARG_COUNT) != null)
          acceptor.acceptError(
            "Can not inherit from a parameterized class in Puppet versions < 3.0.", o, //
            PPPackage.Literals.HOST_CLASS_DEFINITION__PARENT,
            IPPDiagnostics.ISSUE__INHERITANCE_WITH_PARAMETERS);
      }
    }
  }

  private void _link(MethodCall o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    // if not a name, then there is nothing to link, and this error is handled
    // elsewhere
    Expression methodExpr = o.getMethodExpr();
    if(!(methodExpr instanceof LiteralName))
      return;
    final String name = ((LiteralName) methodExpr).getValue();
    internalLinkFunctionCall(o, methodExpr, name, importedNames, acceptor);
  }

  /**
   * polymorph {@link #link(EObject, IMessageAcceptor)}
   */
  private void _link(ResourceBody o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor,
      boolean profileThis) {

    ResourceExpression resource = (ResourceExpression) o.eContainer();
    ClassifierAdapter adapter = ClassifierAdapterFactory.eINSTANCE.adapt(resource);
    if(adapter.getClassifier() == ClassifierAdapter.UNKNOWN) {
      classifier.classify(resource);
      adapter = ClassifierAdapterFactory.eINSTANCE.adapt(resource);
    }
    CLASSPARAMS: if(adapter.getClassifier() == RESOURCE_IS_CLASSPARAMS) {
      // pp: class { classname : parameter => value ... }

      final String className = stringConstantEvaluator.doToString(o.getNameExpr());
      if(className == null) {
        if(canBeAClassReference(o.getNameExpr())) {
          acceptor.acceptWarning(
            "Can not determine until runtime if this is a valid class reference (parameters not validated).", //
            o, // Flag entire body
              // PPPackage.Literals.RESOURCE_BODY__NAME_EXPR, //
            IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE);
        }
        else {
          acceptor.acceptError(
            "Not a valid class reference", o, PPPackage.Literals.RESOURCE_BODY__NAME_EXPR,
            IPPDiagnostics.ISSUE__NOT_CLASSREF);
        }
        CrossReferenceAdapter.clear(o.getNameExpr());
        break CLASSPARAMS;
      }
      SearchResult searchResult = ppFinder.findHostClasses(o, className, importedNames);
      List<IEObjectDescription> descs = searchResult.getAdjusted();
      if(descs.size() < 1) {
        if(searchResult.getRaw().size() > 0) {
          // Sort of ok
          importedNames.addResolved(searchResult.getRaw());
          CrossReferenceAdapter.set(o.getNameExpr(), searchResult.getRaw());
          acceptor.acceptWarning(
            "Found outside current search path (parameters not validated): '" + className + "'", o,
            PPPackage.Literals.RESOURCE_BODY__NAME_EXPR, IPPDiagnostics.ISSUE__NOT_ON_PATH);
          return; // skip validating parameters

        }

        // Add unresolved info at resource level
        addUnresolved(
          importedNames, converter.toQualifiedName(className),
          NodeModelUtils.findActualNodeFor(o.getNameExpr()));
        // importedNames.addUnresolved(converter.toQualifiedName(className));
        CrossReferenceAdapter.clear(o.getNameExpr());

        String[] proposals = proposer.computeProposals(
          className, ppFinder.getExportedDescriptions(), searchPath, CLASS_AND_TYPE);
        acceptor.acceptError(
          "Unknown class: '" + className + "'", o, //
          PPPackage.Literals.RESOURCE_BODY__NAME_EXPR,
          proposalIssue(IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE, proposals), //
          proposals);
        return; // not meaningful to continue (do not report errors for each "inner name")
      }
      if(descs.size() > 0) {
        descs = Lists.newArrayList(Sets.newHashSet(descs));
        // Report resolution at resource level
        importedNames.addResolved(descs);
        CrossReferenceAdapter.set(o.getNameExpr(), descs);

        if(descs.size() > 1) {
          // this is an ambiguous link - multiple targets available and order depends on the
          // order at runtime (may not be the same). ISSUE: o can be a ResourceBody
          importedNames.addAmbiguous(descs);
          acceptor.acceptWarning(
            "Ambiguous reference to: '" + className + "' found in: " +
                visibleResourceList(o.eResource(), descs), o,
            PPPackage.Literals.RESOURCE_BODY__NAME_EXPR,
            IPPDiagnostics.ISSUE__RESOURCE_AMBIGUOUS_REFERENCE,
            proposer.computeDistinctProposals(className, descs));
        }
        // use the first description found to find attributes
        internalLinkAttributeOperations(o.getAttributes(), descs.get(0), importedNames, acceptor);
      }

    }
    else if(adapter.getClassifier() != RESOURCE_IS_OVERRIDE || resource.getResourceExpr() instanceof AtExpression) {
      // normal resource or override file{} or File[x] { }
      IEObjectDescription desc = (IEObjectDescription) adapter.getTargetObjectDescription();
      // do not flag undefined parameters as errors if type is unknown
      if(desc != null) {
        internalLinkAttributeOperations(o.getAttributes(), desc, importedNames, acceptor);
      }
    }
  }

  /**
   * polymorph {@link #link(EObject, IMessageAcceptor)}
   */
  private void _link(ResourceExpression o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    classifier.classify(o);

    ClassifierAdapter adapter = ClassifierAdapterFactory.eINSTANCE.adapt(o);
    int resourceType = adapter.getClassifier();
    String resourceTypeName = adapter.getResourceTypeName();

    // Should not really happen, but if a workspace state is maintained with old things...
    if(resourceTypeName == null)
      return;

    // If resource is good, and not 'class', then it must have a known reference type.
    // the resource type - also requires getting the type name from the override's expression).
    if(resourceType == RESOURCE_IS_CLASSPARAMS) {
      // resource is pp: class { classname : parameter => value }
      // do nothing
    }
    else if(resourceType == RESOURCE_IS_OVERRIDE) {
      // TODO: possibly check a resource override if the expression is constant (or it is impossible to lookup
      // do nothing
      if(o.getResourceExpr() instanceof AtExpression) {
        internalLinkTypeExpression(
          o, ((AtExpression) o.getResourceExpr()).getLeftExpr(), true, importedNames, acceptor);
      }
    }
    else {
      internalLinkTypeExpression(o, o.getResourceExpr(), false, importedNames, acceptor);
    }
  }

  private void _link(VariableExpression o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    // a definition of a variable (as opposed to a reference) is a leftExpr in an assignment expression
    if(o.eContainer().eClass() == PPPackage.Literals.ASSIGNMENT_EXPRESSION &&
        PPPackage.Literals.BINARY_EXPRESSION__LEFT_EXPR == o.eContainingFeature())
      return; // is a definition
    internalLinkVariable(
      o, PPPackage.Literals.VARIABLE_EXPRESSION__VAR_NAME, o.getVarName(), importedNames, acceptor);

  }

  private void _link(VariableTE o, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    internalLinkVariable(o, PPPackage.Literals.VARIABLE_TE__VAR_NAME, o.getVarName(), importedNames, acceptor);
  }

  private void addUnresolved(PPImportedNamesAdapter importedNames, QualifiedName name, INode node) {
    importedNames.addUnresolved(name, node.getTotalStartLine(), node.getTotalOffset(), node.getTotalLength());
  }

  private void addUnresolved(PPImportedNamesAdapter importedNames, String name, INode node) {
    addUnresolved(importedNames, converter.toQualifiedName(name), node);
  }

  private IValidationAdvisor advisor() {
    return validationAdvisorProvider.get();
  }

  /**
   * Returns false if it is impossible that the given expression can result in a valid class
   * reference at runtime.
   *
   * TODO: this is a really stupid way of doing "type inference", but better than nothing.
   *
   * @param e
   * @return
   */
  private boolean canBeAClassReference(Expression e) {
    if(e == null)
      return false; // can happen while editing
    switch(e.eClass().getClassifierID()) {
      case PPPackage.HOST_CLASS_DEFINITION:
      case PPPackage.ASSIGNMENT_EXPRESSION:
      case PPPackage.NODE_DEFINITION:
      case PPPackage.DEFINITION:
      case PPPackage.IMPORT_EXPRESSION:
      case PPPackage.RELATIONAL_EXPRESSION:
      case PPPackage.RESOURCE_EXPRESSION:
      case PPPackage.IF_EXPRESSION:
      case PPPackage.SELECTOR_EXPRESSION:
      case PPPackage.AND_EXPRESSION:
      case PPPackage.OR_EXPRESSION:
      case PPPackage.CASE_EXPRESSION:
      case PPPackage.EQUALITY_EXPRESSION:
      case PPPackage.RELATIONSHIP_EXPRESSION:
        return false;
    }
    return true;
  }

  private void checkCircularInheritence(HostClassDefinition o, Collection<IEObjectDescription> descs,
      List<QualifiedName> stack, IMessageAcceptor acceptor, PPImportedNamesAdapter importedNames) {
    for(IEObjectDescription d : descs) {
      QualifiedName name = d.getName();
      if(stack.contains(name)) {
        // Gotcha!
        acceptor.acceptError( //
          "Circular inheritence", o, //
          PPPackage.Literals.HOST_CLASS_DEFINITION__PARENT, //
          IPPDiagnostics.ISSUE__CIRCULAR_INHERITENCE);
        return; // no use continuing
      }
      stack.add(name);
      String parentName = d.getUserData(PPDSLConstants.PARENT_NAME_DATA);
      if(parentName == null || parentName.length() == 0)
        continue;
      SearchResult searchResult = ppFinder.findHostClasses(d.getEObjectOrProxy(), parentName, importedNames);
      List<IEObjectDescription> parents = searchResult.getAdjusted(); // findHostClasses(d.getEObjectOrProxy(), parentName, importedNames);
      checkCircularInheritence(o, parents, stack, acceptor, importedNames);
    }
  }

  private boolean containsNameVar(List<IEObjectDescription> descriptions) {
    for(IEObjectDescription d : descriptions)
      if("true".equals(d.getUserData(PPDSLConstants.PARAMETER_NAMEVAR)))
        return true;
    return false;
  }

  private boolean containsRegularExpression(EObject o) {
    if(o.eClass().getClassifierID() == PPPackage.LITERAL_REGEX)
      return true;
    TreeIterator<EObject> itor = o.eAllContents();
    while(itor.hasNext())
      if(itor.next().eClass().getClassifierID() == PPPackage.LITERAL_REGEX)
        return true;
    return false;
  }

  /**
   * Returns the first type description. If none is found, the first description is returned.
   *
   * @param descriptions
   * @return
   */
  private IEObjectDescription getFirstTypeDescription(List<IEObjectDescription> descriptions) {
    for(IEObjectDescription e : descriptions) {
      if(e.getEClass() == PPTPPackage.Literals.TYPE)
        return e;
    }
    return descriptions.get(0);
  }

  private void internalLinkAttributeOperations(AttributeOperations aos, IEObjectDescription desc,
      PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    List<AttributeOperation> nameVariables = Lists.newArrayList();
    // Multimap<String, AttributeOperation> seen = ArrayListMultimap.create();

    if(aos != null && desc != null)
      for(AttributeOperation ao : aos.getAttributes()) {
        QualifiedName fqn = desc.getQualifiedName().append(ao.getKey());
        // Accept name if there is at least one type/definition that lists the key
        // NOTE/TODO: If there are other problems (multiple definitions with same name etc,
        // the property could be ok in one, but not in another instance.
        // finding that A'::x exists but not A''::x requires a lot more work
        List<IEObjectDescription> foundAttributes = ppFinder.findAttributes(aos, fqn, importedNames).getAdjusted();
        // if the ao is a namevar reference, remember it so uniqueness can be validated
        if(foundAttributes.size() > 0) {
          importedNames.addResolved(foundAttributes);
          CrossReferenceAdapter.set(ao, foundAttributes);
          // seen.put(ao.getKey(), ao);

          if(containsNameVar(foundAttributes))
            nameVariables.add(ao);
          continue; // found one such parameter == ok
        }
        // if the reference is to "name" (and it was not found), then this is a deprecated
        // reference to the namevar
        if("name".equals(ao.getKey())) {
          nameVariables.add(ao);
          acceptor.acceptWarning(
            "Deprecated use of the alias 'name'. Use the type's real name attribute.", ao,
            PPPackage.Literals.ATTRIBUTE_OPERATION__KEY,
            IPPDiagnostics.ISSUE__RESOURCE_DEPRECATED_NAME_ALIAS);
          continue;
        }
        String[] proposals = proposer.computeAttributeProposals(
          fqn, ppFinder.getExportedDescriptions(), searchPath);
        acceptor.acceptError(
          "Unknown attribute: '" + ao.getKey() + "' in definition: '" + desc.getName() + "'", ao,
          PPPackage.Literals.ATTRIBUTE_OPERATION__KEY,
          proposalIssue(IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_PROPERTY, proposals), proposals);
      }
    if(nameVariables.size() > 1) {
      for(AttributeOperation ao : nameVariables) {
        acceptor.acceptError(
          "Duplicate resource name attribute", ao, PPPackage.Literals.ATTRIBUTE_OPERATION__KEY,
          IPPDiagnostics.ISSUE__RESOURCE_DUPLICATE_NAME_PARAMETER);
        // // do not also flag as a general duplicate name
        // seen.removeAll(ao.getKey());
      }
    }
  }

  private void internalLinkFunctionArguments(String name, LiteralNameOrReference s, EList<Expression> statements,
      int idx, PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    // have 0:M classes as arguments
    if("require".equals(name) || "include".equals(name)) {

      // there should have been at least one argument
      if(idx >= statements.size()) {
        acceptor.acceptError("Call to '" + name + "' must have at least one argument.", s, //
          PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE, //
          IPPDiagnostics.ISSUE__REQUIRED_EXPRESSION);
        return;
      }

      Expression param = statements.get(idx);
      List<Expression> parameterList = null;
      if(param instanceof ExprList)
        parameterList = ((ExprList) param).getExpressions();
      else
        parameterList = Lists.newArrayList(param);

      int parameterIndex = -1;
      for(Expression pe : parameterList) {
        parameterIndex++;
        final String className = stringConstantEvaluator.doToString(pe);
        if(className != null) {
          SearchResult searchResult = ppFinder.findHostClasses(s, className, importedNames);
          List<IEObjectDescription> foundClasses = searchResult.getAdjusted(); // findHostClasses(o, className, importedNames);
          if(foundClasses.size() > 1) {
            // ambiguous
            importedNames.addAmbiguous(foundClasses);
            if(param instanceof ExprList)
              acceptor.acceptWarning(
                "Ambiguous reference to: '" + className + "' found in: " +
                    visibleResourceList(s.eResource(), foundClasses), //
                param, //
                PPPackage.Literals.EXPR_LIST__EXPRESSIONS,
                parameterIndex, //
                IPPDiagnostics.ISSUE__RESOURCE_AMBIGUOUS_REFERENCE,
                proposer.computeDistinctProposals(className, foundClasses));
            else
              acceptor.acceptWarning(
                "Ambiguous reference to: '" + className + "' found in: " +
                    visibleResourceList(s.eResource(), foundClasses), //
                param.eContainer(), param.eContainingFeature(),
                idx, //
                IPPDiagnostics.ISSUE__RESOURCE_AMBIGUOUS_REFERENCE,
                proposer.computeDistinctProposals(className, foundClasses));

          }
          else if(foundClasses.size() < 1) {
            if(searchResult.getRaw().size() > 0) {
              // sort of ok
              importedNames.addResolved(searchResult.getRaw());
              CrossReferenceAdapter.set(pe, searchResult.getRaw());

              if(param instanceof ExprList)
                acceptor.acceptWarning(
                  "Found outside current search path: '" + className + "'", param,
                  PPPackage.Literals.EXPR_LIST__EXPRESSIONS, parameterIndex,
                  IPPDiagnostics.ISSUE__NOT_ON_PATH);
              else
                acceptor.acceptWarning("Found outside current search path: '" + className + "'", //
                  param.eContainer(), param.eContainingFeature(), idx, // IPPDiagnostics.ISSUE__NOT_ON_PATH);
                  IPPDiagnostics.ISSUE__NOT_ON_PATH);

            }
            else {
              // not found
              // record unresolved name at resource level
              addUnresolved(importedNames, className, NodeModelUtils.findActualNodeFor(pe));
              // importedNames.addUnresolved(converter.toQualifiedName(className));
              CrossReferenceAdapter.clear(pe);

              String[] proposals = proposer.computeProposals(
                className, ppFinder.getExportedDescriptions(), searchPath, CLASS_AND_TYPE);
              String issueCode = proposalIssue(IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE, proposals);
              if(param instanceof ExprList) {
                acceptor.acceptError("Unknown class: '" + className + "'", //
                  param, //
                  PPPackage.Literals.EXPR_LIST__EXPRESSIONS, parameterIndex, //
                  issueCode, //
                  proposals);
              }
              else {
                acceptor.acceptError("Unknown class: '" + className + "'", //
                  param.eContainer(), param.eContainingFeature(), idx, //
                  issueCode, //
                  proposals);
              }
            }
          }
          else {
            // found
            importedNames.addResolved(foundClasses);
            CrossReferenceAdapter.set(pe, foundClasses);
          }
        }
        else {
          CrossReferenceAdapter.clear(pe);

          // warning or error depending on if this is a reasonable class reference expr or not
          String msg = null;
          boolean error = false;
          if(canBeAClassReference(pe)) {
            msg = "Can not determine until runtime if this is a valid class reference";
          }
          else {
            msg = "Not an acceptable parameter. Function '" + name + "' requires a class reference.";
            error = true;
          }
          if(param instanceof ExprList)
            acceptor.accept(error
                ? Severity.ERROR
                : Severity.WARNING, msg, //
              param, //
              PPPackage.Literals.EXPR_LIST__EXPRESSIONS, parameterIndex, //
              IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE);

          else
            acceptor.accept(error
                ? Severity.ERROR
                : Severity.WARNING, msg, //
              param.eContainer(), param.eContainingFeature(), idx, //
              IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE);
        }
      }
    }
  }

  /**
   * Link well known functions that must have references to defined things.
   *
   * @param o
   * @param importedNames
   * @param acceptor
   */
  private void internalLinkFunctionArguments(String name, ParameterizedExpression o,
      PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    // have 0:M classes as arguments
    if("require".equals(name) || "include".equals(name)) {
      int parameterIndex = -1;
      for(Expression pe : o.getParameters()) {
        parameterIndex++;
        final String className = stringConstantEvaluator.doToString(pe);
        if(className != null) {
          SearchResult searchResult = ppFinder.findHostClasses(o, className, importedNames);
          List<IEObjectDescription> foundClasses = searchResult.getAdjusted(); // findHostClasses(o, className, importedNames);
          if(foundClasses.size() > 1) {
            // ambiguous
            importedNames.addAmbiguous(foundClasses);
            acceptor.acceptWarning(
              "Ambiguous reference to: '" + className + "' found in: " +
                  visibleResourceList(o.eResource(), foundClasses), o,
              PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, parameterIndex,
              IPPDiagnostics.ISSUE__RESOURCE_AMBIGUOUS_REFERENCE,
              proposer.computeDistinctProposals(className, foundClasses));
          }
          else if(foundClasses.size() < 1) {
            if(searchResult.getRaw().size() > 0) {
              // sort of ok
              importedNames.addResolved(searchResult.getRaw());
              CrossReferenceAdapter.set(pe, searchResult.getRaw());
              acceptor.acceptWarning(
                "Found outside current search path: '" + className + "'", o,
                PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, parameterIndex,
                IPPDiagnostics.ISSUE__NOT_ON_PATH);
            }
            else {
              // not found
              // record unresolved name at resource level
              addUnresolved(importedNames, className, NodeModelUtils.findActualNodeFor(pe));
              // importedNames.addUnresolved(converter.toQualifiedName(className));
              CrossReferenceAdapter.clear(pe);

              String[] p = proposer.computeProposals(
                className, ppFinder.getExportedDescriptions(), searchPath, CLASS_AND_TYPE);
              acceptor.acceptError(
                "Unknown class: '" + className + "'", o, //
                PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, parameterIndex,
                proposalIssue(IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE, p), //
                p);
            }
          }
          else {
            // found
            importedNames.addResolved(foundClasses);
            CrossReferenceAdapter.set(pe, foundClasses);
          }
        }
        else {
          CrossReferenceAdapter.clear(pe);
          // warning or error depending on if this is a reasonable class reference expr or not
          if(canBeAClassReference(pe)) {
            acceptor.acceptWarning(
              "Can not determine until runtime if this is a valid class reference", //
              o, //
              PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, parameterIndex,
              IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE);
          }
          else {
            acceptor.acceptError(
              "Not an acceptable parameter. Function '" + name + "' requires a class reference.", //
              o, //
              PPPackage.Literals.PARAMETERIZED_EXPRESSION__PARAMETERS, parameterIndex,
              IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE);
          }
        }

      }
      // there should have been at least one argument
      if(parameterIndex < 0) {
        acceptor.acceptError("Call to '" + name + "' must have at least one argument.", o, //
          PPPackage.Literals.PARAMETERIZED_EXPRESSION__LEFT_EXPR, //
          IPPDiagnostics.ISSUE__REQUIRED_EXPRESSION);

      }
    }
  }

  private void internalLinkFunctionCall(ParameterizedExpression o, EObject nameExpr, String name,
      PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    final SearchResult searchResult = ppFinder.findFunction(o, name, importedNames);
    final List<IEObjectDescription> found = searchResult.getAdjusted(); // findFunction(o, name, importedNames);
    if(found.size() > 0) {
      // record resolution at resource level
      importedNames.addResolved(found);
      CrossReferenceAdapter.set(nameExpr, found);
      internalLinkFunctionArguments(name, o, importedNames, acceptor);
      return; // ok, found
    }
    if(searchResult.getRaw().size() > 0) {
      // Not a hard error, it may be valid with a different path
      // not found on path, but exists somewhere in what is visible
      // record resolution at resource level
      importedNames.addResolved(searchResult.getRaw());
      CrossReferenceAdapter.set(nameExpr, searchResult.getRaw());
      internalLinkFunctionArguments(name, o, importedNames, acceptor);
      acceptor.acceptWarning("Found outside current path: '" + name + "'", nameExpr, //
        IPPDiagnostics.ISSUE__NOT_ON_PATH //
      );
      return; // sort of ok
    }
    String[] proposals = proposer.computeProposals(name, ppFinder.getExportedDescriptions(), searchPath, FUNC);
    acceptor.acceptError("Unknown function: '" + name + "'", nameExpr, //
      proposalIssue(IPPDiagnostics.ISSUE__UNKNOWN_FUNCTION_REFERENCE, proposals), //
      proposals);
    // record failure at resource level
    addUnresolved(importedNames, name, NodeModelUtils.findActualNodeFor(nameExpr));
    CrossReferenceAdapter.clear(nameExpr);
  }

  /**
   * Produces an error if the given EObject o is not contained (nested) in an expression that injects
   * the result of a regular expression evaluation (i.e. $0 - $n).
   * The injecting expressions are unless, if, elseif, case (entry), case expression, and selector entry.
   *
   * TODO: Check if there are (less obvious) expressions
   *
   * @param o
   * @param varName
   * @param attr
   * @param acceptor
   */
  private void internalLinkRegexpVariable(EObject o, String varName, EAttribute attr, IMessageAcceptor acceptor) {
    // upp the containment chain
    for(EObject p = o.eContainer() /* , contained = o */; p != null; /* contained = p, */p = p.eContainer()) {
      switch(p.eClass().getClassifierID()) {
        case PPPackage.UNLESS_EXPRESSION:
          // o is either in cond, or then part
          // TODO: pedantic, check position in cond, must have regexp to the left.
          if(containsRegularExpression(((UnlessExpression) p).getCondExpr()))
            return;
          break;
        case PPPackage.IF_EXPRESSION:
          // o is either in cond, then or else part
          // TODO: pedantic, check position in cond, must have regexp to the left.
          if(containsRegularExpression(((IfExpression) p).getCondExpr()))
            return;
          break;
        case PPPackage.ELSE_IF_EXPRESSION:
          // o is either in cond, then or else part
          // TODO: pedantic, check position in cond, must have regexp to the left.
          if(containsRegularExpression(((ElseIfExpression) p).getCondExpr()))
            return;
          break;
        case PPPackage.SELECTOR_ENTRY:
          // TODO: pedantic, check position in leftExpr, must have regexp to the left.
          if(containsRegularExpression(((SelectorEntry) p).getLeftExpr()))
            return;
          break;
        // TODO: CHECK IF THIS ISOTHERIC CASE IS SUPPORTED
        // case PPPackage.SELECTOR_EXPRESSION:
        // if(containsRegularExpression(((SelectorExpression)p).get))
        // return;
        // break;
        case PPPackage.CASE:
          // i.e. case expr { v0, v1, v2 : statements }
          for(EObject v : ((Case) p).getValues())
            if(containsRegularExpression(v))
              return;
          break;
        case PPPackage.CASE_EXPRESSION:
          // TODO: Investigate if this is allowed i.e.:
          // case $α =~ /regexp { true : $a = $0; false : $a = $0 }
          if(containsRegularExpression(((CaseExpression) p).getSwitchExpr()))
            return;
          break;
      }
    }
    acceptor.acceptWarning(
      "Corresponding regular expression not found. Value of '" + varName +
          "' can only be undefined at this point: '" + varName + "'", o, attr,
      IPPDiagnostics.ISSUE__UNKNOWN_REGEXP);

  }

  private void internalLinkTypeExpression(EObject o, EObject reference, boolean upperCaseProposals,
      PPImportedNamesAdapter importedNames, IMessageAcceptor acceptor) {
    ClassifierAdapter adapter = ClassifierAdapterFactory.eINSTANCE.adapt(o);
    // int resourceType = adapter.getClassifier();
    String resourceTypeName = adapter.getResourceTypeName();

    // Should not really happen, but if a workspace state is maintained with old things...
    if(resourceTypeName == null)
      return;

    // normal resource
    SearchResult searchResult = ppFinder.findDefinitions(o, resourceTypeName, importedNames);
    List<IEObjectDescription> descs = searchResult.getAdjusted(); // findDefinitions(o, resourceTypeName, importedNames);
    if(descs.size() > 0) {
      // make list only contain unique references
      descs = Lists.newArrayList(Sets.newHashSet(descs));
      removeDisqualifiedContainers(descs, o);
      // if any remain, pick the first type (or the first if there are no types)
      IEObjectDescription usedResolution = null;
      if(descs.size() > 0) {
        usedResolution = getFirstTypeDescription(descs);
        adapter.setTargetObject(usedResolution); // Resource expression's resolution of type
        CrossReferenceAdapter.set(reference, descs); // the actual reference
      }

      if(descs.size() > 1) {
        // this is an ambiguous link - multiple targets available and order depends on the
        // order at runtime (may not be the same).
        importedNames.addAmbiguous(descs);
        acceptor.acceptWarning(
          "Ambiguous reference to: '" + resourceTypeName + "' found in: " +
              visibleResourceList(o.eResource(), descs), reference,
          IPPDiagnostics.ISSUE__RESOURCE_AMBIGUOUS_REFERENCE,
          proposer.computeDistinctProposals(resourceTypeName, descs, upperCaseProposals));
      }
      // Add resolved information at resource level
      if(usedResolution != null)
        importedNames.addResolved(usedResolution);
      else
        importedNames.addResolved(descs);
    }
    else if(searchResult.getRaw().size() > 0) {
      // sort of ok
      importedNames.addResolved(searchResult.getRaw());
      CrossReferenceAdapter.set(reference, searchResult.getRaw());

      // do not record the type

      acceptor.acceptWarning(
        "Found outside search path: '" + resourceTypeName + "'", reference, IPPDiagnostics.ISSUE__NOT_ON_PATH);

    }
    // only report unresolved if no raw (since if not found adjusted, error is reported as warning)
    if(searchResult.getRaw().size() < 1) {
      CrossReferenceAdapter.clear(reference);
      // ... and finally, if there was neither a type nor a definition reference
      if(adapter.getResourceType() == null && adapter.getTargetObjectDescription() == null) {
        // Add unresolved info at resource level
        addUnresolved(importedNames, resourceTypeName, NodeModelUtils.findActualNodeFor(reference));
        String[] proposals = proposer.computeProposals(
          resourceTypeName, ppFinder.getExportedDescriptions(), upperCaseProposals, searchPath, DEF_AND_TYPE);
        acceptor.acceptError("Unknown resource type: '" + resourceTypeName + "'", reference,
        // PPPackage.Literals.RESOURCE_EXPRESSION__RESOURCE_EXPR, //
        proposalIssue(IPPDiagnostics.ISSUE__RESOURCE_UNKNOWN_TYPE, proposals), //
          proposals);
      }
    }
  }

  /**
   * Links/validates unparenthesized function calls.
   *
   * @param statements
   * @param acceptor
   */
  private void internalLinkUnparenthesisedCall(EList<Expression> statements, PPImportedNamesAdapter importedNames,
      IMessageAcceptor acceptor) {
    if(statements == null || statements.size() == 0)
      return;

    each_top: for(int i = 0; i < statements.size(); i++) {
      Expression s = statements.get(i);

      // -- may be a non parenthesized function call
      if(s instanceof LiteralNameOrReference) {
        // there must be one more expression in the list (a single argument, or
        // an Expression list
        // TODO: different issue, can be fixed by adding "()" if this is a function call without
        // parameters, but difficult as validator does not know if function exists (would need
        // an adapter to be able to pick this up in validation).
        if((i + 1) >= statements.size()) {
          continue each_top; // error reported by validation.
        }
        // the next expression is consumed as a single arg, or an expr list
        // TODO: if there are expressions that can not be used as arguments check them here
        i++;
        // Expression arg = statements.get(i); // not used yet...
        String name = ((LiteralNameOrReference) s).getValue();
        SearchResult searchResult = ppFinder.findFunction(s, name, importedNames);
        final boolean existsAdjusted = searchResult.getAdjusted().size() > 0;
        final boolean existsOutside = searchResult.getRaw().size() > 0;

        recordCrossReference(
          converter.toQualifiedName(name), searchResult, existsAdjusted, existsOutside, true, importedNames,
          s);
        if(existsAdjusted || existsOutside) {
          internalLinkFunctionArguments(
            name, (LiteralNameOrReference) s, statements, i, importedNames, acceptor);

          if(!existsAdjusted)
            acceptor.acceptWarning("Found outside current path: '" + name + "'", s, //
              IPPDiagnostics.ISSUE__NOT_ON_PATH);
          continue each_top; // ok, found
        }
        String[] proposals = proposer.computeProposals(
          name, ppFinder.getExportedDescriptions(), searchPath, FUNC);
        acceptor.acceptError(
          "Unknown function: '" + name + "'", s, PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE,
          proposalIssue(IPPDiagnostics.ISSUE__UNKNOWN_FUNCTION_REFERENCE, proposals), //
          proposals);

        continue each_top;
      }
    }
  }

  private void internalLinkVariable(EObject o, EAttribute attr, String varName, PPImportedNamesAdapter importedNames,
      IMessageAcceptor acceptor) {
    boolean qualified = false;
    boolean global = false;
    boolean disqualified = false;
    QualifiedName qName = null;
    SearchResult searchResult = null;
    boolean existsAdjusted = false; // variable found as stated
    boolean existsOutside = false; // if not found, reflects if found outside search path
    try {
      qName = converter.toQualifiedName(varName);
      if(patternHelper.isDECIMALVAR(varName)) {
        internalLinkRegexpVariable(o, varName, attr, acceptor);
        return;
      }

      qualified = qName.getSegmentCount() > 1;
      global = qName.getFirstSegment().length() == 0;
      searchResult = ppFinder.findVariable(o, qName, importedNames);

      // remove all references to not yet initialized variables
      disqualified = (0 != removeDisqualifiedVariables(searchResult.getRaw(), o));
      if(disqualified) // adjusted can not have disqualified entries if raw did not have them
        removeDisqualifiedVariables(searchResult.getAdjusted(), o);
      existsAdjusted = searchResult.getAdjusted().size() > 0;
      existsOutside = existsAdjusted
          ? false // we are not interested in that it may be both adjusted and raw
          : searchResult.getRaw().size() > 0;
    }
    catch(IllegalArgumentException iae) {
      // Can happen if there is something seriously wrong with the qualified name, should be caught by
      // validation - just ignore it here
      return;
    }
    IValidationAdvisor advisor = advisor();

    boolean mustExist = true;
    if(qualified && global) {
      // TODO: Possible future improvement when more global variables are known.
      // if reported as error now, almost all global variables would be flagged as errors.
      // Future enhancement could warn about those that are not found (resolved at runtime).
      mustExist = false;
    }

    // Record facts at resource and model levels about where variable was found
    recordCrossReference(qName, searchResult, existsAdjusted, existsOutside, mustExist, importedNames, o);

    if(mustExist) {
      if(!(existsAdjusted || existsOutside)) {
        // importedNames.addUnresolved(qName);

        // found nowhere
        if(qualified || advisor.unqualifiedVariables().isWarningOrError()) {
          StringBuilder message = new StringBuilder();
          if(disqualified)
            message.append("Reference to not yet initialized variable: ");
          else
            message.append(qualified
                ? "Unknown variable: '"
                : "Unqualified and Unknown variable: '");
          message.append(varName);
          message.append("'");

          String issue = disqualified
              ? IPPDiagnostics.ISSUE__UNINITIALIZED_VARIABLE
              : IPPDiagnostics.ISSUE__UNKNOWN_VARIABLE;
          if(disqualified || advisor.unqualifiedVariables() == ValidationPreference.ERROR)
            acceptor.acceptError(message.toString(), o, attr, issue);
          else
            acceptor.acceptWarning(message.toString(), o, attr, issue);
        }
      }
      else if(!existsAdjusted && existsOutside) {
        // found outside
        if(qualified || advisor.unqualifiedVariables().isWarningOrError())
          acceptor.acceptWarning(
            "Found outside current search path variable: '" + varName + "'", o, attr,
            IPPDiagnostics.ISSUE__NOT_ON_PATH);
      }
    }

  }

  /**
   * Returns true if the descriptions resource path is the same as for the given object and
   * the fragment path of the given object starts with the fragment path of the description.
   *
   * (An alternative impl would be to first check if they are from the same resource - if so,
   * it is know that this resource is loaded (since we have the given o) and it should
   * be possible to search up the containment chain.
   *
   * @param desc
   * @param o
   * @return
   */
  private boolean isParent(IEObjectDescription desc, EObject o) {
    URI descUri = desc.getEObjectURI();
    URI oUri = o.eResource().getURI();
    if(!descUri.path().equals(oUri.path()))
      return false;
    // same resource, if desc's fragment is in at the start of the path, then o is contained
    boolean result = o.eResource().getURIFragment(o).startsWith(descUri.fragment());
    return result;
  }

  /**
   * Link all resources in the model
   *
   * @param model
   * @param acceptor
   */
  public void link(EObject model, IMessageAcceptor acceptor, boolean profileThis) {
    ppFinder.configure(model);
    resource = model.eResource();
    searchPath = searchPathProvider.get(resource);

    // clear names remembered in the past
    PPImportedNamesAdapter importedNames = PPImportedNamesAdapterFactory.eINSTANCE.adapt(resource);
    importedNames.clear();

    IResourceDescriptions descriptionIndex = indexProvider.getResourceDescriptions(resource);
    IResourceDescription descr = descriptionIndex.getResourceDescription(resource.getURI());

    if(descr == null) {
      if(tracer.isTracing()) {
        tracer.trace("Cleaning resource: " + resource.getURI().path());
      }
      return;
    }

    if(tracer.isTracing())
      tracer.trace("Linking resource: ", resource.getURI().path(), "{");

    // Need to get everything in the resource, not just the content of the PuppetManifest (as the manifest has top level
    // expressions that need linking).
    TreeIterator<EObject> everything = resource.getAllContents();
    // it is important that ResourceExpresion are linked before ResourceBodyExpression (but that should
    // be ok with the tree iterator as the bodies are contained).

    while(everything.hasNext()) {
      EObject o = everything.next();
      EClass clazz = o.eClass();
      switch(clazz.getClassifierID()) {
        case PPPackage.EXPRESSION_TE:
          _link((ExpressionTE) o, importedNames, acceptor);
          break;

        case PPPackage.VARIABLE_TE:
          _link((VariableTE) o, importedNames, acceptor);
          break;

        case PPPackage.VARIABLE_EXPRESSION:
          _link((VariableExpression) o, importedNames, acceptor);
          break;

        case PPPackage.RESOURCE_EXPRESSION:
          _link((ResourceExpression) o, importedNames, acceptor);
          break;

        case PPPackage.RESOURCE_BODY:
          _link((ResourceBody) o, importedNames, acceptor, profileThis);
          break;

        case PPPackage.FUNCTION_CALL:
          _link((FunctionCall) o, importedNames, acceptor);
          break;

        // these are needed to link un-parenthesised function calls
        case PPPackage.PUPPET_MANIFEST:
          internalLinkUnparenthesisedCall(((PuppetManifest) o).getStatements(), importedNames, acceptor);
          break;

        case PPPackage.IF_EXPRESSION:
          internalLinkUnparenthesisedCall(((IfExpression) o).getThenStatements(), importedNames, acceptor);
          break;

        case PPPackage.UNLESS_EXPRESSION:
          internalLinkUnparenthesisedCall(((UnlessExpression) o).getThenStatements(), importedNames, acceptor);
          break;

        case PPPackage.ELSE_EXPRESSION:
          internalLinkUnparenthesisedCall(((ElseExpression) o).getStatements(), importedNames, acceptor);
          break;

        case PPPackage.ELSE_IF_EXPRESSION:
          internalLinkUnparenthesisedCall(((ElseIfExpression) o).getThenStatements(), importedNames, acceptor);
          break;

        case PPPackage.NODE_DEFINITION:
          internalLinkUnparenthesisedCall(((NodeDefinition) o).getStatements(), importedNames, acceptor);
          break;

        case PPPackage.DEFINITION:
          internalLinkUnparenthesisedCall(((Definition) o).getStatements(), importedNames, acceptor);
          break;

        case PPPackage.CASE:
          internalLinkUnparenthesisedCall(((Case) o).getStatements(), importedNames, acceptor);
          break;

        case PPPackage.HOST_CLASS_DEFINITION:
          _link((HostClassDefinition) o, importedNames, acceptor);
          internalLinkUnparenthesisedCall(((HostClassDefinition) o).getStatements(), importedNames, acceptor);
          break;

        case PPPackage.COLLECT_EXPRESSION:
          _link((CollectExpression) o, importedNames, acceptor);
          break;

        case PPPackage.METHOD_CALL:
          _link((MethodCall) o, importedNames, acceptor);
          break;

        case PPPackage.JAVA_LAMBDA:
        case PPPackage.RUBY_LAMBDA:
          internalLinkUnparenthesisedCall(((Lambda) o).getStatements(), importedNames, acceptor);
          break;

        case PPPackage.UNQUOTED_STRING:
          Expression expr = ((UnquotedString) o).getExpression();
          if(expr != null && expr instanceof LiteralNameOrReference) {
            //
            String varName = ((LiteralNameOrReference) expr).getValue();
            StringBuilder varName2 = new StringBuilder();
            if(!varName.startsWith("$"))
              varName2.append("$");
            varName2.append(varName);
            if(patternHelper.isVARIABLE(varName2.toString()))
              internalLinkVariable(
                expr, PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE, varName, importedNames,
                acceptor);
            else
              acceptor.acceptError(
                "Not a valid variable name", expr, PPPackage.Literals.LITERAL_NAME_OR_REFERENCE__VALUE,
                IPPDiagnostics.ISSUE__NOT_VARNAME);

          }
          break;
      }
    }
    if(tracer.isTracing())
      tracer.trace("}");

  }

  /**
   * Record facts about resolution of qName at model and resource level
   *
   * @param qName
   *            - the name being resolved
   * @param searchResult
   *            - the result
   * @param existsAdjusted
   *            - if the adjusted result should be used
   * @param existsOutside
   *            - if the raw result should be used
   * @param mustExists
   *            - if non existence should be recored as unresolved
   * @param importedNames
   *            - resource level fact recorder
   * @param o
   *            - the model element where positive result is (also) recorded.
   */
  private void recordCrossReference(QualifiedName qName, SearchResult searchResult, boolean existsAdjusted,
      boolean existsOutside, boolean mustExists, PPImportedNamesAdapter importedNames, EObject o) {
    List<IEObjectDescription> descriptions = null;

    if(existsAdjusted)
      descriptions = searchResult.getAdjusted();
    else if(existsOutside)
      searchResult.getRaw();

    if(descriptions != null) {
      importedNames.addResolved(descriptions);
      CrossReferenceAdapter.set(o, descriptions);
    }
    else {
      CrossReferenceAdapter.clear(o);
    }
    if(mustExists && !(existsAdjusted || existsOutside)) {
      addUnresolved(importedNames, qName, NodeModelUtils.findActualNodeFor(o));
      // importedNames.addUnresolved(qName);
    }

  }

  /**
   * Surgically remove all disqualified descriptions (those that are HostClass and a container
   * of the given object 'o'.
   *
   * @param descs
   * @param o
   */
  private void removeDisqualifiedContainers(List<IEObjectDescription> descs, EObject o) {
    if(descs == null)
      return;
    ListIterator<IEObjectDescription> litor = descs.listIterator();
    while(litor.hasNext()) {
      IEObjectDescription x = litor.next();
      if(x.getEClass() == PPPackage.Literals.DEFINITION || !isParent(x, o))
        continue;
      litor.remove();
    }
  }

  private int removeDisqualifiedVariables(List<IEObjectDescription> descs, EObject o) {
    return removeDisqualifiedVariablesDefinitionArg(descs, o) + removeDisqualifiedVariablesAssignment(descs, o);
  }

  /**
   * Disqualifies variables that appear on the lhs of an assignment
   *
   * @param descs
   * @param o
   * @return
   */
  private int removeDisqualifiedVariablesAssignment(List<IEObjectDescription> descs, EObject o) {
    if(descs == null || descs.size() == 0)
      return 0;
    EObject p = null;
    for(p = o; p != null; p = p.eContainer()) {
      int classifierId = p.eClass().getClassifierID();
      if(classifierId == PPPackage.ASSIGNMENT_EXPRESSION || classifierId == PPPackage.APPEND_EXPRESSION)
        break;
    }
    if(p == null)
      return 0; // not in an assignment expression

    // p is a BinaryExpression at this point, we want it's parent being an abstract Definition
    final String definitionFragment = p.eResource().getURIFragment(p);
    final String definitionURI = p.eResource().getURI().toString();

    int removedCount = 0;
    ListIterator<IEObjectDescription> litor = descs.listIterator();
    while(litor.hasNext()) {
      IEObjectDescription x = litor.next();
      URI xURI = x.getEObjectURI();
      // if in the same resource, and contain by the same EObject
      if(xURI.toString().startsWith(definitionURI) && xURI.fragment().startsWith(definitionFragment)) {
        litor.remove();
        removedCount++;
      }
    }
    return removedCount;

  }

  /**
   * Remove variables/entries that are not yet initialized. These are the values
   * defined in the same name and type if the variable is contained in a definition argument
   *
   * <p>
   * e.g. in define selfref($selfa = $selfref::selfa, $selfb=$selfa::x) { $x=10 } none of the references to selfa, or x are disqualified.
   *
   * @param descs
   * @param o
   * @return the number of disqualified variables removed from the list
   */
  private int removeDisqualifiedVariablesDefinitionArg(List<IEObjectDescription> descs, EObject o) {
    if(descs == null || descs.size() == 0)
      return 0;
    EObject p = o;
    while(p != null && p.eClass().getClassifierID() != PPPackage.DEFINITION_ARGUMENT)
      p = p.eContainer();
    if(p == null)
      return 0; // not in a definition argument value tree

    // p is a DefinitionArgument at this point, we want it's parent being an abstract Definition
    EObject d = p.eContainer();
    if(d == null)
      return 0; // broken model
    d = d.eContainer();
    final String definitionFragment = d.eResource().getURIFragment(d);
    final String definitionURI = d.eResource().getURI().toString();

    int removedCount = 0;
    ListIterator<IEObjectDescription> litor = descs.listIterator();
    while(litor.hasNext()) {
      IEObjectDescription x = litor.next();
      URI xURI = x.getEObjectURI();
      // if in the same resource, and contain by the same EObject
      if(xURI.toString().startsWith(definitionURI) && xURI.fragment().startsWith(definitionFragment)) {
        litor.remove();
        removedCount++;
      }
    }
    return removedCount;
  }

  /**
   * Collects the (unique) set of resource paths and returns a message with <=5 (+ ... and x others).
   *
   * @param descriptors
   * @return
   */
  private String visibleResourceList(Resource r, List<IEObjectDescription> descriptors) {
    ResourcePropertiesAdapter adapter = ResourcePropertiesAdapterFactory.eINSTANCE.adapt(r);
    URI root = (URI) adapter.get(PPDSLConstants.RESOURCE_PROPERTY__ROOT_URI);

    // collect the (unique) resource paths
    List<String> resources = Lists.newArrayList();
    for(IEObjectDescription d : descriptors) {
      URI uri = EcoreUtil.getURI(d.getEObjectOrProxy());
      if(root != null) {
        uri = uri.deresolve(root.appendSegment(""));
      }
      boolean isPptpResource = "pptp".equals(uri.fileExtension());
      String path = isPptpResource
          ? uri.lastSegment().replace(".pptp", "")
          : uri.devicePath();
      if(!resources.contains(path))
        resources.add(path);
    }
    StringBuffer buf = new StringBuffer();
    buf.append(resources.size());
    buf.append(" resource");
    buf.append(resources.size() > 1
        ? "s ["
        : " [");

    int count = 0;

    // if there are 4 include all, else limit to 3 - typically 2 (fresh user mistake) or is *many*
    final int countCap = resources.size() == 4
        ? 4
        : 3;
    for(String s : resources) {
      if(count > 0)
        buf.append(", ");
      buf.append(s);
      if(count++ > countCap) {
        buf.append("and ");
        buf.append(resources.size() - countCap);
        buf.append(" other files...");
        break;
      }
    }
    buf.append("]");
    return buf.toString();
  }
}
TOP

Related Classes of com.puppetlabs.geppetto.pp.dsl.linking.PPResourceLinker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.