package com.sap.ide.cts.parser.incremental; import static com.sap.furcas.runtime.textblocks.modifcation.TbChangeUtil.addToBlockAt; import static com.sap.furcas.runtime.textblocks.modifcation.TbVersionUtil.getOtherVersion; import static com.sap.ide.cts.parser.incremental.IncrementalParsingUtil.checkIsDefinedOptional; import static com.sap.ide.cts.parser.incremental.IncrementalParsingUtil.deleteCorrespondingModelElements; import static com.sap.ide.cts.parser.incremental.IncrementalParsingUtil.getOriginalVersion; import java.util.ArrayList; import java.util.Collection; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.Lexer; import org.antlr.runtime.Token; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.ETypedElement; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; import com.sap.furcas.metamodel.FURCAS.TCS.Alternative; import com.sap.furcas.metamodel.FURCAS.TCS.AndExp; import com.sap.furcas.metamodel.FURCAS.TCS.AtomExp; import com.sap.furcas.metamodel.FURCAS.TCS.ConditionalElement; import com.sap.furcas.metamodel.FURCAS.TCS.LiteralRef; import com.sap.furcas.metamodel.FURCAS.TCS.Property; import com.sap.furcas.metamodel.FURCAS.TCS.SequenceElement; import com.sap.furcas.metamodel.FURCAS.TCS.Template; import com.sap.furcas.metamodel.FURCAS.textblocks.AbstractToken; import com.sap.furcas.metamodel.FURCAS.textblocks.Bostoken; import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode; import com.sap.furcas.metamodel.FURCAS.textblocks.LexedToken; import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock; import com.sap.furcas.metamodel.FURCAS.textblocks.Version; import com.sap.furcas.runtime.common.exceptions.DeferredModelElementCreationException; import com.sap.furcas.runtime.common.interfaces.IModelElementInvestigator; import com.sap.furcas.runtime.common.interfaces.IModelElementProxy; import com.sap.furcas.runtime.common.util.EcoreHelper; import com.sap.furcas.runtime.parser.ANTLR3LocationToken; import com.sap.furcas.runtime.parser.impl.ModelElementProxy; import com.sap.furcas.runtime.parser.textblocks.TextBlockFactory; import com.sap.furcas.runtime.parser.textblocks.observer.TextBlockProxy; import com.sap.furcas.runtime.parser.textblocks.observer.TokenRelocationUtil; import com.sap.furcas.runtime.tcs.TcsUtil; import com.sap.furcas.runtime.textblocks.TbNavigationUtil; import com.sap.furcas.runtime.textblocks.TbUtil; import com.sap.furcas.runtime.textblocks.modifcation.TbVersionUtil; import com.sap.furcas.runtime.textblocks.shortprettyprint.ShortPrettyPrinter; /** * @see TextBlockReuseStrategy * @author C5106462 * */ public class TextBlockReuseStrategyImpl implements TextBlockReuseStrategy { private TextBlockFactory tbFactory; private ReferenceHandler referenceHandler; private final Lexer lexer; private ShortPrettyPrinter shortPrettyPrinter; private final Collection<TextBlock> changedBlocks = new ArrayList<TextBlock>(); public TextBlockReuseStrategyImpl(Lexer lexer, IModelElementInvestigator mi) { setModelElementInvestigator(mi); this.lexer = lexer; } public void setModelElementInvestigator(IModelElementInvestigator mi) { this.shortPrettyPrinter = new ShortPrettyPrinter(mi); } @Override public void setTextBlockFactory(TextBlockFactory factory) { tbFactory = factory; ((ReuseAwareTextBlockFactoryImpl) tbFactory).setTextBlocksReuseStrategy(this); ((ReuseAwareTextBlockFactoryImpl) tbFactory).setReferenceHandler(referenceHandler); } @Override public void setReferenceHandler(ReferenceHandler handler) { this.referenceHandler = handler; } @Override public TbBean reuseTextBlock(TextBlock oldVersion, TextBlockProxy newVersion) throws DeferredModelElementCreationException { return reusetextBlockInteral(TbVersionUtil.getOtherVersion(oldVersion, Version.CURRENT), newVersion); } private TbBean reusetextBlockInteral(TextBlock oldVersion, TextBlockProxy newVersion) throws DeferredModelElementCreationException { // now check if textblock was changed if (TcsUtil.isReferenceOnly(newVersion.getTemplate())) { return handleReferenceOnlyTemplate(oldVersion, newVersion); } else if (isTBEqual(oldVersion, newVersion)) { handleAlternativeChoices(oldVersion, newVersion); int endIndex = 0; if (TbNavigationUtil.isUltraRoot(oldVersion) && !(newVersion.getSubNodes().get(0) instanceof Bostoken)) { // need to add to index because ultra root has additinal // BOS token which might not be present in newVersion endIndex++; } // TODO check if this can be removed: referenceHandler.reEvaluatePropertyInits(oldVersion, newVersion); boolean valueChanged = false; for (Object subNode : newVersion.getSubNodes()) { if (subNode instanceof TextBlockProxy) { // check for subBlock if it can be re-used TextBlock original = getOriginalVersion((TextBlockProxy) subNode, oldVersion); TbBean subNodeResult = reusetextBlockInteral(original, (TextBlockProxy) subNode); if (subNodeResult.isNew) { addToBlockAt(oldVersion, endIndex, subNodeResult.textBlock); // if the sub result is new then we have to add the // corresponding model elements // to the appropriate feature of the original // corresponding model element SetNewFeatureBean newFeatureBean = IncrementalParsingUtil.setFeatureWithNewValue( (TextBlockProxy) subNode, oldVersion, getOppositeEndFinder()); if (subNodeResult.reuseType.equals(ReuseType.DELETE)) { // Delete all blocks that are empty now Collection<EObject> affectedModelElements = IncrementalParsingUtil .deleteEmptyBlocksIncludingAdjecentBlocks(original); // delete all affected modelelements for (EObject refObject : affectedModelElements) { if (EcoreHelper.isAlive(refObject) && !isFromReferenceOnlyTemplate(subNodeResult.textBlock)) { EcoreUtil.delete(refObject, true); } } // this block // is under our control and therefore need // to be // deleted in this case // otherwise the client has to be // responsible for // dealing with these elements if (EcoreHelper.isAlive((original)) && TbNavigationUtil.getSubNodesSize(original) == 0) { // delete only if original subblock // is empty now deleteCorrespondingModelElements(newFeatureBean.parentRefObject, newFeatureBean.property, original, referenceHandler); } } // orig if (newFeatureBean != null) { // it might be null in the case of operator // templates? referenceHandler.setNewFeature(newFeatureBean, !TcsUtil.isReferenceOnly(subNodeResult.textBlock.getType())); } } } else if (subNode instanceof AbstractToken) { // if it is an additional token it has to be added to the // reused textblock AbstractToken token = (AbstractToken) subNode; boolean wasReUsed = wasReUsed(token); valueChanged = valueChanged(token); if (!wasReUsed || valueChanged) { referenceHandler.registerTokenForReferenceResolving(token); referenceHandler.setNewPrimitiveFeature(newVersion, oldVersion, token); } if (!oldVersion.equals((token).getParent())) { // parent needs to be changed so move it to the new // block TokenRelocationUtil.relocateToken(token, endIndex, oldVersion); } } endIndex++; } IncrementalParsingUtil.deleteEmptyBlocksIncludingAdjecentBlocks(oldVersion); deleteElementsForRemovedSubBlocks(oldVersion); // ensure offset and length are correctly computed // for // the new node // TokenRelocationUtil // .makeRelativeOffsetRecursively(oldVersion); // TbValidationUtil // .assertTextBlockConsistency(oldVersion); for (DocumentNode node : TbNavigationUtil.getSubNodes(getOtherVersion(oldVersion, Version.REFERENCE))) { if (node instanceof LexedToken && getOtherVersion(node, Version.PREVIOUS) == null && (checkIsDefinedOptional((AbstractToken) node) || checkIsInAlternative((AbstractToken) node))) { // if the token was deleted a primitive value depending on // this value needs to be updated referenceHandler.setNewPrimitiveFeature(newVersion, oldVersion, (AbstractToken) node); } } if (oldVersion.getParent() != null) { // delete any non existent siblings IncrementalParsingUtil.deleteEmptyBlocksIncludingAdjecentBlocks(oldVersion.getParent()); deleteElementsForRemovedSubBlocks(oldVersion.getParent()); } IncrementalParsingUtil.deleteEmptyBlocksIncludingAdjecentBlocks(oldVersion); deleteElementsForRemovedSubBlocks(oldVersion); resolveProxies(oldVersion, newVersion); handleContextElements(oldVersion, newVersion); return new TbBean(oldVersion, false, ReuseType.COMPLETE); } // return new TbBean(tbCreator.createNewTextBlock(newVersion), true, // getReuseType(oldVersion, newVersion)); TextBlock tb = tbFactory.createNewTextBlock(newVersion, oldVersion.getParent()); changedBlocks.add(tb); if (!TbUtil.isEmpty(oldVersion)) { // old version still there to this was an insert case return new TbBean(tb, true, ReuseType.INSERT); } else { // old version is empty now so this was a delete case return new TbBean(tb, true, ReuseType.DELETE); } } private OppositeEndFinder getOppositeEndFinder() { // TODO Auto-generated method stub return null; } /** * Synchronizes the elements that reside in the #context of the {@link TextBlock}s. * * @param oldVersion * @param newVersion */ private void handleContextElements(TextBlock oldVersion, TextBlockProxy newVersion) { // clear context oldVersion.getElementsInContext().clear(); // add all elements that exists in the new version for (Object element : newVersion.getContextElements()) { if (element instanceof IModelElementProxy) { element = ((IModelElementProxy) element).getRealObject(); } if (element instanceof EObject) { oldVersion.getElementsInContext().add((EObject) element); } } } private void handleAlternativeChoices(TextBlock oldVersion, TextBlockProxy newVersion) { // TODO: is this right in all cases? // Assumption: if the TB's are equal, there should be no problem to assume // that also the alternatives information can be re-used. oldVersion.getParentAltChoices().clear(); oldVersion.getParentAltChoices().addAll(newVersion.getAlternativeChoices()); } private TbBean handleReferenceOnlyTemplate(TextBlock oldVersion, TextBlockProxy newVersion) throws DeferredModelElementCreationException { if (oldVersion.getParent() != null) { for (EObject ro : new ArrayList<EObject>(oldVersion.getParent().getCorrespondingModelElements())) { for (EObject value : new ArrayList<EObject>(oldVersion.getCorrespondingModelElements())) { try { SetNewFeatureBean bean = new SetNewFeatureBean(ro, ((Property) oldVersion.getSequenceElement()) .getPropertyReference().getStrucfeature().getName(), value, 0); referenceHandler.unsetFeature(bean); oldVersion.getCorrespondingModelElements().remove(ro); } catch (Exception e) { continue; } } } } TextBlock tb = tbFactory.createNewTextBlock(newVersion, oldVersion.getParent()); changedBlocks.add(tb); if (!TbUtil.isEmpty(oldVersion)) { // old version still there to this was an insert case return new TbBean(tb, true, ReuseType.INSERT); } else { // old version is empty now so this was a delete case return new TbBean(tb, true, ReuseType.DELETE); } } private boolean isFromReferenceOnlyTemplate(TextBlock textBlock) { if (textBlock.getType() != null && textBlock.getType() != null) { Template parseRule = textBlock.getType(); return TcsUtil.isReferenceOnly(parseRule); } return false; } private boolean valueChanged(AbstractToken token) { boolean valueChanged; AbstractToken referenceVersion = TbVersionUtil.getOtherVersion(token, Version.REFERENCE); valueChanged = referenceVersion == null || !getSynchronizedValue(referenceVersion).equals(getSynchronizedValue(token)); return valueChanged; } /** * Checks whether the given model element token could be re-used. * * @param nextToken * @return */ @Override public boolean canBeReUsed(AbstractToken candidate, Object lexerToken) { if (!Version.PREVIOUS.equals(candidate.getVersion())) { throw new IllegalArgumentException("Candidate token has to be in PREVIOUS Version but was: " + candidate.getVersion()); } Token nextToken = (Token) lexerToken; boolean typeEquals = nextToken.getType() == candidate.getType(); if (!typeEquals) { // if the type is not equal the token may still be part of // an isDefined Alternative which qualifies for reuse typeEquals = checkIsDefinedAlternative(candidate, nextToken); } if (!typeEquals) { return false; // basic check failed. no need to try the other checks. } // AbstractToken reference = TbVersionUtil.getOtherVersion(candidate, // Version.REFERENCE); boolean contentEquals = compareContentForReuse(candidate, lexerToken);// nextToken.getText().equals(reference.getValue()); boolean stateEquals = true; // TODO antlr has no explicit states, so // what to do here? boolean multipleReusePossibilityCheck = true; String candidateValue = getSynchronizedValue(candidate); if (candidateValue.indexOf(nextToken.getText()) != candidateValue.lastIndexOf(nextToken.getText())) { // ok now we have a problem. The value that could be reused occurs // more than once within the changed PREVIOUS token. // TODO Which one are we going to re-use? // Current heuristic: // Reuse the last possible one multipleReusePossibilityCheck = ((ANTLR3LocationToken) nextToken).getStartIndex() == candidateValue .lastIndexOf(nextToken.getText()); } return typeEquals && contentEquals && stateEquals && multipleReusePossibilityCheck; } /** * Checks the case if an alternative from then-->else (or vice-versa) was done for the given <tt>candidate</tt>. * * E.g.: ...(isDefined(output) ? output {{ ownedTypeDefinitions = lookIn('output') }} :"void") ... Change from "String" (1st * alternative) to "void" 2nd alternative. * * Will result in true if the <tt>nextToken</tt> is a valid alternative to the given <tt>candidate</tt>. * * @param candidate * @param nextToken * @return */ private boolean checkIsDefinedAlternative(AbstractToken candidate, Token nextToken) { if (candidate instanceof LexedToken) { LexedToken lexedToken = (LexedToken) candidate; SequenceElement se = lexedToken.getSequenceElement(); if (se != null) { // now check if it is contained within a isDefined clause // TODO maybe this has to be done recursively ascending? SequenceElement parent = TcsUtil.getContainerSequenceElement(se); if (parent != null) { if (parent instanceof ConditionalElement) { ConditionalElement conditional = (ConditionalElement) parent; SequenceElement otherAlternative = null; if (conditional.getThenSequence().getElements().contains(se)) { if (conditional.getElseSequence() != null && conditional.getElseSequence().getElements().size() > 0) { otherAlternative = conditional.getElseSequence().getElements().iterator().next(); } } else { otherAlternative = conditional.getThenSequence().getElements().iterator().next(); } if (otherAlternative instanceof LiteralRef) { LiteralRef literalRef = (LiteralRef) otherAlternative; Token literalToken = createLexerToken(literalRef.getReferredLiteral().getValue()); return literalToken.getType() == nextToken.getType(); } else if (otherAlternative instanceof Property) { if (conditional.getCondition() instanceof AndExp) { AtomExp first = ((AndExp) conditional.getCondition()).getExpressions().iterator().next(); // Reuse the token if it was used as // within an // isDefined alternative where the // argument // of isDefined is the same the // referred // property. return ((Property) otherAlternative).getPropertyReference().getStrucfeature() .equals(first.getPropertyReference().getStrucfeature()); } } } } } } return false; } /** * TODO this is only an initial version and needs to be improved currently a tb is considered to be re-used if all tokens were * in the same tb before */ private boolean isTBEqual(TextBlock oldVersion, TextBlockProxy newVersion) { if (oldVersion == null || !newVersion.getTemplate().equals(oldVersion.getType())) { return false; // templates have to be the same same } boolean canBeReused = true; boolean containsTokens = false; ArrayList<AbstractToken> newTokens = new ArrayList<AbstractToken>(); for (Object subNode : newVersion.getSubNodes()) { if (subNode instanceof LexedToken) { containsTokens = true; // IF there is a token that was not there before if (TbVersionUtil.getOtherVersion((LexedToken) subNode, Version.REFERENCE) == null) { // if the token is not an optional token the textblock changed newTokens.add(((LexedToken) subNode)); canBeReused = canBeReused && ( checkIsDefinedOptional((LexedToken) subNode) || checkIsInCollectionFeature((LexedToken) subNode) || isSeparator((LexedToken) subNode) || checkIsInAlternative((LexedToken) subNode)); } } } ArrayList<AbstractToken> oldTokens = new ArrayList<AbstractToken>(); if (getOtherVersion(oldVersion, Version.REFERENCE) != null) { for (Object subNode : getOtherVersion(oldVersion, Version.REFERENCE).getSubNodes()) { if (subNode instanceof LexedToken) { // IF there is a token that was there before if (TbVersionUtil.getOtherVersion((AbstractToken) subNode, Version.CURRENT) == null) { // if the token is not an optional token the textblock changed oldTokens.add(((LexedToken) subNode)); canBeReused = canBeReused && ( checkIsDefinedOptional((AbstractToken) subNode) || checkIsInCollectionFeature((AbstractToken) subNode) || isSeparator((AbstractToken) subNode) || checkIsInAlternative((AbstractToken) subNode)); } } } } if (!containsTokens) { // The TB does not have any tokens itself, so make // its reuse dependent on its inner TBs. canBeReused = false; for (Object subNode : newVersion.getSubNodes()) { if (subNode instanceof TextBlockProxy) { TextBlock original = getOriginalVersion((TextBlockProxy) subNode, oldVersion); canBeReused = canBeReused || isTBEqual(original, (TextBlockProxy) subNode); } } } if (newTokens.size() == getTokenSize(newVersion) && getTokenSize(oldVersion) > oldTokens.size()) { // there are ONLY new tokens in the new block AND // there are still tokens in the oldVersion block // so oldVersion block will be reused later and a // newVersion block should be considered as new return false; } if (canBeReused) { return true; } else if (newTokens.size() == oldTokens.size()) { // check if the overlapping tokens correspond to each other. for (int i=0; i<newTokens.size(); i++) { AbstractToken oTok = oldTokens.get(i); AbstractToken nTok = newTokens.get(i); if (!oTok.getSequenceElement().equals(nTok.getSequenceElement()) && !oTok.getValue().equals(nTok.getValue())) { return false; } } return true; } return false; } private int getTokenSize(TextBlockProxy newVersion) { int size = 0; for (Object subNode : newVersion.getSubNodes()) { if (subNode instanceof LexedToken) { size++; } } return size; } private int getTokenSize(TextBlock tb) { int size = 0; for (Object subNode : tb.getSubNodes()) { if (subNode instanceof LexedToken) { size++; } } return size; } /** * Separators never contribute to equality. So this will return true if the given token was created from a separator sequence * element. * * @param lexedToken * @return */ private boolean isSeparator(AbstractToken candidate) { if (candidate instanceof LexedToken) { LexedToken lexedToken = (LexedToken) candidate; SequenceElement se = lexedToken.getSequenceElement(); if (se != null && se instanceof LiteralRef) { return se.getElementSequence().getSeparatorContainer() != null; } } return false; } private boolean checkIsInAlternative(AbstractToken candidate) { if (candidate instanceof LexedToken) { LexedToken lexedToken = (LexedToken) candidate; SequenceElement se = lexedToken.getSequenceElement(); if (se != null) { // now check if it is contained within a isDefined clause // TODO maybe this has to be done recursively ascending? SequenceElement parent = TcsUtil.getContainerSequenceElement(se); if (parent != null) { // TODO check if original element waas in other alternative if (parent instanceof Alternative) { return true; } else if (parent instanceof ConditionalElement) { SequenceElement parentParent = TcsUtil.getContainerSequenceElement(parent); if (parentParent instanceof Alternative) { return true; } } } } return false; } return false; } /** * Checks whether the given token refers to a sequence element which is a {@link Property} and that refers to a 0..1, 0..n or * 1..n property. That indicates that the token may represent an additional element in this list. * * @param candidate * @return */ private boolean checkIsInCollectionFeature(AbstractToken candidate) { if (candidate instanceof LexedToken) { LexedToken lexedToken = (LexedToken) candidate; SequenceElement se = lexedToken.getSequenceElement(); if (se != null) { if (se instanceof Property && ((Property) se).getPropertyReference() != null && ((Property) se).getPropertyReference().getStrucfeature() != null) { ETypedElement te = ((Property) se).getPropertyReference().getStrucfeature(); if (te instanceof EReference) { return ((EReference) te).getLowerBound() == 0 || ((EReference) te).getUpperBound() > 1; } else if (te instanceof EStructuralFeature) { return ((EStructuralFeature) te).getLowerBound() == 0 || ((EStructuralFeature) te).getUpperBound() > 1; } } } } return false; } private boolean compareContentForReuse(AbstractToken candidate, Object lexerToken) { // TODO Improve Heuristic: If the old text is still contained within the // token and the current lexerToken has a different value // consider it not reuseable as this meant an insert before and a later // lexerToken may come that matches this part better. // However, this might not always work. Find those cases and add // additional heuristics for them AbstractToken reference = TbVersionUtil.getOtherVersion(candidate, Version.REFERENCE); String candidateValue = getSynchronizedValue(candidate); String referenceValue = getSynchronizedValue(reference); if (candidateValue.contains(((Token) lexerToken).getText())) { if (reference == null) { // no reference version so it can always be reused return true; } if (referenceValue.equals(((Token) lexerToken).getText())) { // reference value and new value correspond so re-use return true; } Token nextToken = createLexerToken(candidateValue); while (!nextToken.equals(Token.EOF_TOKEN)) { // subtokens.add(nextToken); if (nextToken.getText().equals(referenceValue)) { // reference value still contained in token so wait until we // get there return false; } nextToken = lexer.nextToken(); } // reference value not contained in token so this was a rename return true; } else { // this is the case when the whole token value was replaced by a // different value return true; } } private String getSynchronizedValue(AbstractToken candidate) { if (candidate == null) { return null; } String candidateValue = candidate.getValue(); if (candidateValue == null) { candidateValue = shortPrettyPrinter.resynchronizeToEditableState(candidate); } return candidateValue; } private Token createLexerToken(String value) { lexer.reset(); lexer.setCharStream(new ANTLRStringStream(value)); // Collection<Token> subtokens = new ArrayList<Token>(); Token nextToken = lexer.nextToken(); return nextToken; } /** * Assuming that the oldVersion will be reused and therefore all elements within * {@link DocumentNode#getCorrespondingModelElements()} have their correspondents within * {@link TextBlockProxy#getCorrespondingModelElementProxies()} of the new version. Therefore the proxies will be set resolved * with the elements from the old version. * * The same applies for the {@link TextBlock#getReferencedModelElements() * * @link TextBlockProxy#getReferencedElements()()} * * @param oldVersion * @param newVersion */ private void resolveProxies(TextBlock oldVersion, TextBlockProxy newVersion) { int i = 0; for (EObject ro : oldVersion.getCorrespondingModelElements()) { if (newVersion.getCorrespondingModelElementProxies().size() >= i + 1) { ModelElementProxy proxy = (ModelElementProxy) newVersion.getCorrespondingModelElementProxies().get(i); proxy.setRealObject(ro); } else { break; } i++; } i = 0; } /** * Delete if there is no current version and the template was not reference only. * * @param oldVersion */ private void deleteElementsForRemovedSubBlocks(TextBlock oldVersion) { TextBlock reference = TbVersionUtil.getOtherVersion(oldVersion, Version.REFERENCE); if (!EcoreHelper.isAlive(oldVersion) || reference == null) { return; } for (DocumentNode node : reference.getSubNodes()) { if (TbVersionUtil.getOtherVersion(node, Version.CURRENT) != null) { continue; // only delete if there is no current version } if (node instanceof TextBlock) { TextBlock tb = (TextBlock) node; deleteElementsForRemovedSubBlocks(tb); if (tb.getType() != null && tb.getType() != null && !TcsUtil.isReferenceOnly(tb.getType())) { for (EObject ro : new ArrayList<EObject>(tb.getCorrespondingModelElements())) { if (EcoreHelper.isAlive((ro))) { EcoreUtil.delete(ro, true); } } } } else if (node instanceof LexedToken && node.getSequenceElement() instanceof Property ) { Property prop = (Property) node.getSequenceElement(); boolean unused = true; for (EObject modelElement : reference.getCorrespondingModelElements()) { if (TbUtil.findTokensFor(modelElement, prop, Version.CURRENT).size() > 0) { unused = false; } } if (unused) { referenceHandler.unsetPrimitiveFeature(oldVersion, (LexedToken) node); } } } } /** * Cheks whether the token was reused or not. * * @param subNode * @param oldVersion * @return */ private boolean wasReUsed(AbstractToken subNode) { AbstractToken refVersion = TbVersionUtil.getOtherVersion(subNode, Version.REFERENCE); if (refVersion != null) { return true; } else { return false; } } @Override public void notifyTokenReuse(AbstractToken token) { if (!wasReUsed(token) || valueChanged(token)) { // register for references that may target the token this.referenceHandler.registerTokenForReferenceResolving(token); } } @Override public void clearChangedBlocksList() { changedBlocks.clear(); } @Override public Collection<TextBlock> getChangedBlocks() { return changedBlocks; } }