blob: 624956f083120264823fc2eed02a0d739f8122cb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2008 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
*
*******************************************************************************/
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();
for (int i = 0; i < length; i++) {
region = getRegions().get(i);
if (region.getType() == context)
result += getFullText(region);
}
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) {
ITextRegion result = null;
if (_getRegions() != 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();
//
int length = getRegions().size();
for (int i = 0; i < length; i++) {
ITextRegion region = getRegions().get(i);
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$
}
if ((getStartOffset(region) <= offset) && (offset < getEndOffset(region))) {
result = region;
break;
}
}
}
return result;
}
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() == 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() == 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() == 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) {
fIsDeletedOrEnded = (byte) (isDeleted ? fIsDeletedOrEnded | MASK_IS_DELETED : 0);
}
/**
*
* @param newHasEnd
* boolean
*/
public void setEnded(boolean newHasEnd) {
fIsDeletedOrEnded = (byte) (newHasEnd ? fIsDeletedOrEnded | MASK_IS_ENDED : 0);
}
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;
}
}