protected String findPrefix(
      List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>
          expectedElements,
      org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal expectedAtCursor,
      String content,
      int cursorOffset) {
    if (cursorOffset < 0) {
      return "";
    }

    int end = 0;
    for (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal expectedElement :
        expectedElements) {
      if (expectedElement == expectedAtCursor) {
        final int start = expectedElement.getStartExcludingHiddenTokens();
        if (start >= 0 && start < Integer.MAX_VALUE) {
          end = start;
        }
        break;
      }
    }
    end = Math.min(end, cursorOffset);
    final String prefix = content.substring(end, Math.min(content.length(), cursorOffset));
    return prefix;
  }
 /**
  * Calculates the prefix for each given expected element. The prefix depends on the current
  * document content, the cursor position, and the position where the element is expected.
  */
 protected void setPrefixes(
     List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>
         expectedElements,
     String content,
     int cursorOffset) {
   if (cursorOffset < 0) {
     return;
   }
   for (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal expectedElement :
       expectedElements) {
     String prefix = findPrefix(expectedElements, expectedElement, content, cursorOffset);
     expectedElement.setPrefix(prefix);
   }
 }
 public org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal[]
     getElementsExpectedAt(
         org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal[]
             allExpectedElements,
         int cursorOffset) {
   List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal> expectedAtCursor =
       new ArrayList<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>();
   for (int i = 0; i < allExpectedElements.length; i++) {
     org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal expectedElement =
         allExpectedElements[i];
     int startIncludingHidden = expectedElement.getStartIncludingHiddenTokens();
     int end = getEnd(allExpectedElements, i);
     if (cursorOffset >= startIncludingHidden && cursorOffset <= end) {
       expectedAtCursor.add(expectedElement);
     }
   }
   return expectedAtCursor.toArray(
       new org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal
           [expectedAtCursor.size()]);
 }
  /**
   * Removes all expected elements that refer to the same terminal and that start at the same
   * position.
   */
  protected void removeDuplicateEntries(
      List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>
          expectedElements) {
    int size = expectedElements.size();
    // We split the list of expected elements into buckets where each bucket contains
    // the elements that start at the same position.
    Map<Integer, List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>>
        map =
            new LinkedHashMap<
                Integer,
                List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>>();
    for (int i = 0; i < size; i++) {
      org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal elementAtIndex =
          expectedElements.get(i);
      int start1 = elementAtIndex.getStartExcludingHiddenTokens();
      List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal> list =
          map.get(start1);
      if (list == null) {
        list =
            new ArrayList<
                org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>();
        map.put(start1, list);
      }
      list.add(elementAtIndex);
    }

    // Then, we remove all duplicate elements from each bucket individually.
    for (int position : map.keySet()) {
      List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal> list =
          map.get(position);
      removeDuplicateEntriesFromBucket(list);
    }

    // After removing all duplicates, we merge the buckets.
    expectedElements.clear();
    for (int position : map.keySet()) {
      List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal> list =
          map.get(position);
      expectedElements.addAll(list);
    }
  }
 /**
  * Removes all expected elements that refer to the same terminal. Attention: This method assumes
  * that the given list of expected terminals contains only elements that start at the same
  * position.
  */
 protected void removeDuplicateEntriesFromBucket(
     List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>
         expectedElements) {
   int size = expectedElements.size();
   for (int i = 0; i < size - 1; i++) {
     org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal elementAtIndex =
         expectedElements.get(i);
     org.emftext.language.xpath3.resource.xpath3.IXpath3ExpectedElement terminal =
         elementAtIndex.getTerminal();
     for (int j = i + 1; j < size; ) {
       org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal elementAtNext =
           expectedElements.get(j);
       EClass metaClass = elementAtIndex.getContainmentTrace().getStartClass();
       EClass nextMetaClass = elementAtNext.getContainmentTrace().getStartClass();
       org.emftext.language.xpath3.resource.xpath3.IXpath3ExpectedElement nextTerminal =
           elementAtNext.getTerminal();
       // Terminals that have a different root meta class in the containment trace must
       // be kept because they can the decision whether an expected terminals is valid or
       // not depends on the root of the containment trace.
       boolean differentMetaclass = metaClass != nextMetaClass;
       if (terminal.equals(nextTerminal) && !differentMetaclass) {
         expectedElements.remove(j);
         size--;
       } else {
         j++;
       }
     }
   }
 }
 /** Removes all proposals for keywords that end before the given index. */
 protected void removeKeywordsEndingBeforeIndex(
     Collection<org.emftext.language.xpath3.resource.xpath3.ui.Xpath3CompletionProposal> proposals,
     int index) {
   List<org.emftext.language.xpath3.resource.xpath3.ui.Xpath3CompletionProposal> toRemove =
       new ArrayList<org.emftext.language.xpath3.resource.xpath3.ui.Xpath3CompletionProposal>();
   for (org.emftext.language.xpath3.resource.xpath3.ui.Xpath3CompletionProposal proposal :
       proposals) {
     org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal expectedTerminal =
         proposal.getExpectedTerminal();
     org.emftext.language.xpath3.resource.xpath3.IXpath3ExpectedElement terminal =
         expectedTerminal.getTerminal();
     if (terminal
         instanceof org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedCsString) {
       org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedCsString csString =
           (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedCsString) terminal;
       int startExcludingHiddenTokens = expectedTerminal.getStartExcludingHiddenTokens();
       if (startExcludingHiddenTokens + csString.getValue().length() - 1 < index) {
         toRemove.add(proposal);
       }
     }
   }
   proposals.removeAll(toRemove);
 }
 /**
  * Calculates the end index of the expected element at allExpectedElements[index]. To determine
  * the end, the subsequent expected elements from the array of all expected elements are used. An
  * element is considered to end one character before the next elements starts.
  */
 protected int getEnd(
     org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal[] allExpectedElements,
     int indexInList) {
   org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal elementAtIndex =
       allExpectedElements[indexInList];
   int startIncludingHidden = elementAtIndex.getStartIncludingHiddenTokens();
   int startExcludingHidden = elementAtIndex.getStartExcludingHiddenTokens();
   for (int i = indexInList + 1; i < allExpectedElements.length; i++) {
     org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal elementAtI =
         allExpectedElements[i];
     int startIncludingHiddenForI = elementAtI.getStartIncludingHiddenTokens();
     int startExcludingHiddenForI = elementAtI.getStartExcludingHiddenTokens();
     if (startIncludingHidden != startIncludingHiddenForI
         || startExcludingHidden != startExcludingHiddenForI) {
       return startIncludingHiddenForI - 1;
     }
   }
   return Integer.MAX_VALUE;
 }
  protected EObject findCorrectContainer(
      org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal expectedTerminal) {
    EObject container = expectedTerminal.getContainer();
    EClass ruleMetaclass = expectedTerminal.getTerminal().getRuleMetaclass();
    if (ruleMetaclass.isInstance(container)) {
      // container is correct for expected terminal
      return container;
    }
    // the container is wrong
    EObject parent = null;
    EObject previousParent = null;
    EObject correctContainer = null;
    EObject hookableParent = null;
    org.emftext.language.xpath3.resource.xpath3.grammar.Xpath3ContainmentTrace containmentTrace =
        expectedTerminal.getContainmentTrace();
    EClass startClass = containmentTrace.getStartClass();
    org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ContainedFeature currentLink = null;
    org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ContainedFeature previousLink = null;
    org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ContainedFeature[] containedFeatures =
        containmentTrace.getPath();
    for (int i = 0; i < containedFeatures.length; i++) {
      currentLink = containedFeatures[i];
      if (i > 0) {
        previousLink = containedFeatures[i - 1];
      }
      EClass containerClass = currentLink.getContainerClass();
      hookableParent = findHookParent(container, startClass, currentLink, parent);
      if (hookableParent != null) {
        // we found the correct parent
        break;
      } else {
        previousParent = parent;
        parent = containerClass.getEPackage().getEFactoryInstance().create(containerClass);
        if (parent != null) {
          if (previousParent == null) {
            // replace container for expectedTerminal with correctContainer
            correctContainer = parent;
          } else {
            // This assignment is only performed to get rid of a warning about a potential
            // null pointer access. Variable 'previousLink' cannot be null here, because it is
            // initialized for all loop iterations where 'i' is greather than 0 and for the
            // case where 'i' equals zero, this path is never executed, because
            // 'previousParent' is null in this case.
            org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ContainedFeature link =
                previousLink;
            org.emftext.language.xpath3.resource.xpath3.util.Xpath3EObjectUtil.setFeature(
                parent, link.getFeature(), previousParent, false);
          }
        }
      }
    }

    if (correctContainer == null) {
      correctContainer = container;
    }

    if (currentLink == null) {
      return correctContainer;
    }

    hookableParent = findHookParent(container, startClass, currentLink, parent);

    final EObject finalHookableParent = hookableParent;
    final EStructuralFeature finalFeature = currentLink.getFeature();
    final EObject finalParent = parent;
    if (parent != null && hookableParent != null) {
      expectedTerminal.setAttachmentCode(
          new Runnable() {

            public void run() {
              org.emftext.language.xpath3.resource.xpath3.util.Xpath3EObjectUtil.setFeature(
                  finalHookableParent, finalFeature, finalParent, false);
            }
          });
    }
    return correctContainer;
  }
  protected Collection<org.emftext.language.xpath3.resource.xpath3.ui.Xpath3CompletionProposal>
      deriveProposals(
          final org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal
              expectedTerminal,
          String content,
          org.emftext.language.xpath3.resource.xpath3.IXpath3TextResource resource,
          int cursorOffset) {
    org.emftext.language.xpath3.resource.xpath3.IXpath3ExpectedElement expectedElement =
        (org.emftext.language.xpath3.resource.xpath3.IXpath3ExpectedElement)
            expectedTerminal.getTerminal();
    if (expectedElement
        instanceof org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedCsString) {
      org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedCsString csString =
          (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedCsString) expectedElement;
      return handleKeyword(
          expectedTerminal,
          csString,
          expectedTerminal.getPrefix(),
          expectedTerminal.getContainer());
    } else if (expectedElement
        instanceof org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedBooleanTerminal) {
      org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedBooleanTerminal
          expectedBooleanTerminal =
              (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedBooleanTerminal)
                  expectedElement;
      return handleBooleanTerminal(
          expectedTerminal, expectedBooleanTerminal, expectedTerminal.getPrefix());
    } else if (expectedElement
        instanceof
        org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedEnumerationTerminal) {
      org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedEnumerationTerminal
          expectedEnumerationTerminal =
              (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedEnumerationTerminal)
                  expectedElement;
      return handleEnumerationTerminal(
          expectedTerminal, expectedEnumerationTerminal, expectedTerminal.getPrefix());
    } else if (expectedElement
        instanceof
        org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedStructuralFeature) {
      final org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedStructuralFeature
          expectedFeature =
              (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedStructuralFeature)
                  expectedElement;
      final EStructuralFeature feature = expectedFeature.getFeature();
      final EClassifier featureType = feature.getEType();
      final EObject container = findCorrectContainer(expectedTerminal);

      // Here it gets really crazy. We need to modify the model in a way that reflects
      // the state the model would be in, if the expected terminal were present. After
      // computing the corresponding completion proposals, the original state of the
      // model is restored. This procedure is required, because different models can be
      // required for different completion situations. This can be particularly observed
      // when the user has not yet typed a character that starts an element to be
      // completed.
      final Collection<org.emftext.language.xpath3.resource.xpath3.ui.Xpath3CompletionProposal>
          proposals =
              new ArrayList<
                  org.emftext.language.xpath3.resource.xpath3.ui.Xpath3CompletionProposal>();
      expectedTerminal.materialize(
          new Runnable() {

            public void run() {
              if (feature instanceof EReference) {
                EReference reference = (EReference) feature;
                if (featureType instanceof EClass) {
                  if (reference.isContainment()) {
                    // the FOLLOW set should contain only non-containment references
                    assert false;
                  } else {
                    proposals.addAll(
                        handleNCReference(
                            expectedTerminal,
                            container,
                            reference,
                            expectedTerminal.getPrefix(),
                            expectedFeature.getTokenName()));
                  }
                }
              } else if (feature instanceof EAttribute) {
                EAttribute attribute = (EAttribute) feature;
                if (featureType instanceof EEnum) {
                  EEnum enumType = (EEnum) featureType;
                  proposals.addAll(
                      handleEnumAttribute(
                          expectedTerminal,
                          expectedFeature,
                          enumType,
                          expectedTerminal.getPrefix(),
                          container));
                } else {
                  // handle EAttributes (derive default value depending on the type of the
                  // attribute, figure out token resolver, and call deResolve())
                  proposals.addAll(
                      handleAttribute(
                          expectedTerminal,
                          expectedFeature,
                          container,
                          attribute,
                          expectedTerminal.getPrefix()));
                }
              } else {
                // there should be no other subclass of EStructuralFeature
                assert false;
              }
            }
          });
      // Return the proposals that were computed in the closure call.
      return proposals;
    } else {
      // there should be no other class implementing IExpectedElement
      assert false;
    }
    return Collections.emptyList();
  }
  protected void removeInvalidEntriesAtEnd(
      List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>
          expectedElements) {
    org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3FollowSetGroupList followSetGroupList =
        new org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3FollowSetGroupList(
            expectedElements);
    List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3FollowSetGroup> followSetGroups =
        followSetGroupList.getFollowSetGroups();
    int lastStartExcludingHiddenTokens = -1;
    for (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3FollowSetGroup followSetGroup :
        followSetGroups) {
      boolean sameStartExcludingHiddenTokens =
          followSetGroup.hasSameStartExcludingHiddenTokens(lastStartExcludingHiddenTokens);
      lastStartExcludingHiddenTokens = followSetGroup.getStartExcludingHiddenTokens();
      EObject container = followSetGroup.getContainer();
      EClass currentRule = null;
      if (container != null) {
        currentRule = container.eClass();
      }
      List<org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal>
          expectedTerminals = followSetGroup.getExpectedTerminals();
      for (org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal
          expectedTerminal : expectedTerminals) {
        org.emftext.language.xpath3.resource.xpath3.IXpath3ExpectedElement terminalAtIndex =
            expectedTerminal.getTerminal();
        EClass ruleMetaclass = terminalAtIndex.getRuleMetaclass();
        boolean differentRule = currentRule != ruleMetaclass;
        // If the two expected elements have a different parent in the syntax definition,
        // we must not discard the second element, because it probably stems from a parent
        // rule.
        org.emftext.language.xpath3.resource.xpath3.grammar.Xpath3ContainmentTrace
            containmentTrace = expectedTerminal.getContainmentTrace();
        boolean fitsAtCurrentPosition = fitsAtCurrentPosition(container, containmentTrace);
        boolean inContainmentTrace =
            pathToRootContains(container, expectedTerminal.getTerminal().getRuleMetaclass());
        boolean keepElement = true;
        if (differentRule && !inContainmentTrace) {
          if (!fitsAtCurrentPosition) {
            keepElement = false;
          }
        }
        if (sameStartExcludingHiddenTokens) {
          keepElement = false;
        }

        if (keepElement) {
        } else {
          // We must not call expectedElements.remove(expectedTerminal) because the
          // hashCode() method of ExpectedTerminal does not consider the start positions and
          // remove the wrong elements.
          for (int i = 0; i < expectedElements.size(); i++) {
            org.emftext.language.xpath3.resource.xpath3.mopp.Xpath3ExpectedTerminal next =
                expectedElements.get(i);
            if (next == expectedTerminal) {
              expectedElements.remove(i);
              break;
            }
          }
        }
      }
    }
  }