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