/******************************************************************************* * Copyright (c) 2001, 2011 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation * Jens Lukowski/Innoopract - initial renaming/restructuring * David Carver (Intalio) - bug 300427 - Comparison of String Objects using == or != *******************************************************************************/ package org.eclipse.wst.sse.core.internal.text; import java.util.Iterator; import java.util.List; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.IRegion; import org.eclipse.wst.sse.core.internal.Logger; import org.eclipse.wst.sse.core.internal.ltk.parser.BlockTagParser; import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent; import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent; import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent; import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent; import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextReParser; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; import org.eclipse.wst.sse.core.internal.util.Debug; import org.eclipse.wst.sse.core.internal.util.Utilities; import org.eclipse.wst.sse.core.utils.StringUtils; /** * This class provides a centralized place to put "reparsing" logic. This is * the logic that reparses the text incrementally, as a user types in new * characters, or DOM nodes are inserted or deleted. Note: it is not a thread * safe class. */ public class StructuredDocumentReParser implements IStructuredTextReParser { protected IStructuredDocumentRegion dirtyEnd = null; protected IStructuredDocumentRegion dirtyStart = null; final private String doubleQuote = new String(new char[]{'\"'}); protected final CoreNodeList EMPTY_LIST = new CoreNodeList(); protected String fChanges; protected String fDeletedText; protected boolean fIsEntireDocument; private FindReplaceDocumentAdapter fFindReplaceDocumentAdapter = null; protected int fLengthDifference; protected int fLengthToReplace; protected Object fRequester; protected int fStart; // note: this is the impl class of IStructuredDocument, not the interface // FUTURE_TO_DO: I believe some of these can be made private now.? protected BasicStructuredDocument fStructuredDocument; /** * variable used in anticiapation of multithreading */ protected boolean isParsing; final private String singleQuote = new String(new char[]{'\''}); public StructuredDocumentReParser() { super(); } public StructuredDocumentEvent _checkBlockNodeList(List blockTagList) { StructuredDocumentEvent result = null; if (blockTagList != null) { for (int i = 0; i < blockTagList.size(); i++) { org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker blockTag = (org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker) blockTagList.get(i); String tagName = blockTag.getTagName(); final String tagStart = "<" + tagName; //$NON-NLS-1$ result = checkForCriticalName(tagStart); //$NON-NLS-1$ if (result != null) break; result = checkForCriticalName("</" + tagName); //$NON-NLS-1$ if (result != null) break; result = checkForSelfClosing(tagStart); if (result != null) break; result = checkForTransitionToOpen(tagStart); if (result != null) break; } } return result; } /** * Checks if the start region has become self-closing. e.g., <style> -> <style/> */ private StructuredDocumentEvent checkForSelfClosing(String tagName) { StructuredDocumentEvent result = null; if (dirtyStart.getText().toLowerCase().indexOf(tagName.toLowerCase()) >= 0) { // within a start-tag final int documentLength = fStructuredDocument.getLength(); int end = fStart + fLengthToReplace + fChanges.length() + 1; if (end > documentLength) end = documentLength - 1; final String oldText = fStructuredDocument.get(fStart, 1); final String peek = StringUtils.paste(oldText, fChanges, 0, fLengthToReplace); if ("/>".equals(peek)) { // Reparse afterwards if the tag became self-closing result = reparse(dirtyStart.getStart(), documentLength - 1); } } return result; } /** * Checks if the start region has become self-closing. e.g., <style/> -> <style> */ private StructuredDocumentEvent checkForTransitionToOpen(String tagName) { StructuredDocumentEvent result = null; if (dirtyStart.getText().toLowerCase().indexOf(tagName.toLowerCase()) >= 0) { // within a start-tag final int documentLength = fStructuredDocument.getLength(); int end = fStart + fLengthToReplace + fChanges.length() + 1; if (end > documentLength) end = documentLength - 1; final String oldText = fStructuredDocument.get(fStart, 2); final String peek = StringUtils.paste(oldText, fChanges, 0, fLengthToReplace); if ("/>".equals(oldText) && ">".equals(peek)) { // Reparse afterwards if the block tag went from self-closing to open result = reparse(dirtyStart.getStart(), documentLength - 1); } } return result; } /** * Common utility for checking for critical word such as " <SCRIPT>" */ private StructuredDocumentEvent _checkForCriticalWord(String criticalTarget, boolean checkEnd) { StructuredDocumentEvent result = null; int documentLength = fStructuredDocument.getLength(); int propLen = fLengthToReplace; if (propLen > documentLength) propLen = documentLength; int startNeighborhood = fStart - criticalTarget.length(); int adjustInsert = 0; if (startNeighborhood < 0) { adjustInsert = 0 - startNeighborhood; startNeighborhood = 0; } int endNeighborhood = fStart + fLengthToReplace + criticalTarget.length() - 1; if (endNeighborhood > documentLength) endNeighborhood = documentLength - 1; int oldlen = endNeighborhood - startNeighborhood; // + 1; if (oldlen + startNeighborhood > documentLength) { oldlen = documentLength - startNeighborhood; } String oldText = fStructuredDocument.get(startNeighborhood, oldlen); String peek = StringUtils.paste(oldText, fChanges, criticalTarget.length() - adjustInsert, fLengthToReplace); boolean isCriticalString = checkTagNames(oldText, criticalTarget, checkEnd); boolean toBeCriticalString = checkTagNames(peek, criticalTarget, checkEnd); if ((isCriticalString != toBeCriticalString) || // OR if both are // critical and there's // a change in the end // tag ('>') ((isCriticalString && toBeCriticalString) && (changeInIsEndedState(oldText, peek)))) { // if it involves a change of a critical string (making one where // there wasn't, or removing // one where there was one) then reparse everthing. result = reparse(0, documentLength - 1); } return result; } private int _computeStartOfDifferences(CoreNodeList oldNodes, CoreNodeList newNodes) { int startOfDifferences = -1; int newNodesLength = newNodes.getLength(); boolean foundDifference = false; boolean done = false; // we'll control our loop based on the old List length int oldNodesLength = oldNodes.getLength(); // be sure to check 'done' first, so startOfDifferences isn't // icremented if done is true done : while ((!done) && (++startOfDifferences < oldNodesLength)) { IStructuredDocumentRegion oldNode = oldNodes.item(startOfDifferences); // this lessThanEffectedRegion is to check to be sure the node is // infact a candidate // to be considered as "old". This check is important for the case // where some // text is replaceing text that // appears identical, but is a different instance. For example, if // the text // is <P><B></B></P> and <B></B> is inserted at postion 3, // resulting in <P><B></B><B></B></P> // we do not want the // first <B> to be considered old ... it is the new one, the // second // <B> is the old one. if (_lessThanEffectedRegion(oldNode)) { // be sure to check that we have new nodes to compare against. if (startOfDifferences > newNodesLength) { foundDifference = false; done = true; continue done; } else { // IStructuredDocumentRegion newNode = newNodes.item(startOfDifferences); // note: shift is 0 while at beginning of list, before the // insertion (or deletion) point. After that, it is // fStart+fLengthDifference if (!(oldNode.sameAs(newNode, 0))) { foundDifference = true; done = true; continue done; } else { // if they are equal, then we will be keeping the // old one, so // we need to be sure its parentDocument is set back // to // the right instance oldNode.setParentDocument(fStructuredDocument); } } } else { // we didn't literally find a difference, but we count it as // such by implication foundDifference = true; done = true; continue done; } } // if we literally found a difference, then all is ok and we can // return // it. // if we did not literally find one, then we have to decide why. if (!foundDifference) { if (newNodesLength == oldNodesLength) { // then lists are // identical // (and may be of zero // length) startOfDifferences = -1; } else { if (newNodesLength > oldNodesLength) { // then lists are // identical except for // newNodes added startOfDifferences = oldNodesLength; } else { if (newNodesLength < oldNodesLength) { // then lists are // identical except // for old Nodes // deleted startOfDifferences = newNodesLength; } } } } return startOfDifferences; } private int _computeStartOfDifferences(IStructuredDocumentRegion oldNodeParam, ITextRegionList oldRegions, IStructuredDocumentRegion newNodeParam, ITextRegionList newRegions) { int startOfDifferences = -1; int newRegionsLength = newRegions.size(); boolean foundDifference = false; boolean done = false; // we'll control our loop based on the old List length int oldRegionsLength = oldRegions.size(); // be sure to check 'done' first, so startOfDifferences isn't // icremented if done is true done : while ((!done) && (++startOfDifferences < oldRegionsLength)) { ITextRegion oldRegion = oldRegions.get(startOfDifferences); // this lessThanEffectedRegion is to check to be sure the node is // infact a candidate // to be considered as "old". This check is important for the case // where some // text is replaceing text that // appears identical, but is a different instance. For example, if // the text // is <P><B></B></P> and <B></B> is inserted at postion 3, // resulting in <P><B></B><B></B></P> // we do not want the // first <B> to be considered old ... it is the new one, the // second // <B> is the old one. if (_lessThanEffectedRegion(oldNodeParam, oldRegion)) { // be sure to check that we have new nodes to compare against. if (startOfDifferences > newRegionsLength) { foundDifference = false; done = true; continue done; } else { // ITextRegion newRegion = newRegions.get(startOfDifferences); // note: shift is 0 while at beginning of list, before the // insertion (or deletion) point. After that, it is // fStart+fLengthDifference if (!(oldNodeParam.sameAs(oldRegion, newNodeParam, newRegion, 0))) { foundDifference = true; done = true; continue done; } else { // if they are equal, then we will be keeping the old // one. // unlike the flatnode case, there is no reason to // update // the textstore, since its the same text store in // either case // (since its the same flatnode) //oldRegion.setTextStore(fStructuredDocument.parentDocument); } } } else { // we didn't literally find a difference, but we count it as // such by implication foundDifference = true; done = true; continue done; } } // if we literally found a difference, then all is ok and we can // return // it. // if we did not literally find one, then we have to decide why. if (!foundDifference) { if (newRegionsLength == oldRegionsLength) { // then lists are // identical (and may // be of zero length) startOfDifferences = -1; } else { if (newRegionsLength > oldRegionsLength) { // then lists are // identical except // for newRegions // added startOfDifferences = oldRegionsLength; } else { if (newRegionsLength < oldRegionsLength) { // then lists // are identical // except for // old Nodes // deleted startOfDifferences = newRegionsLength; } } } } return startOfDifferences; } /** * Part 1 of 2 steps to do a core_reparse * * Parses a portion of the current text in the IStructuredDocument and * returns the raw result */ private IStructuredDocumentRegion _core_reparse_text(int rescanStart, int rescanEnd) { fStructuredDocument.resetParser(rescanStart, rescanEnd); return fStructuredDocument.getParser().getDocumentRegions(); } /** * Part 2 of 2 steps to do a core_reparse * * Integrates a list of StructuredDocumentRegions based on the current * text contents of the IStructuredDocument into the IStructuredDocument * data structure */ private StructuredDocumentEvent _core_reparse_update_model(IStructuredDocumentRegion newNodesHead, int rescanStart, int rescanEnd, CoreNodeList oldNodes, boolean firstTime) { StructuredDocumentEvent result = null; CoreNodeList newNodes = null; // rescan newNodes = new CoreNodeList(newNodesHead); // adjust our newNode chain so the offset positions match // our text store (not the simple string of text reparsed) StructuredDocumentRegionIterator.adjustStart(newNodesHead, rescanStart); // initialize the parentDocument variable of each instance in the new // chain StructuredDocumentRegionIterator.setParentDocument(newNodesHead, fStructuredDocument); // initialize the structuredDocument variable of each instance in the // new chain //StructuredDocumentRegionIterator.setStructuredDocument(newNodesHead, // fStructuredDocument); // if (firstTime) { fStructuredDocument.setCachedDocumentRegion(newNodesHead); fStructuredDocument.initializeFirstAndLastDocumentRegion(); // note: since we are inserting nodes, for the first time, there // is // no adjustments // to downstream stuff necessary. result = new StructuredDocumentRegionsReplacedEvent(fStructuredDocument, fRequester, oldNodes, newNodes, fChanges, fStart, fLengthToReplace, fIsEntireDocument); } else { // note: integrates changes into model as a side effect result = minimumEvent(oldNodes, newNodes); } result.setDeletedText(fDeletedText); return result; } private CoreNodeList _formMinimumList(CoreNodeList flatnodes, int startOfDifferences, int endOfDifferences) { CoreNodeList minimalNodes = null; // if startOfDifferces is still its initial value, then we have an // empty document if (startOfDifferences == -1) { minimalNodes = EMPTY_LIST; } else { // if we do not have any flatnode in our flatnode list, then // simply // return our standard empty list if (flatnodes.getLength() == 0) { minimalNodes = EMPTY_LIST; } else { // if startOfDifferences is greater than endOfDifferences, // then // that means the calculations "crossed" each other, and // hence, // there really is no differences, so, again, return the empty // list if (startOfDifferences > endOfDifferences) { minimalNodes = EMPTY_LIST; } else { // the last check be sure we have some differnces if ((endOfDifferences > -1)) { minimalNodes = new CoreNodeList(flatnodes.item(startOfDifferences), flatnodes.item(endOfDifferences)); } else { // there were no differences, the list wasn't // minimized, so simply return it. minimalNodes = flatnodes; } } } } return minimalNodes; } private boolean _greaterThanEffectedRegion(IStructuredDocumentRegion oldNode) { boolean result = false; int nodeStart = oldNode.getStartOffset(); int changedRegionEnd = fStart + fLengthToReplace - 1; result = nodeStart > changedRegionEnd; return result; } private boolean _greaterThanEffectedRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion) { boolean result = false; int regionStartOffset = oldNode.getStartOffset(oldRegion); int effectedRegionEnd = fStart + fLengthToReplace - 1; result = regionStartOffset > effectedRegionEnd; return result; } private boolean _lessThanEffectedRegion(IStructuredDocumentRegion oldNode) { boolean result = false; int nodeEnd = oldNode.getEndOffset() - 1; result = nodeEnd < fStart; return result; } private boolean _lessThanEffectedRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion) { boolean result = false; int nodeEnd = oldNode.getEndOffset(oldRegion) - 1; result = nodeEnd < fStart; return result; } private boolean _regionsSameKind(ITextRegion newRegion, ITextRegion oldRegion) { boolean result = false; // if one region is a container region, and the other not, always // return false // else, just check their type. // DW druing refactoring, looks like a "typo" here, using 'old' in // both. // if (isContainerRegion(oldRegion) != isContainerRegion(oldRegion)) if (isCollectionRegion(oldRegion) != isCollectionRegion(newRegion)) result = false; else if (oldRegion.getType().equals(newRegion.getType())) result = true; return result; } // private boolean hasCollectionRegions(ITextRegion aRegion) { // boolean result = false; // if (aRegion instanceof ITextRegionCollection) { // ITextRegionCollection regionContainter = (ITextRegionCollection) // aRegion; // ITextRegionList regions = regionContainter.getRegions(); // Iterator iterator = regions.iterator(); // while (iterator.hasNext()) { // if (aRegion instanceof ITextRegionCollection) { // result = true; // break; // } // } // } // return result; // } /** * This method is specifically to detect changes in 'isEnded' state, * although it still does so with heuristics. If number of '>' changes, * assume the isEnded state has changed. */ private boolean changeInIsEndedState(String oldText, String newText) { int nOld = StringUtils.occurrencesOf(oldText, '>'); int nNew = StringUtils.occurrencesOf(newText, '>'); return !(nOld == nNew); } private void checkAndAssignParent(IStructuredDocumentRegion oldNode, ITextRegion region) { if (region instanceof ITextRegionContainer) { ((ITextRegionContainer) region).setParent(oldNode); return; } if (region instanceof ITextRegionCollection) { ITextRegionCollection textRegionCollection = (ITextRegionCollection) region; ITextRegionList regionList = textRegionCollection.getRegions(); for (int i = 0; i < regionList.size(); i++) { ITextRegion innerRegion = regionList.get(i); checkAndAssignParent(oldNode, innerRegion); } } } /** * A change to a CDATA tag can result in all being reparsed. */ private StructuredDocumentEvent checkForCDATA() { StructuredDocumentEvent result = null; result = checkForCriticalKey("<![CDATA["); //$NON-NLS-1$ if (result == null) result = checkForCriticalKey("]]>"); //$NON-NLS-1$ return result; } /** * If a comment start or end tag is being added or deleted, we'll rescan * the whole document. The reason is that content that is revealed or * commented out can effect the interpretation of the rest of the * document. Note: for now this is very XML specific, can refactor/improve * later. */ protected StructuredDocumentEvent checkForComments() { StructuredDocumentEvent result = null; result = checkForCriticalKey("<!--"); //$NON-NLS-1$ if (result == null) result = checkForCriticalKey("-->"); //$NON-NLS-1$ // we'll also check for these degenerate cases if (result == null) result = checkForCriticalKey("<!--->"); //$NON-NLS-1$ return result; } /** * Common utility for checking for critical word such as " <SCRIPT>" */ protected StructuredDocumentEvent checkForCriticalKey(String criticalTarget) { return _checkForCriticalWord(criticalTarget, false); } /** * Common utility for checking for critical word such as " <SCRIPT>" */ private StructuredDocumentEvent checkForCriticalName(String criticalTarget) { return _checkForCriticalWord(criticalTarget, true); } // /** // * Currently this method is pretty specific to ?ML // * @deprecated - not really deprecated, but plan to make // * protected ... I'm not sure why its public or misspelled? // */ protected StructuredDocumentEvent checkForCrossStructuredDocumentRegionBoundryCases() { StructuredDocumentEvent result = null; // Case 1: See if the language's syntax requires that multiple // StructuredDocumentRegions be rescanned if (result == null) { result = checkForCrossStructuredDocumentRegionSyntax(); } // Case 2: "block tags" whose content is left unparsed if (result == null) { Object parser = fStructuredDocument.getParser(); if (parser instanceof BlockTagParser) { List blockTags = ((BlockTagParser) parser).getBlockMarkers(); result = _checkBlockNodeList(blockTags); } } // FUTURE_TO_DO: is there a better place to do this? // or! do we already do it some other more central place? if (result != null) { result.setDeletedText(fDeletedText); } return result; } /** * Allow a reparser to check for extra syntactic cases that require * parsing beyond the flatNode boundary. * * This implementation is very XML-centric. */ protected StructuredDocumentEvent checkForCrossStructuredDocumentRegionSyntax() { StructuredDocumentEvent result; // Case 1: Quote characters are involved result = checkForQuotes(); if (result == null) { // Case 2: The input forms or undoes a comment beginning or // comment // end result = checkForComments(); } if (result == null) { // Case 3: The input forms or undoes a processing instruction result = checkForPI(); } if (result == null) { // Case 4: The input forms or undoes a CDATA section result = checkForCDATA(); } return result; } /** * Checks to see if change request exactly matches the text it would be * replacing. (In future, this, or similar method is where to check for * "read only" attempted change.) */ private StructuredDocumentEvent checkForNoChange() { StructuredDocumentEvent result = null; // don't check equals unless lengths match // should be a tiny bit faster, since usually not // of equal lengths (I'm surprised String's equals method // doesn't do this.) if ((fChanges != null) && (fDeletedText != null) && (fChanges.length() == fDeletedText.length()) && (fChanges.equals(fDeletedText))) { result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace); ((NoChangeEvent)result).reason = NoChangeEvent.NO_CONTENT_CHANGE; } return result; } /** * A change to a PI tag can result in all being reparsed. */ private StructuredDocumentEvent checkForPI() { StructuredDocumentEvent result = null; result = checkForCriticalKey("<?"); //$NON-NLS-1$ if (result == null) result = checkForCriticalKey("?>"); //$NON-NLS-1$ return result; } /* * For simplicity, if either text to be deleted, or text to be inserted * contains at least one quote, we'll search for previous quote in * document, if any, and use that document region as a dirty start, and we'll use * end of document as dirty end. We need to assume either \" or \' is an * acceptable quote. (NOTE: this is, loosely, an XML assumption -- other * languages would differ, but we'll "hard code" for XML for now. * * future_TODO: this is a really bad heuristic ... we should be looking * for odd number of quotes within a structuredDocumentRegion (or * something!) This causes way too much reparsing on simple cases, like * deleting a tag with a quoted attribute! */ private StructuredDocumentEvent checkForQuotes() { // routine is supported with null or empty string meaning the same // thing: deletion if (fChanges == null) fChanges = ""; //$NON-NLS-1$ // StructuredDocumentEvent result = null; try { int dirtyStartPos = -1; String proposedDeletion = fStructuredDocument.get(fStart, fLengthToReplace); if (fStart < fStructuredDocument.getLength()) { if ((fChanges.indexOf(singleQuote) > -1) || (proposedDeletion.indexOf(singleQuote) > -1)) { IRegion singleQuoteRegion = getFindReplaceDocumentAdapter().find(fStart, singleQuote, false, false, false, false); if (singleQuoteRegion != null) { dirtyStartPos = singleQuoteRegion.getOffset(); } } else if ((fChanges.indexOf(doubleQuote) > -1) || (proposedDeletion.indexOf(doubleQuote) > -1)) { IRegion doubleQuoteRegion = getFindReplaceDocumentAdapter().find(fStart, doubleQuote, false, false, false, false); if (doubleQuoteRegion != null) { dirtyStartPos = doubleQuoteRegion.getOffset(); } } } if (dirtyStartPos > -1) { // then we found one, do create new structuredDocument event // based on the previous quote to end of document // except, we need to be positive that the previous quote is // in a "safe start" region (e.g. if in JSP content, we need // to // backup till we include the whole JSP region, in order for // it // to be correctly re-parsed. The backing up is done in the // reparse/find dirty start from hint // method. result = reparse(dirtyStartPos, fStructuredDocument.getLength() - 1); } } catch (BadLocationException e) { Logger.logException(e); } if (result != null) { result.setDeletedText(fDeletedText); } return result; } private StructuredDocumentEvent checkHeuristics() { StructuredDocumentEvent result = null; result = checkForNoChange(); if (result == null) { result = checkForCrossStructuredDocumentRegionBoundryCases(); if (result == null) { result = quickCheck(); } } return result; } /** * Takes into account "tag name" rules for comparisons; case-insensitive. */ private boolean checkTagNames(String compareText, String criticalTarget, boolean checkEnd) { boolean result = false; if ((compareText == null) || (criticalTarget == null)) return false; int posOfCriticalWord = compareText.toLowerCase().indexOf(criticalTarget.toLowerCase()); result = posOfCriticalWord > -1; if (checkEnd && result) { // instead of returning true right away, we'll only return true // the // potentially matched tag is indeed a tag, for example, if // <SCRIPT // becomes <SCRIPTS we don't want to say the latter is a critical // tag int lastPos = posOfCriticalWord + criticalTarget.length(); if (lastPos < compareText.length()) { char lastChar = compareText.charAt(lastPos); // Future: check formal definition of this java method, vs. // XML // parsing rules result = (!Character.isLetterOrDigit(lastChar)); } } return result; } /** * The core reparsing method ... after the dirty start and dirty end have * been calculated elsewhere, and the text updated. */ protected StructuredDocumentEvent core_reparse(int rescanStart, int rescanEnd, CoreNodeList oldNodes, boolean firstTime) { IStructuredDocumentRegion newNodesHead = null; StructuredDocumentEvent result = null; newNodesHead = _core_reparse_text(rescanStart, rescanEnd); result = _core_reparse_update_model(newNodesHead, rescanStart, rescanEnd, oldNodes, firstTime); return result; } /** * Resets state to "not parsing" */ private synchronized void endReParse() { isParsing = false; dirtyStart = null; dirtyEnd = null; fChanges = null; fDeletedText = null; fIsEntireDocument = false; } protected IStructuredDocumentRegion findDirtyEnd(int end) { // Caution: here's one place we have to cast IStructuredDocumentRegion result = fStructuredDocument.getRegionAtCharacterOffset(end); // if not well formed, get one past, if there is something there if ((result != null) && (!result.isEnded())) { if (result.getNext() != null) { result = result.getNext(); } } // also, get one past if exactly equal to the end (this was needed // as a simple fix to when a whole exact region is deleted. // there's probably a better way. if ((result != null) && (end == result.getEnd())) { if (result.getNext() != null) { result = result.getNext(); } } // moved to subclass for quick transition // 12/6/2001 - Since we've changed the parser/scanner to allow a lone // '<' without // always interpretting it as start of a tag name, we need to be a // little fancier, in order // to "skip" over any plain 'ol content between the lone '<' and any // potential meating // regions past plain 'ol content. // if (isLoneOpenFollowedByContent(result) && (result.getNext() != // null)) { // result = result.getNext(); // } if (result != null) fStructuredDocument.setCachedDocumentRegion(result); dirtyEnd = result; return dirtyEnd; } protected void findDirtyStart(int start) { IStructuredDocumentRegion result = fStructuredDocument.getRegionAtCharacterOffset(start); // heuristic: if the postion is exactly equal to the start, then // go back one more, if it exists. This prevents problems with // insertions // of text that should be merged with the previous node instead of // simply hung // off of it as a separate node (ex.: XML content inserted right // before // an open // bracket should become part of the previous content node) if (result != null) { IStructuredDocumentRegion previous = result.getPrevious(); if ((previous != null) && ((!(previous.isEnded())) || (start == result.getStart()))) { result = previous; } // If we are now at the end of a "tag dependent" content area (or // JSP area) // then we need to back up all the way to the beginning of that. IStructuredDocumentRegion potential = result; // moved to subclass to speed transition // while (isPartOfBlockRegion(potential)) { // potential = potential.getPrevious(); // } if (potential != null) { result = potential; fStructuredDocument.setCachedDocumentRegion(result); } } dirtyStart = result; } protected CoreNodeList formOldNodes(IStructuredDocumentRegion dirtyStart, IStructuredDocumentRegion dirtyEnd) { CoreNodeList oldNodes = new CoreNodeList(dirtyStart, dirtyEnd); // Now save the old text, that "goes with" the old nodes and regions. // Notice we are getting it directly from the text store String oldText = null; int oldStart = -1; int oldEnd = -1; // make sure there is some text, if not, use empty string // (if one node is not null, the other should ALWAYS be not null too, // since it // would at least be equal to it.) if (dirtyStart != null) { oldStart = dirtyStart.getStart(); oldEnd = dirtyEnd.getEnd(); oldText = fStructuredDocument.get(oldStart, oldEnd - oldStart); } else { oldStart = 0; oldEnd = 0; oldText = ""; //$NON-NLS-1$ } // create a temporary text store for this text SubSetTextStore subTextStore = new SubSetTextStore(oldText, oldStart, oldEnd, fStructuredDocument.getLength()); // Now update the text store of the oldNodes StructuredDocumentRegionIterator.setParentDocument(oldNodes, new MinimalDocument(subTextStore)); return oldNodes; } /** * @return Returns the findReplaceDocumentAdapter. */ public FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() { if (fFindReplaceDocumentAdapter == null) { fFindReplaceDocumentAdapter = new FindReplaceDocumentAdapter(fStructuredDocument); } return fFindReplaceDocumentAdapter; } // Note: if thead safety is needed, this and all the other public methods // of this class // should be synchronized. public void initialize(Object requester, int start, int lengthToReplace, String changes) { isParsing = true; fRequester = requester; fStart = start; fLengthToReplace = lengthToReplace; fChanges = changes; // notice this one is derived fLengthDifference = Utilities.calculateLengthDifference(fChanges, fLengthToReplace); fDeletedText = fStructuredDocument.get(fStart, fLengthToReplace); int docLength = fStructuredDocument.getLength(); fIsEntireDocument = lengthToReplace >= docLength && docLength > 0; } protected void insertNodes(IStructuredDocumentRegion previousOldNode, IStructuredDocumentRegion nextOldNode, CoreNodeList newNodes) { // IStructuredDocumentRegion firstNew = null; IStructuredDocumentRegion lastNew = null; // IStructuredDocumentRegion oldPrevious = previousOldNode; IStructuredDocumentRegion oldNext = nextOldNode; // if (newNodes.getLength() > 0) { // get pointers firstNew = newNodes.item(0); lastNew = newNodes.item(newNodes.getLength() - 1); // switch surrounding StructuredDocumentRegions' references to // lists if (oldPrevious != null) oldPrevious.setNext(firstNew); if (oldNext != null) { oldNext.setPrevious(lastNew); } else { // SIDE EFFECT // if oldNext is null, that means we are replaceing the // lastNode in the chain, // so we need to update the structuredDocuments lastNode as // the // last of the new nodes. fStructuredDocument.setLastDocumentRegion(newNodes.item(newNodes.getLength() - 1)); } if (firstNew != null) firstNew.setPrevious(oldPrevious); if (lastNew != null) lastNew.setNext(oldNext); } // else nothing to insert } /** * @param oldRegion */ private boolean isCollectionRegion(ITextRegion aRegion) { return (aRegion instanceof ITextRegionCollection); } /** * @return boolean */ public boolean isParsing() { return isParsing; } /** * The minimization algorithm simply checks the old nodes to see if any of * them "survived" the rescan and are unchanged. If so, the instance of * the old node is used instead of the new node. Before the requested * change, need to check type, offsets, and text to determine if the same. * After the requested change, need to check type and text, but adjust the * offsets to what ever the change was. */ protected StructuredDocumentEvent minimumEvent(CoreNodeList oldNodes, CoreNodeList newNodes) { StructuredDocumentEvent event = null; CoreNodeList minimalOldNodes = null; CoreNodeList minimalNewNodes = null; // To minimize nodes, we'll collect all those // that are not equal into old and new lists // Note: we assume that old and new nodes // are basically contiguous -- and we force it to be so, // by starting at the beginning to // find first difference, and then starting at the end to find // last difference. Everything in between we assume is different. // // // // startOfDifferences is the index into the core node list where the // first difference // occurs. But it may point into the old or the new list. int startOfDifferences = _computeStartOfDifferences(oldNodes, newNodes); int endOfDifferencesOld = -1; int endOfDifferencesNew = -1; // if one of the lists are shorter than where the differences start, // then // then some portion of the lists are identical if ((startOfDifferences >= oldNodes.getLength()) || (startOfDifferences >= newNodes.getLength())) { if (oldNodes.getLength() < newNodes.getLength()) { // Then there are new regions to add // these lengths will cause the vector of old ones to not // have any elements, and the vector of new regions to have // just the new ones not in common with the old ones //startOfDifferences should equal oldNodes.getLength(), // calculated above on _computeStartOfDifferences minimalOldNodes = EMPTY_LIST; endOfDifferencesNew = newNodes.getLength() - 1; minimalNewNodes = _formMinimumList(newNodes, startOfDifferences, endOfDifferencesNew); } else { if (oldNodes.getLength() > newNodes.getLength()) { // delete old // then there are old regions to delete // these lengths will cause the vector of old regions to // contain the ones to delete, and the vector of new // regions // not have any elements //startOfDifferences should equal newNodes.getLength(), // calculated above on _computeStartOfDifferences endOfDifferencesOld = oldNodes.getLength() - 1; minimalOldNodes = _formMinimumList(oldNodes, startOfDifferences, endOfDifferencesOld); minimalNewNodes = EMPTY_LIST; } else // unlikely event event = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace); } } else { // We found a normal startOfDiffernces, but have not yet found the // ends. // We'll look for the end of differences by going backwards down // the two lists. // Here we need a seperate index for each array, since they may be // (and // probably are) of different lengths. int indexOld = oldNodes.getLength() - 1; int indexNew = newNodes.getLength() - 1; // The greaterThanEffectedRegion is important to gaurd against // incorrect counting // when something identical is inserted to what's already there // (see minimization test case 5) // Note: the indexOld > startOfDifferences keeps indexOld from // getting too small, // so that the subsequent oldNodes.item(indexOld) is always valid. while ((indexOld >= startOfDifferences) && (_greaterThanEffectedRegion(oldNodes.item(indexOld)))) { if (!(oldNodes.item(indexOld).sameAs(newNodes.item(indexNew), fLengthDifference))) { break; } else { // if they are equal, then we will be keeping the old one, // so // we need to be sure its parentDocument is set back to // the // right instance oldNodes.item(indexOld).setParentDocument(fStructuredDocument); } indexOld--; indexNew--; } endOfDifferencesOld = indexOld; endOfDifferencesNew = indexNew; minimalOldNodes = _formMinimumList(oldNodes, startOfDifferences, endOfDifferencesOld); minimalNewNodes = _formMinimumList(newNodes, startOfDifferences, endOfDifferencesNew); } ///////////////////////////////////////// // IStructuredDocumentRegion firstDownStreamNode = null; event = regionCheck(minimalOldNodes, minimalNewNodes); if (event != null) { firstDownStreamNode = minimalOldNodes.item(0).getNext(); if (firstDownStreamNode != null && fLengthDifference != 0) { // if // firstDownStream // is // null, // then // we're // at // the // end // of // the // document StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference); } // } else { event = nodesReplacedCheck(minimalOldNodes, minimalNewNodes); // now splice the new chain of nodes to where the old chain is (or // was) // the firstDownStreamNode (the first of those after the new // nodes) // is // remembered as a tiny optimization. if (minimalOldNodes.getLength() == 0 && minimalNewNodes.getLength() > 0) { // if no old nodes are being deleted, then use the // the newNodes offset (minus one) to find the point to // update downstream nodes, and after updating downstream // nodes postions, insert the new ones. int insertOffset = minimalNewNodes.item(0).getStartOffset(); IStructuredDocumentRegion lastOldUnchangedNode = null; if (insertOffset > 0) { lastOldUnchangedNode = fStructuredDocument.getRegionAtCharacterOffset(insertOffset - 1); firstDownStreamNode = lastOldUnchangedNode.getNext(); } else { // we're inserting at very beginning firstDownStreamNode = fStructuredDocument.getFirstStructuredDocumentRegion(); // SIDE EFFECT: change the firstNode pointer if we're // inserting at beginning fStructuredDocument.setFirstDocumentRegion(minimalNewNodes.item(0)); } StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference); insertNodes(lastOldUnchangedNode, firstDownStreamNode, minimalNewNodes); // this (nodes replaced) is the only case where we need to // update the cached Node reSetCachedNode(minimalOldNodes, minimalNewNodes); } else { firstDownStreamNode = switchNodeLists(minimalOldNodes, minimalNewNodes); // no need to adjust the length of the new nodes themselves, // they // are already correct, but we do need to // adjust all "down stream" nodes with the length of the // insertion or deletion // --- adjustment moved to calling method. if (firstDownStreamNode != null) { // && event != null StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference); } // // this (nodes replaced) is the only case where we need to // update the cached Node reSetCachedNode(minimalOldNodes, minimalNewNodes); } } return event; } // TODO: This should be abstract. public IStructuredTextReParser newInstance() { return new StructuredDocumentReParser(); } protected StructuredDocumentEvent nodesReplacedCheck(CoreNodeList oldNodes, CoreNodeList newNodes) { // actually, nothing to check here, since (and assuming) we've already // minimized the number of nodes, and ruled out mere region changes StructuredDocumentEvent result = new StructuredDocumentRegionsReplacedEvent(fStructuredDocument, fRequester, oldNodes, newNodes, fChanges, fStart, fLengthToReplace, fIsEntireDocument); return result; } /** * A method to allow any heuristic "quick checks" that might cover many * many cases, before expending the time on a full reparse. * */ public StructuredDocumentEvent quickCheck() { StructuredDocumentEvent result = null; // if the dirty start is null, then we have an empty document. // in which case we'll return null so everything can be // reparsed "from scratch" . If its not null, we'll give the flatnode // a // chance // to handle, but only if there is one flatnode involved. if (dirtyStart != null && dirtyStart == dirtyEnd) { IStructuredDocumentRegion targetNode = dirtyStart; result = dirtyStart.updateRegion(fRequester, targetNode, fChanges, fStart, fLengthToReplace); if (result != null) { // at this point only, we need to update the text store and // and downstream nodes. // FUTURE_TO_DO: can this dependency on structuredDocument // method be eliminated? fStructuredDocument.updateDocumentData(fStart, fLengthToReplace, fChanges); IStructuredDocumentRegion firstDownStreamNode = targetNode.getNext(); // then flatnode must have been the last one, so need to // update // any downstream ones if (firstDownStreamNode != null) { StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference); } } } if (result != null) { result.setDeletedText(fDeletedText); } return result; } /** * If only one node is involved, sees how many regions are changed. If * only one, then its a 'regionChanged' event ... if more than one, its a * 'regionsReplaced' event. */ protected StructuredDocumentEvent regionCheck(CoreNodeList oldNodes, CoreNodeList newNodes) { if (Debug.debugStructuredDocument) System.out.println("IStructuredDocument::regionsReplacedCheck"); //$NON-NLS-1$ //$NON-NLS-1$ //$NON-NLS-1$ // the "regionsReplaced" event could only be true if and only if the // nodelists // are each only "1" in length. StructuredDocumentEvent result = null; int oldLength = oldNodes.getLength(); int newLength = newNodes.getLength(); if ((oldLength != 1) || (newLength != 1)) { result = null; } else { IStructuredDocumentRegion oldNode = oldNodes.item(0); IStructuredDocumentRegion newNode = newNodes.item(0); result = regionCheck(oldNode, newNode); } return result; } /** * If only one node is involved, sees how many regions are changed. If * only one, then its a 'regionChanged' event ... if more than one, its a * 'regionsReplaced' event. */ protected StructuredDocumentEvent regionCheck(IStructuredDocumentRegion oldNode, IStructuredDocumentRegion newNode) { // StructuredDocumentEvent result = null; ITextRegionList oldRegions = oldNode.getRegions(); ITextRegionList newRegions = newNode.getRegions(); ITextRegion[] oldRegionsArray = oldRegions.toArray(); ITextRegion[] newRegionsArray = newRegions.toArray(); // // for the 'regionsReplaced' event, we don't care if // the regions changed due to type, or text, // we'll just collect all those that are not equal // into the old and new region lists. // Note: we, of course, assume that old and new regions // are basically contiguous -- and we force it to be so, // even if not literally so, by starting at beginning to // find first difference, and then starting at end to find // last difference. Everything in between we assume is different. // // going up is easy, we start at zero in each, and continue // till regions are not the same. int startOfDifferences = _computeStartOfDifferences(oldNode, oldRegions, newNode, newRegions); int endOfDifferencesOld = -1; int endOfDifferencesNew = -1; // // // if one of the lists are shorter than where the differences start, // then // then some portion of the lists are identical if ((startOfDifferences >= oldRegions.size()) || (startOfDifferences >= newRegions.size())) { if (oldRegions.size() < newRegions.size()) { // INSERT CASE // then there are new regions to add // these lengths will cause the vector of old ones to not // have any elements, and the vector of new regions to have // just the new ones. startOfDifferences = oldRegionsArray.length; endOfDifferencesOld = oldRegionsArray.length - 1; endOfDifferencesNew = newRegionsArray.length - 1; } else { if (oldRegions.size() > newRegions.size()) { // DELETE CASE // then there are old regions to delete // these lengths will cause the vector of old regions to // contain the ones to delete, and the vector of new // regions // not have any elements startOfDifferences = newRegionsArray.length; endOfDifferencesOld = oldRegionsArray.length - 1; endOfDifferencesNew = newRegionsArray.length - 1; } else { // else the lists are identical! // unlikely event, probably error in current design, since // we check for identity at the very beginning of // reparsing. result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace); } } } else { if ((startOfDifferences > -1) && (endOfDifferencesOld < 0) && (endOfDifferencesNew < 0)) { // We found a normal startOfDiffernces, but have not yet found // the ends. // We'll look for the end of differences by going backwards // down the two lists. // Here we need a seperate index for each array, since they // may // be (and // probably are) of different lengths. int indexOld = oldRegionsArray.length - 1; int indexNew = newRegionsArray.length - 1; while ((indexOld >= startOfDifferences) && (_greaterThanEffectedRegion(oldNode, oldRegionsArray[indexOld]))) { if ((!(oldNode.sameAs(oldRegionsArray[indexOld], newNode, newRegionsArray[indexNew], fLengthDifference)))) { //endOfDifferencesOld = indexOne; //endOfDifferencesNew = indexTwo; break; } indexOld--; indexNew--; } endOfDifferencesOld = indexOld; endOfDifferencesNew = indexNew; } } // // result != null means the impossible case above occurred if (result == null) { // Now form the two vectors of different regions ITextRegionList holdOldRegions = new TextRegionListImpl(); ITextRegionList holdNewRegions = new TextRegionListImpl(); if (startOfDifferences > -1 && endOfDifferencesOld > -1) { for (int i = startOfDifferences; i <= endOfDifferencesOld; i++) { holdOldRegions.add(oldRegionsArray[i]); } } if (startOfDifferences > -1 && endOfDifferencesNew > -1) { for (int i = startOfDifferences; i <= endOfDifferencesNew; i++) { holdNewRegions.add(newRegionsArray[i]); } } if (holdOldRegions.size() == 0 && holdNewRegions.size() == 0) { // then this means the regions were identical, which means // someone // pasted exactly the same thing they had selected, or !!! // someone deleted the end bracket of the tag. !!!? result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace); } else { //If both holdOldRegions and holdNewRegions are of length 1, // then its // a "region changed" event, else a "regions replaced" event. // so we want the new instance of region to become part of the // old instance of old node if ((holdOldRegions.size() == 1) && (holdNewRegions.size() == 1) && _regionsSameKind((holdNewRegions.get(0)), (holdOldRegions.get(0)))) { ITextRegion newOldRegion = swapNewForOldRegion(oldNode, holdOldRegions.get(0), newNode, holdNewRegions.get(0)); // -- need to update any down stream regions, within this // 'oldNode' updateDownStreamRegions(oldNode, newOldRegion); result = new RegionChangedEvent(fStructuredDocument, fRequester, oldNode, newOldRegion, fChanges, fStart, fLengthToReplace); } else { replaceRegions(oldNode, holdOldRegions, newNode, holdNewRegions); // -- need to update any down stream regions, within this // 'oldNode' // don't need with the way replaceRegions is implemented. // It handles. //if(holdNewRegions.size() > 0) //updateDownStreamRegions(oldNode, (ITextRegion) // holdNewRegions.lastElement()); result = new RegionsReplacedEvent(fStructuredDocument, fRequester, oldNode, holdOldRegions, holdNewRegions, fChanges, fStart, fLengthToReplace); } } } return result; } /** * An entry point for reparsing. It calculates the dirty start and dirty * end flatnodes based on the start point and length of the changes. * */ public StructuredDocumentEvent reparse() { StructuredDocumentEvent result = null; // if we do not have a cachedNode, then the document // must be empty, so simply use 'null' as the dirtyStart and dirtyEnd // otherwise, find them. if (fStructuredDocument.getCachedDocumentRegion() != null) { findDirtyStart(fStart); int end = fStart + fLengthToReplace; findDirtyEnd(end); } if (fStructuredDocument.getCachedDocumentRegion() != null) { result = checkHeuristics(); } if (result == null) { result = reparse(dirtyStart, dirtyEnd); } endReParse(); return result; } /** * An entry point for reparsing. It calculates the dirty start and dirty * end flatnodes based on suggested positions to begin and end. This is * needed for cases where parsing must go beyond the immediate node and * its direct neighbors. * */ protected StructuredDocumentEvent reparse(int reScanStartHint, int reScanEndHint) { StructuredDocumentEvent result = null; // if we do not have a cachedNode, then the document // must be empty, so simply use 'null' as the dirtyStart and dirtyEnd if (fStructuredDocument.getCachedDocumentRegion() != null) { findDirtyStart(reScanStartHint); findDirtyEnd(reScanEndHint); } result = reparse(dirtyStart, dirtyEnd); isParsing = false; // debug //verifyStructured(result); return result; } /** * The core reparsing method ... after the dirty start and dirty end have * been calculated elsewhere. */ protected StructuredDocumentEvent reparse(IStructuredDocumentRegion dirtyStart, IStructuredDocumentRegion dirtyEnd) { StructuredDocumentEvent result = null; int rescanStart = -1; int rescanEnd = -1; boolean firstTime = false; // // "save" the oldNodes (that may be replaced) in a list CoreNodeList oldNodes = formOldNodes(dirtyStart, dirtyEnd); if (dirtyStart == null || dirtyEnd == null) { // dirtyStart or dirty end are null, then that means we didn't // have // a // cached node, which means we have an empty document, so we // just need to rescan the changes rescanStart = 0; rescanEnd = fChanges.length(); firstTime = true; } else { // set the start of the text to rescan rescanStart = dirtyStart.getStart(); // // set the end of the text to rescan // notice we use the same rationale as for the rescanStart, // with the added caveat that length has to be added to it, // to compensate for the new text which has been added or deleted. // If changes has zero length, then "length" will be negative, // since // we are deleting text. Otherwise, use the difference between // what's selected to be replaced and the length of the new text. rescanEnd = dirtyEnd.getEnd() + fLengthDifference; } // now that we have the old stuff "saved" away, update the document // with the changes. // FUTURE_TO_DO -- don't fire "document changed" event till later fStructuredDocument.updateDocumentData(fStart, fLengthToReplace, fChanges); // ------------------ now the real work result = core_reparse(rescanStart, rescanEnd, oldNodes, firstTime); // // event is returned to the caller, incase there is // some opitmization they can do return result; } protected void replaceRegions(IStructuredDocumentRegion oldNode, ITextRegionList oldRegions, IStructuredDocumentRegion newNode, ITextRegionList newRegions) { int insertPos = -1; ITextRegionList regions = oldNode.getRegions(); // make a fake flatnode to be new parent of oldRegions, so their text // will be right. //IStructuredDocumentRegion holdOldStructuredDocumentRegion = new // BasicStructuredDocumentRegion(oldNode); // // need to reset the parent of the new to-be-inserted regions to be // the // same oldNode that is the one having its regions changed // DW, 4/16/2003, removed since ITextRegion no longer has parent. // ITextRegionContainer oldParent = oldNode; // for (int i = 0; i < newRegions.size(); i++) { // AbstractRegion region = (AbstractRegion) newRegions.elementAt(i); // region.setParent(oldParent); // } // if there are no old regions, insert the new regions according to // offset if (oldRegions.size() == 0) { ITextRegion firstNewRegion = newRegions.get(0); int firstOffset = newNode.getStartOffset(firstNewRegion); // if at beginning, insert there if (firstOffset == 0) { insertPos = 0; } else { // ITextRegion regionAtOffset = oldNode.getRegionAtCharacterOffset(firstOffset); if (regionAtOffset == null) insertPos = regions.size(); else insertPos = regions.indexOf(regionAtOffset); } } else { // else, delete old ones before inserting new ones in their place ITextRegion firstOldRegion = oldRegions.get(0); insertPos = regions.indexOf(firstOldRegion); regions.removeAll(oldRegions); } regions.addAll(insertPos, newRegions); // now regions vector of each node should be of equal length, // so go through each, and make sure the old regions // offsets matches the new regions offsets // (we'll just assign them all, but could be slightly more effiecient) ITextRegionList allNewRegions = newNode.getRegions(); for (int i = 0; i < regions.size(); i++) { ITextRegion nextOldishRegion = regions.get(i); ITextRegion nextNewRegion = allNewRegions.get(i); nextOldishRegion.equatePositions(nextNewRegion); checkAndAssignParent(oldNode, nextOldishRegion); } oldNode.setLength(newNode.getLength()); oldNode.setEnded(newNode.isEnded()); oldNode.setParentDocument(newNode.getParentDocument()); // removed concept of part of these regions, so no longer need to do. // for (int i = 0; i < oldRegions.size(); i++) { // ITextRegion region = (ITextRegion) oldRegions.elementAt(i); // region.setParent(holdOldStructuredDocumentRegion); // } } private void reSetCachedNode(CoreNodeList oldNodes, CoreNodeList newNodes) { // use the last newNode as the new cachedNode postion, unless its null // (e.g. when nodes are deleted) in which case, assign // it to a "safe" node so we don't lose reference to the // structuredDocument! if (newNodes.getLength() > 0) { // use last new node as the cache fStructuredDocument.setCachedDocumentRegion(newNodes.item(newNodes.getLength() - 1)); } else { // if cachedNode is an old node, then we're in trouble: // we can't leave it as the cached node! and its already // been disconnected from the model, so we can't do getNext // or getPrevious, so we'll get one that is right before // (or right after) the offset of the old nodes that are being // deleted. // // if newNodesHead and cachedNode are both null, then // it means we were asked to insert an empty string into // an empty document. So we have nothing to do here // (that is, we have no node to cache) // similarly if there are no new nodes and no old nodes then // nothing to do (but that should never happen ... we shouldn't // get there if there is no event to generate). if ((fStructuredDocument.getCachedDocumentRegion() != null) && (oldNodes.getLength() > 0)) { // note: we can't simple use nodeAtCharacterOffset, since it // depends on cachedNode. if (oldNodes.includes(fStructuredDocument.getCachedDocumentRegion())) fStructuredDocument.setCachedDocumentRegion(fStructuredDocument.getFirstStructuredDocumentRegion()); } if ((fStructuredDocument.getCachedDocumentRegion() == null) && (Debug.displayWarnings)) { // this will happen now legitamately when all text is deleted // from a document System.out.println("Warning: StructuredDocumentReParser::reSetCachedNode: could not find a node to cache! (its ok if all text deleted)"); //$NON-NLS-1$ } } } public void setStructuredDocument(IStructuredDocument newStructuredDocument) { // NOTE: this method (and class) depend on being able to // do the following cast (i.e. references some fields directly) fStructuredDocument = (BasicStructuredDocument) newStructuredDocument; fFindReplaceDocumentAdapter = null; } private IStructuredDocumentRegion splice(CoreNodeList oldNodes, CoreNodeList newNodes) { // IStructuredDocumentRegion firstOld = null; IStructuredDocumentRegion firstNew = null; IStructuredDocumentRegion lastOld = null; IStructuredDocumentRegion lastNew = null; // IStructuredDocumentRegion oldPrevious = null; IStructuredDocumentRegion oldNext = null; IStructuredDocumentRegion newPrevious = null; IStructuredDocumentRegion newNext = null; // // if called with both arguments empty lists, we can disregard. // this happens, for example, when some text is replaced with the // identical text. if ((oldNodes.getLength() == 0) && (newNodes.getLength() == 0)) { return null; } // get pointers if (newNodes.getLength() > 0) { firstNew = newNodes.item(0); lastNew = newNodes.item(newNodes.getLength() - 1); } // if (oldNodes.getLength() > 0) { firstOld = oldNodes.item(0); lastOld = oldNodes.item(oldNodes.getLength() - 1); if (firstOld != null) oldPrevious = firstOld.getPrevious(); if (lastOld != null) oldNext = lastOld.getNext(); } // handle switch if (newNodes.getLength() > 0) { // switch surrounding StructuredDocumentRegions' references to // lists if (oldPrevious != null) oldPrevious.setNext(firstNew); if (newPrevious != null) newPrevious.setNext(firstOld); if (oldNext != null) oldNext.setPrevious(lastNew); if (newNext != null) newNext.setPrevious(lastOld); // switch list pointers to surrounding StructuredDocumentRegions if (firstOld != null) firstOld.setPrevious(newPrevious); if (lastOld != null) lastOld.setNext(newNext); if (firstNew != null) firstNew.setPrevious(oldPrevious); if (lastNew != null) lastNew.setNext(oldNext); } else { // short circuit when there are no new nodes if (oldPrevious != null) oldPrevious.setNext(oldNext); if (oldNext != null) oldNext.setPrevious(oldPrevious); } // // SIDE EFFECTs // if we have oldNodes, and if oldNext or oldPrevious is null, // that means we are replacing // the lastNode or firstNode the structuredDocuments's chain of nodes, // so we need to update the structuredDocuments last or first Node // as the last or first of the new nodes. // (and sometimes even these will be null! such as when deleting all // text in a document). if ((oldNext == null) && (oldNodes.getLength() > 0)) { if (newNodes.getLength() > 0) { fStructuredDocument.setLastDocumentRegion(lastNew); } else { // in this case, the last node is being deleted, but not // replaced // with anything. In this case, we can just back up one // from the first old node fStructuredDocument.setLastDocumentRegion(firstOld.getPrevious()); } } if ((oldPrevious == null) && (oldNodes.getLength() > 0)) { if (newNodes.getLength() > 0) { fStructuredDocument.setFirstDocumentRegion(firstNew); } else { // in this case the first node is being deleted, but not // replaced // with anything. So, we just go one forward past the last old // node. fStructuredDocument.setFirstDocumentRegion(lastOld.getNext()); } } // as a tiny optimization, we return the first of the downstream // nodes, // if any return oldNext; } /** * The purpose of this method is to "reuse" the old container region, when * found to be same (so same instance doesn't change). The goal is to * "transform" the old region, so its equivelent to the newly parsed one. * */ private ITextRegion swapNewForOldRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion, IStructuredDocumentRegion newNode, ITextRegion newRegion) { // makes the old region instance the correct size. oldRegion.equatePositions(newRegion); // adjusts old node instance appropriately oldNode.setLength(newNode.getLength()); oldNode.setEnded(newNode.isEnded()); // we do have to set the parent document, since the oldNode's // were set to a temporary one, then newNode's have the // right one. oldNode.setParentDocument(newNode.getParentDocument()); // if we're transforming a container region, we need to be sure to // transfer the new embedded regions, to the old parent // Note: if oldRegion hasEmbeddedRegions, then we know the // newRegion does too, since we got here because they were the // same type. if (isCollectionRegion(oldRegion)) { // || // hasContainerRegions(oldRegion)) // { transferEmbeddedRegions(oldNode, (ITextRegionContainer) oldRegion, (ITextRegionContainer) newRegion); } return oldRegion; } private IStructuredDocumentRegion switchNodeLists(CoreNodeList oldNodes, CoreNodeList newNodes) { IStructuredDocumentRegion result = splice(oldNodes, newNodes); // ensure that the old nodes hold no references to the existing model if (oldNodes.getLength() > 0) { IStructuredDocumentRegion firstItem = oldNodes.item(0); firstItem.setPrevious(null); IStructuredDocumentRegion lastItem = oldNodes.item(oldNodes.getLength() - 1); lastItem.setNext(null); } return result; } /** * The purpose of this method is to "reuse" the old container region, when * found to be same (so same instance doesn't change). The goal is to * "transform" the old region, so its equivelent to the newly parsed one. * */ private void transferEmbeddedRegions(IStructuredDocumentRegion oldNode, ITextRegionContainer oldRegion, ITextRegionContainer newRegion) { // the oldRegion should already have the right parent, since // we got here because all's equivelent except the region // postions have changed. //oldRegion.setParent(newRegion.getParent()); // but we should check if there's "nested" embedded regions, and if // so, we can just move them over. setting their parent as this old // region. ITextRegionList newRegionsToTransfer = newRegion.getRegions(); oldRegion.setRegions(newRegionsToTransfer); Iterator newRegionsInOldOne = newRegionsToTransfer.iterator(); while (newRegionsInOldOne.hasNext()) { ITextRegion newOne = (ITextRegion) newRegionsInOldOne.next(); if (isCollectionRegion(newOne)) { // || // hasContainerRegions(newOne)) { //((ITextRegionContainer) newOne).setParent(oldRegion); oldRegion.setRegions(newRegion.getRegions()); } } } private void updateDownStreamRegions(IStructuredDocumentRegion flatNode, ITextRegion lastKnownRegion) { // so all regions after the last known region (last known to be ok) // have to have their start and end values adjusted. ITextRegionList regions = flatNode.getRegions(); int listLength = regions.size(); int startIndex = 0; // first, loop through to find where to start for (int i = 0; i < listLength; i++) { ITextRegion region = regions.get(i); if (region == lastKnownRegion) { startIndex = i; break; } } // now, beginning one past the last known one, loop // through to end of list, adjusting the start and end postions. startIndex++; for (int j = startIndex; j < listLength; j++) { ITextRegion region = regions.get(j); region.adjustStart(fLengthDifference); } } }