blob: 551e64f6997efd1d84ae090fca45b2ddb3fb97cd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.text;
/**
* A child document is one particular example of a slave document. Child documents
* are created by the <code>ChildDocumentManager</code>, an implementation of
* <code>ISlaveDocumentManager</code>.<p>
*
* A child document represents one consequetive range of its master document.
* The master document is called parent document. The child document is always in sync
* with its parent document by utilizing the parent document as its <code>ITextStore</code>.<p>
*
* This class is for internal use only.
*
* @see ITextStore
*/
public final class ChildDocument extends AbstractDocument {
/**
* Implements <code>ITextStore</code> based on <code>IDocument</code>.
*/
class TextStore implements ITextStore {
/*
* @see ITextStore#set(String)
*/
public void set(String txt) {
try {
fParentDocument.replace(fRange.getOffset(), fRange.getLength(), txt);
} catch (BadLocationException x) {
// cannot happen
}
}
/*
* @see ITextStore#replace(int, int, String)
*/
public void replace(int offset, int length, String txt) {
try {
fParentDocument.replace(fRange.getOffset() + offset, length, txt);
} catch (BadLocationException x) {
// ignored as surrounding document should have handled this
}
}
/*
* @see ITextStore#getLength()
*/
public int getLength() {
return fRange.getLength();
}
/*
* @see ITextStore#get(int, int)
*/
public String get(int offset, int length) {
try {
return fParentDocument.get(fRange.getOffset() + offset, length);
} catch (BadLocationException x) {
}
return null;
}
/*
* @see ITextStore#get(int)
*/
public char get(int offset) {
try {
return fParentDocument.getChar(fRange.getOffset() + offset);
} catch (BadLocationException x) {
}
return (char) 0;
}
}
/** The parent document */
private IDocument fParentDocument;
/**
* The parent document as <code>IDocumentExtension</code>
* @since 2.0
*/
private IDocumentExtension fExtension;
/**
* The parent document as <code>IDocumentExtension2</code>
* @since 2.1
*/
private IDocumentExtension2 fExtension2;
/** The section inside the parent document */
private Position fRange;
/** The document event issued by the parent document */
private DocumentEvent fParentEvent;
/** The document event issued and to be issued by the child document */
private DocumentEvent fEvent;
/** Indicates whether the child document initiated a parent document update or not */
private boolean fIsUpdating= false;
/**
* The expected document content after the parent document changed.
* @since 2.1
*/
private String fExpectedContent;
/**
* The length of this child document prior to the change of the parent document
* @since 2.1
*/
private int fRememberedLength;
/**
* Indicates whether this document is in auto expand mode.
* @since 2.1
*/
private boolean fIsAutoExpanding= false;
/**
* Creates a child document for the given range of the given parent document.
*
* @param parentDocument the parent Document
* @param range the parent document range covered by the child document
*/
public ChildDocument(IDocument parentDocument, Position range) {
super();
fParentDocument= parentDocument;
if (fParentDocument instanceof IDocumentExtension)
fExtension= (IDocumentExtension) fParentDocument;
if (fParentDocument instanceof IDocumentExtension2)
fExtension2= (IDocumentExtension2) fParentDocument;
fRange= range;
ITextStore s= new TextStore();
ILineTracker tracker= new DefaultLineTracker();
tracker.set(s.get(0, fRange.getLength()));
setTextStore(s);
setLineTracker(tracker);
completeInitialization();
}
/**
* Sets the child document's parent document range.
*
* @param offset the offset of the parent document range
* @param length the length of the parent document range
*/
public void setParentDocumentRange(int offset, int length) throws BadLocationException {
if (offset < 0 || length < 0 || offset + length > fParentDocument.getLength())
throw new BadLocationException();
fRange.setOffset(offset);
fRange.setLength(length);
getTracker().set(fParentDocument.get(offset, length));
}
/**
* Returns parent document
*
* @return the parent document
*/
public IDocument getParentDocument() {
return fParentDocument;
}
/**
* Returns the range of the parent document covered by this child document.
*
* @return the child document's parent document range
*/
public Position getParentDocumentRange() {
return fRange;
}
/**
* <p>Transforms a document event of the parent document into a child document
* based document event. This method considers whether the document is in
* auto expand mode.</p>
* This method is public for test purposes only.
*
* @param e the parent document event
* @return the child document event
*/
public DocumentEvent normalize(DocumentEvent event) {
int delta= event.getOffset() - fRange.getOffset();
if (isAutoExpandEvent(event)) {
if (delta < 0) {
int eventEndOffset= event.getOffset() + event.getLength();
if (eventEndOffset <= fRange.getOffset()) {
// case 1
StringBuffer buffer= new StringBuffer();
if (event.getText() != null)
buffer.append(event.getText());
try {
buffer.append(fParentDocument.get(eventEndOffset, -delta - event.getLength()));
} catch (BadLocationException e) {
// should not happen as the event is a valid parent document event
}
return new SlaveDocumentEvent(this, 0, 0, buffer.toString(), event);
} else {
// cases 2 and 3
int length= Math.min(eventEndOffset - fRange.getOffset(), fRange.getLength());
return new SlaveDocumentEvent(this, 0, length, event.getText(), event);
}
} else {
int rangeEndOffset= fRange.getOffset() + fRange.getLength();
if (event.getOffset() >= rangeEndOffset) {
// case 5
StringBuffer buffer= new StringBuffer();
try {
buffer.append(fParentDocument.get(rangeEndOffset, event.getOffset() - rangeEndOffset));
} catch (BadLocationException e) {
// should not happen as this event is a valid parent document event
}
if (event.getText() != null)
buffer.append(event.getText());
return new SlaveDocumentEvent(this, fRange.getLength(), 0, buffer.toString(), event);
} else {
// case 4 and 6
int offset= event.getOffset() - fRange.getOffset();
int length= Math.min(rangeEndOffset - event.getOffset(), event.getLength());
return new SlaveDocumentEvent(this, offset, length, event.getText(), event);
}
}
} else if (fRange.overlapsWith(event.fOffset, event.fLength)) {
int offset= delta < 0 ? 0 : delta;
int length= delta < 0 ? event.fLength + delta : event.fLength;
if (offset + length > fRange.getLength())
length= fRange.getLength() - offset;
return new SlaveDocumentEvent(this, offset, length, event.fText, event);
}
return null;
}
/**
* When called this child document is informed about a forthcoming change
* of its parent document. This child document checks whether the parent
* document change affects it and if so informs all document listeners.
*
* @param event the parent document event
*/
public void parentDocumentAboutToBeChanged(DocumentEvent event) {
fParentEvent= event;
fEvent= normalize(event);
if (fEvent != null) {
StringBuffer buffer= new StringBuffer(get());
fRememberedLength= buffer.length();
buffer.replace(fEvent.fOffset, fEvent.fOffset+ fEvent.fLength, fEvent.fText == null ? "" : fEvent.fText); //$NON-NLS-1$
fExpectedContent= buffer.toString();
delayedFireDocumentAboutToBeChanged();
}
}
/**
* When called this child document is informed about a change of its parent document.
* If this child document is affected it informs all of its document listeners.
*
* @param event the parent document event
*/
public void parentDocumentChanged(DocumentEvent event) {
if ( !fIsUpdating && event == fParentEvent && fEvent != null) {
try {
if (!fExpectedContent.equals(get())) {
// patch the event
fEvent.fOffset= 0;
fEvent.fLength= fRememberedLength;
fEvent.fText= get();
}
fRememberedLength= 0;
fExpectedContent= null;
getTracker().replace(fEvent.fOffset, fEvent.fLength, fEvent.fText);
fireDocumentChanged(fEvent);
} catch (BadLocationException x) {
Assert.isLegal(false);
}
}
}
/*
* @see AbstractDocument#fireDocumentAboutToBeChanged(DocumentEvent)
*/
protected void fireDocumentAboutToBeChanged(DocumentEvent event) {
// delay it until there is a notification from the parent document
// otherwise there it is expensive to construct the parent document information
}
/**
* Fires the child document event as about-to-be-changed event to all
* registed listeners.
*/
private void delayedFireDocumentAboutToBeChanged() {
super.fireDocumentAboutToBeChanged(fEvent);
}
/**
* Ignores the given event and sends the similar child document event instead.
*
* @param event the event to be ignored
*/
protected void fireDocumentChanged(DocumentEvent event) {
super.fireDocumentChanged(fEvent);
}
/*
* @see IDocument#replace(int, int, String)
* @since 2.0
*/
public void replace(int offset, int length, String text) throws BadLocationException {
try {
fIsUpdating= true;
if (fExtension != null)
fExtension.stopPostNotificationProcessing();
if ((0 > offset) || (0 > length) || (offset + length > getLength()))
throw new BadLocationException();
DocumentEvent event= new DocumentEvent(this, offset, length, text);
fireDocumentAboutToBeChanged(event);
try {
if (fExtension2 != null)
fExtension2.stopListenerNotification();
getStore().replace(offset, length, text);
getTracker().replace(offset, length, text);
updateDocumentStructures(fEvent);
} finally {
if (fExtension2 != null)
fExtension2.resumeListenerNotification();
}
doFireDocumentChanged(fEvent);
} finally {
fIsUpdating= false;
if (fExtension != null)
fExtension.resumePostNotificationProcessing();
}
}
/*
* @see IDocument#set(String)
* @since 2.0
*/
public void set(String text) {
try {
fIsUpdating= true;
if (fExtension != null)
fExtension.stopPostNotificationProcessing();
int length= getStore().getLength();
DocumentEvent event= new DocumentEvent(this, 0, length, text);
fireDocumentAboutToBeChanged(event);
try {
if (fExtension2 != null)
fExtension2.stopListenerNotification();
getStore().set(text);
getTracker().set(text);
updateDocumentStructures(fEvent);
} finally {
if (fExtension2 != null)
fExtension2.resumeListenerNotification();
}
doFireDocumentChanged(fEvent);
} finally {
fIsUpdating= false;
if (fExtension != null)
fExtension.resumePostNotificationProcessing();
}
}
/*
* @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace)
* @since 2.0
*/
public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) {
if (!fIsUpdating)
throw new UnsupportedOperationException();
super.registerPostNotificationReplace(owner, replace);
}
/**
* Sets the auto expand mode of this document.
*
* @param autoExpand <code>true</code> if auto expanding, <code>false</code> otherwise
* @since 2.1
*/
public void setAutoExpandMode(boolean autoExpand) {
fIsAutoExpanding= autoExpand;
}
/**
* Returns whether the given document might cause this document to auto expand.
* This default implementation always answers with its auto expand state.
*
* @param event the document event
* @return <code>true</code> if the given event might cause this document to auto expand
*/
public boolean isAutoExpandEvent(DocumentEvent event) {
return fIsAutoExpanding;
}
}