| /******************************************************************************* |
| * Copyright (c) 2001, 2010 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 300430 - String concatenation |
| * David Carver (Intalio) - bug 300427 - Comparison of String Objects == or != |
| *******************************************************************************/ |
| package org.eclipse.wst.sse.core.internal.text; |
| |
| |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.wst.sse.core.internal.Logger; |
| import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent; |
| 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.ITextRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; |
| import org.eclipse.wst.sse.core.internal.util.Assert; |
| import org.eclipse.wst.sse.core.internal.util.Debug; |
| import org.eclipse.wst.sse.core.internal.util.Utilities; |
| |
| |
| public class BasicStructuredDocumentRegion implements IStructuredDocumentRegion { |
| private static final String TEXT_STORE_NOT_ASSIGNED = "text store not assigned yet"; //$NON-NLS-1$ |
| private static final String UNDEFINED = "org.eclipse.wst.sse.core.structuredDocument.UNDEFINED"; //$NON-NLS-1$ |
| |
| private ITextRegionList _regions; |
| /** |
| * has this region been removed from its document |
| */ |
| private static final byte MASK_IS_DELETED = 1; |
| /** |
| * was this region terminated normally |
| */ |
| private static final byte MASK_IS_ENDED = 1 << 1; |
| |
| private byte fIsDeletedOrEnded = 0; |
| |
| /** |
| * allow a pointer back to this nodes model |
| */ |
| private IStructuredDocument fParentDocument; |
| |
| protected int fLength; |
| private IStructuredDocumentRegion next = null; |
| private IStructuredDocumentRegion previous = null; |
| protected int start; |
| |
| public BasicStructuredDocumentRegion() { |
| super(); |
| _regions = new TextRegionListImpl(); |
| |
| } |
| |
| /** |
| * Even inside-this class uses of 'regions' should use this method, as |
| * this is where (soft) memory management/reparsing, etc., will be |
| * centralized. |
| */ |
| private ITextRegionList _getRegions() { |
| |
| return _regions; |
| } |
| |
| public void addRegion(ITextRegion aRegion) { |
| _getRegions().add(aRegion); |
| } |
| |
| public void adjust(int i) { |
| start += i; |
| } |
| |
| public void adjustLength(int i) { |
| fLength += i; |
| } |
| |
| public void adjustStart(int i) { |
| start += i; |
| } |
| |
| public void adjustTextLength(int i) { |
| // not supported |
| |
| } |
| |
| public boolean containsOffset(int i) { |
| |
| return getStartOffset() <= i && i < getEndOffset(); |
| } |
| |
| public boolean containsOffset(ITextRegion containedRegion, int offset) { |
| return getStartOffset(containedRegion) <= offset && offset < getEndOffset(containedRegion); |
| } |
| |
| public void equatePositions(ITextRegion region) { |
| start = region.getStart(); |
| fLength = region.getLength(); |
| } |
| |
| /** |
| * getEnd and getEndOffset are the same only for |
| * IStructuredDocumentRegions |
| */ |
| public int getEnd() { |
| return start + fLength; |
| } |
| |
| /** |
| * getEnd and getEndOffset are the same only for |
| * IStructuredDocumentRegions |
| */ |
| public int getEndOffset() { |
| return getEnd(); |
| } |
| |
| public int getEndOffset(ITextRegion containedRegion) { |
| return getStartOffset(containedRegion) + containedRegion.getLength(); |
| } |
| |
| public ITextRegion getFirstRegion() { |
| if (_getRegions() == null) |
| return null; |
| return _getRegions().get(0); |
| } |
| |
| public String getFullText() { |
| String result = ""; //$NON-NLS-1$ |
| try { |
| result = getParentDocument().get(start, fLength); |
| } |
| catch (BadLocationException e) { |
| // log for now, unless we find reason not to |
| Logger.log(Logger.INFO, e.getMessage()); |
| } |
| return result; |
| } |
| |
| public String getFullText(ITextRegion aRegion) { |
| String result = ""; //$NON-NLS-1$ |
| try { |
| int regionStart = aRegion.getStart(); |
| int regionLength = aRegion.getLength(); |
| result = fParentDocument.get(start + regionStart, regionLength); |
| } |
| catch (BadLocationException e) { |
| // log for now, unless we find reason not to |
| Logger.log(Logger.INFO, e.getMessage()); |
| } |
| return result; |
| } |
| |
| public String getFullText(String context) { |
| // DMW: looping is faster than enumeration, |
| // so switched around 2/12/03 |
| // Enumeration e = getRegions().elements(); |
| ITextRegion region = null; |
| String result = ""; //$NON-NLS-1$ |
| int length = getRegions().size(); |
| StringBuffer sb = new StringBuffer(result); |
| for (int i = 0; i < length; i++) { |
| region = getRegions().get(i); |
| if (region.getType().equals(context)) |
| sb.append(getFullText(region)); |
| } |
| result = sb.toString(); |
| return result; |
| } |
| |
| public ITextRegion getLastRegion() { |
| if (_getRegions() == null) |
| return null; |
| return _getRegions().get(_getRegions().size() - 1); |
| } |
| |
| public int getLength() { |
| return fLength; |
| } |
| |
| public IStructuredDocumentRegion getNext() { |
| return next; |
| } |
| |
| public int getNumberOfRegions() { |
| return _getRegions().size(); |
| } |
| |
| public IStructuredDocument getParentDocument() { |
| |
| return fParentDocument; |
| } |
| |
| public IStructuredDocumentRegion getPrevious() { |
| return previous; |
| } |
| |
| /** |
| * The parameter offset refers to the overall offset in the document. |
| */ |
| public ITextRegion getRegionAtCharacterOffset(int offset) { |
| if (_getRegions() != null) { |
| int thisStartOffset = getStartOffset(); |
| if (offset < thisStartOffset) |
| return null; |
| int thisEndOffset = getStartOffset() + getLength(); |
| if (offset > thisEndOffset) |
| return null; |
| // transform the requested offset to the "scale" that |
| // regions are stored in, which are all relative to the |
| // start point. |
| // int transformedOffset = offset - getStartOffset(); |
| // |
| ITextRegionList regions = getRegions(); |
| int length = regions.size(); |
| int low = 0; |
| int high = length; |
| int mid = 0; |
| // Binary search for the region |
| while (low < high) { |
| mid = low + ((high - low) >> 1); |
| ITextRegion region = regions.get(mid); |
| if (Debug.debugStructuredDocument) { |
| System.out.println("region(s) in IStructuredDocumentRegion::getRegionAtCharacterOffset: " + region); //$NON-NLS-1$ |
| System.out.println(" requested offset: " + offset); //$NON-NLS-1$ |
| // System.out.println(" transformedOffset: " + |
| // transformedOffset); //$NON-NLS-1$ |
| System.out.println(" region start: " + region.getStart()); //$NON-NLS-1$ |
| System.out.println(" region end: " + region.getEnd()); //$NON-NLS-1$ |
| System.out.println(" region type: " + region.getType()); //$NON-NLS-1$ |
| System.out.println(" region class: " + region.getClass()); //$NON-NLS-1$ |
| |
| } |
| // Region is before this one |
| if (offset < region.getStart() + thisStartOffset) |
| high = mid; |
| else if (offset > (region.getEnd() + thisStartOffset - 1)) |
| low = mid + 1; |
| else |
| return region; |
| } |
| } |
| return null; |
| } |
| |
| public ITextRegionList getRegions() { |
| return _getRegions(); |
| } |
| |
| /** |
| * getStart and getStartOffset are the same only for |
| * IStrucutredDocumentRegions |
| */ |
| public int getStart() { |
| return start; |
| } |
| |
| /** |
| * getStart and getStartOffset are the same only for |
| * IStrucutredDocumentRegions |
| */ |
| public int getStartOffset() { |
| return getStart(); |
| } |
| |
| public int getStartOffset(ITextRegion containedRegion) { |
| // assert: containedRegion can not be null |
| // (might be performance hit if literally put in assert call, |
| // but containedRegion can not be null). Needs to be checked |
| // by calling code. |
| return getStartOffset() + containedRegion.getStart(); |
| } |
| |
| public String getText() { |
| String result = null; |
| try { |
| if (fParentDocument == null) { |
| // likely to happen during inspecting |
| result = TEXT_STORE_NOT_ASSIGNED; |
| } |
| else { |
| result = fParentDocument.get(start, fLength); |
| } |
| } |
| catch (BadLocationException e) { |
| // log for now, unless we find reason not to |
| Logger.log(Logger.INFO, e.getMessage()); |
| } |
| return result; |
| } |
| |
| public String getText(ITextRegion aRegion) { |
| // assert: aRegion can not be null |
| // (might be performance hit if literally put in assert call, |
| // but aRegion can not be null). Needs to be checked |
| // by calling code. |
| try { |
| return fParentDocument.get(this.getStartOffset(aRegion), aRegion.getTextLength()); |
| } |
| catch (BadLocationException e) { |
| Logger.logException(e); |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns the text of the first region with the matching context type |
| */ |
| public String getText(String context) { |
| // DMW: looping is faster than enumeration, |
| // so switched around 2/12/03 |
| // Enumeration e = getRegions().elements(); |
| ITextRegion region = null; |
| String result = ""; //$NON-NLS-1$ |
| int length = getRegions().size(); |
| for (int i = 0; i < length; i++) { |
| region = getRegions().get(i); |
| if (region.getType().equals(context)) { |
| result = getText(region); |
| break; |
| } |
| } |
| return result; |
| } |
| |
| public int getTextEnd() { |
| return start + fLength; |
| } |
| |
| /** |
| * @return int |
| */ |
| public int getTextEndOffset() { |
| ITextRegion region = _getRegions().get(_getRegions().size() - 1); |
| return getStartOffset() + region.getTextEnd(); |
| } |
| |
| public int getTextEndOffset(ITextRegion containedRegion) { |
| return getStartOffset(containedRegion) + containedRegion.getTextLength(); |
| } |
| |
| public int getTextLength() { |
| return fLength; |
| } |
| |
| /** |
| * Provides the type of IStructuredDocumentRegion ... not to be confused |
| * with type of XML node! This is subclassed, if something other than type |
| * of first region is desired. |
| * |
| */ |
| public String getType() { |
| String result = UNDEFINED; |
| ITextRegionList subregions = getRegions(); |
| if (subregions != null && subregions.size() > 0) { |
| ITextRegion firstRegion = subregions.get(0); |
| if (firstRegion != null) { |
| result = firstRegion.getType(); |
| } |
| } |
| return result; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.wst.sse.core.text.IStructuredDocumentRegion#isDeleted() |
| */ |
| public boolean isDeleted() { |
| return (fIsDeletedOrEnded & MASK_IS_DELETED) != 0 || (fParentDocument == null); |
| } |
| |
| /** |
| * |
| * @return boolean |
| */ |
| public boolean isEnded() { |
| return (fIsDeletedOrEnded & MASK_IS_ENDED) != 0; |
| } |
| |
| public boolean sameAs(IStructuredDocumentRegion region, int shift) { |
| boolean result = false; |
| // if region == null, we return false; |
| if (region != null) { |
| // if the regions are the same instance, they are equal |
| if (this == region) { |
| result = true; |
| } |
| else { |
| // this is the non-trivial part |
| // note: we change for type first, then start offset and end |
| // offset, |
| // since that would decide many cases right away and avoid the |
| // text comparison |
| if (getType().equals(region.getType())) { |
| if (sameOffsetsAs(region, shift) && sameTextAs(region, shift)) { |
| result = true; |
| } |
| } |
| |
| } |
| } |
| return result; |
| } |
| |
| public boolean sameAs(ITextRegion oldRegion, IStructuredDocumentRegion newDocumentRegion, ITextRegion newRegion, int shift) { |
| boolean result = false; |
| // if any region is null, we return false (even if both are!) |
| if ((oldRegion != null) && (newRegion != null)) { |
| // if the regions are the same instance, they are equal |
| if (oldRegion == newRegion) { |
| result = true; |
| } |
| else { |
| // this is the non-trivial part |
| // note: we change for type first, then start offset and end |
| // offset, |
| // since that would decide many cases right away and avoid the |
| // text comparison |
| if (oldRegion.getType().equals(newRegion.getType())) { |
| if (sameOffsetsAs(oldRegion, newDocumentRegion, newRegion, shift)) { |
| if (sameTextAs(oldRegion, newDocumentRegion, newRegion, shift)) { |
| result = true; |
| } |
| } |
| } |
| } |
| |
| } |
| |
| return result; |
| } |
| |
| private boolean sameOffsetsAs(IStructuredDocumentRegion region, int shift) { |
| if (getStartOffset() == region.getStartOffset() - shift) { |
| if (getEndOffset() == region.getEndOffset() - shift) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean sameOffsetsAs(ITextRegion oldRegion, IStructuredDocumentRegion documentRegion, ITextRegion newRegion, int shift) { |
| if (getStartOffset(oldRegion) == documentRegion.getStartOffset(newRegion) - shift) { |
| if (getEndOffset(oldRegion) == documentRegion.getEndOffset(newRegion) - shift) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean sameTextAs(IStructuredDocumentRegion region, int shift) { |
| boolean result = false; |
| try { |
| if (getText().equals(region.getText())) { |
| result = true; |
| } |
| } |
| // ISSUE: we should not need this |
| catch (StringIndexOutOfBoundsException e) { |
| result = false; |
| } |
| |
| return result; |
| } |
| |
| private boolean sameTextAs(ITextRegion oldRegion, IStructuredDocumentRegion documentRegion, ITextRegion newRegion, int shift) { |
| boolean result = false; |
| |
| if (getText(oldRegion).equals(documentRegion.getText(newRegion))) { |
| result = true; |
| } |
| |
| return result; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.wst.sse.core.text.IStructuredDocumentRegion#setDelete(boolean) |
| */ |
| public void setDeleted(boolean isDeleted) { |
| if (isDeleted) |
| fIsDeletedOrEnded |= MASK_IS_DELETED; |
| else |
| fIsDeletedOrEnded &= ~MASK_IS_DELETED; |
| } |
| |
| /** |
| * |
| * @param newHasEnd |
| * boolean |
| */ |
| public void setEnded(boolean newHasEnd) { |
| if (newHasEnd) |
| fIsDeletedOrEnded |= MASK_IS_ENDED; |
| else |
| fIsDeletedOrEnded &= ~MASK_IS_ENDED; |
| } |
| |
| public void setLength(int newLength) { |
| // textLength = newLength; |
| fLength = newLength; |
| } |
| |
| public void setNext(IStructuredDocumentRegion newNext) { |
| next = newNext; |
| } |
| |
| public void setParentDocument(IStructuredDocument document) { |
| fParentDocument = document; |
| |
| } |
| |
| public void setPrevious(IStructuredDocumentRegion newPrevious) { |
| previous = newPrevious; |
| } |
| |
| public void setRegions(ITextRegionList containedRegions) { |
| _regions = containedRegions; |
| } |
| |
| public void setStart(int newStart) { |
| start = newStart; |
| } |
| |
| public String toString() { |
| // NOTE: if the document held by any region has been updated and the |
| // region offsets have not |
| // yet been updated, the output from this method invalid. |
| // Also note, this method can not be changed, without "breaking" |
| // unit tests, since some of them compare current results to previous |
| // results. |
| String result = null; |
| result = "[" + getStart() + ", " + getEnd() + "] (" + getText() + ")"; //$NON-NLS-4$//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ |
| return result; |
| } |
| |
| private void updateDownStreamRegions(ITextRegion changedRegion, int lengthDifference) { |
| int listLength = _getRegions().size(); |
| int startIndex = 0; |
| // first, loop through to find index of where to start |
| for (int i = 0; i < listLength; i++) { |
| ITextRegion region = _getRegions().get(i); |
| if (region == changedRegion) { |
| startIndex = i; |
| break; |
| } |
| } |
| // now, beginning one past the one that was changed, loop |
| // through to end of list, adjusting the start postions. |
| startIndex++; |
| for (int j = startIndex; j < listLength; j++) { |
| ITextRegion region = _getRegions().get(j); |
| region.adjustStart(lengthDifference); |
| } |
| } |
| |
| public StructuredDocumentEvent updateRegion(Object requester, IStructuredDocumentRegion structuredDocumentRegion, String changes, int requestStart, int lengthToReplace) { |
| StructuredDocumentEvent result = null; |
| int lengthDifference = Utilities.calculateLengthDifference(changes, lengthToReplace); |
| // Get the region pointed to by the requestStart postion, and give |
| // that region a chance to effect |
| // the update. |
| ITextRegion region = getRegionAtCharacterOffset(requestStart); |
| // if there is no region, then the requested changes must come right |
| // after the |
| // node (and right after the last region). This happens, for example, |
| // when someone |
| // types something at the end of the document, or more commonly, when |
| // they are right |
| // at the beginning of one node, and the dirty start is therefore |
| // calculated to be the |
| // previous node. |
| // So, in this case, we'll give the last region a chance to see if it |
| // wants to |
| // swallow the requested changes -- but only for inserts -- deletes |
| // and "replaces" |
| // should be reparsed if they are in these border regions, and only if |
| // the |
| if ((region == null) && (lengthToReplace == 0)) { |
| region = _getRegions().get(_getRegions().size() - 1); |
| // make sure the region is contiguous |
| if (getEndOffset(region) == requestStart) { |
| result = region.updateRegion(requester, this, changes, requestStart, lengthToReplace); |
| } |
| } |
| else { |
| if (region != null) { |
| // |
| // If the requested change spans more than one region, then |
| // we don't give the region a chance to update. |
| if ((containsOffset(region, requestStart)) && (containsOffset(region, requestStart + lengthToReplace))) { |
| result = region.updateRegion(requester, this, changes, requestStart, lengthToReplace); |
| } |
| } |
| } |
| // if result is not null, then we need to update the start and end |
| // postions of the regions that follow this one |
| // if result is null, then apply the flatnode specific checks on what |
| // it can change |
| // (i.e. more than one region, but no change to the node itself) |
| if (result != null) { |
| // That is, a region decided it could handle the change and |
| // created |
| // a region changed event. |
| Assert.isTrue(result instanceof RegionChangedEvent, "Program Error"); //$NON-NLS-1$ |
| updateDownStreamRegions(((RegionChangedEvent) result).getRegion(), lengthDifference); |
| // PLUS, we need to update our own node end point (length) |
| setLength(getLength() + lengthDifference); |
| } |
| |
| return result; |
| } |
| |
| } |