blob: 16da77c138dc6015171c5a40a7191c368c45d559 [file] [log] [blame]
package org.eclipse.jface.text;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import org.eclipse.jface.util.Assert;
/**
* Manages a set of child documents for given parent documents.
* A child document represents a particular range of the parent
* document and is accordingly adapted to changes of the parent document.
* Vice versa, the parent document is accordingly adapted to changes of
* its child documents. The manager does not maintain any particular management
* structure but utilizes mechanisms given by <code>IDocument</code> such
* as position categories and position updaters. <p>
*
* For internal use only.
*/
public final class ChildDocumentManager implements IDocumentListener {
/**
* Name of the position categories used to keep track of the child
* documents offset ranges into the parent document.
*/
public final static String CHILDDOCUMENTS= "__childdocuments"; //$NON-NLS-1$
/**
* Positions which are used to mark the child documents offset ranges into
* the parent documents. This position uses as bidirectional reference as
* it knows the child document as well as the parent document.
*/
static class ChildPosition extends Position {
public IDocument fParentDocument;
public ChildDocument fChildDocument;
public ChildPosition(IDocument parentDocument, int offset, int length) {
super(offset, length);
fParentDocument= parentDocument;
}
/**
* Changed to be compatible to the position updater behavior
* @see Position#overlapsWith(int, int)
*/
public boolean overlapsWith(int offset, int length) {
boolean append= (offset == this.offset + this.length) && length == 0;
return append || super.overlapsWith(offset, length);
}
};
/**
* The position updater used to adapt the positions representing
* the child document ranges to changes of the parent document.
*/
static class ChildPositionUpdater extends DefaultPositionUpdater {
/**
* Creates the position updated.
*/
protected ChildPositionUpdater() {
super(CHILDDOCUMENTS);
}
/**
* Child document ranges cannot be deleted other then by calling
* freeChildDocument.
*/
protected boolean notDeleted() {
return true;
}
/**
* If an insertion happens at a child document's start offset, the
* position is extended rather than shifted. Also, if something is added
* right behind the end of the position, the position is extended rather
* than kept stable.
*/
protected void adaptToInsert() {
int myStart= fPosition.offset;
int myEnd= fPosition.offset + fPosition.length;
myEnd= Math.max(myStart, myEnd);
int yoursStart= fOffset;
int yoursEnd= fOffset + fReplaceLength -1;
yoursEnd= Math.max(yoursStart, yoursEnd);
if (myEnd < yoursStart)
return;
if (myStart <= yoursStart)
fPosition.length += fReplaceLength;
else
fPosition.offset += fReplaceLength;
}
};
/**
* The child document partitioner uses the parent document to answer all questions.
*/
static class ChildPartitioner implements IDocumentPartitioner {
protected ChildDocument fChildDocument;
protected IDocument fParentDocument;
protected ChildPartitioner() {
}
/*
* @see IDocumentPartitioner#getPartition(int)
*/
public ITypedRegion getPartition(int offset) {
try {
offset += fChildDocument.getParentDocumentRange().getOffset();
return fParentDocument.getPartition(offset);
} catch (BadLocationException x) {
}
return null;
}
/*
* @see IDocumentPartitioner#computePartitioning(int, int)
*/
public ITypedRegion[] computePartitioning(int offset, int length) {
try {
offset += fChildDocument.getParentDocumentRange().getOffset();
return fParentDocument.computePartitioning(offset, length);
} catch (BadLocationException x) {
}
return null;
}
/*
* @see IDocumentPartitioner#getContentType(int)
*/
public String getContentType(int offset) {
try {
offset += fChildDocument.getParentDocumentRange().getOffset();
return fParentDocument.getContentType(offset);
} catch (BadLocationException x) {
}
return null;
}
/*
* @see IDocumentPartitioner#getLegalContentTypes()
*/
public String[] getLegalContentTypes() {
return fParentDocument.getLegalContentTypes();
}
/*
* @see IDocumentPartitioner#documentChanged(DocumentEvent)
*/
public boolean documentChanged(DocumentEvent event) {
// ignore as the parent does this for us
return false;
}
/*
* @see IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
// ignore as the parent does this for us
}
/*
* @see IDocumentPartitioner#disconnect()
*/
public void disconnect() {
fChildDocument= null;
fParentDocument= null;
}
/*
* @see IDocumentPartitioner#connect(IDocument)
*/
public void connect(IDocument childDocument) {
Assert.isTrue(childDocument instanceof ChildDocument);
fChildDocument= (ChildDocument) childDocument;
fParentDocument= fChildDocument.getParentDocument();
}
};
/** The position updater shared by all documents which have child documents */
private IPositionUpdater fChildPositionUpdater;
/**
* Returns the child position updater. If necessary, it is dynamically created.
*
* @return the child position updater
*/
protected IPositionUpdater getChildPositionUpdater() {
if (fChildPositionUpdater == null)
fChildPositionUpdater= new ChildPositionUpdater();
return fChildPositionUpdater;
}
/**
* Creates and returns a new child document for the specified range of the given parent document.
* The created child document is initialized with a child document partitioner.
*
* @param parent the parent document
* @param offset the offset of the parent document range
* @param length the length of the parent document range
* @exception BadLocationException if the specified range is invalid in the parent document
*/
public ChildDocument createChildDocument(IDocument parent, int offset, int length) throws BadLocationException {
if (!parent.containsPositionCategory(CHILDDOCUMENTS)) {
parent.addPositionCategory(CHILDDOCUMENTS);
parent.addPositionUpdater(getChildPositionUpdater());
parent.addDocumentListener(this);
}
ChildPosition pos= new ChildPosition(parent, offset, length);
try {
parent.addPosition(CHILDDOCUMENTS, pos);
} catch (BadPositionCategoryException x) {
// cannot happen
}
ChildDocument child= new ChildDocument(parent, pos);
IDocumentPartitioner partitioner= new ChildPartitioner();
child.setDocumentPartitioner(partitioner);
partitioner.connect(child);
pos.fChildDocument= child;
return child;
}
/**
* Disconnects the given child document from it's parent document and frees
* all resources which are no longer needed.
*
* @param childDocument the child document to be freed
*/
public void freeChildDocument(ChildDocument childDocument) {
childDocument.getDocumentPartitioner().disconnect();
ChildPosition pos= (ChildPosition) childDocument.getParentDocumentRange();
IDocument parent= pos.fParentDocument;
try {
parent.removePosition(CHILDDOCUMENTS, pos);
Position[] category= parent.getPositions(CHILDDOCUMENTS);
if (category.length == 0) {
parent.removeDocumentListener(this);
parent.removePositionUpdater(getChildPositionUpdater());
parent.removePositionCategory(CHILDDOCUMENTS);
}
} catch (BadPositionCategoryException x) {
// cannot happen
}
}
/**
* Informs all child documents of the document which issued this document event.
*
* @param about indicates whether the change is about to happen or alread happend
* @param event the document event which will be processed to inform child documents
*/
protected void fireDocumentEvent(boolean about, DocumentEvent event) {
try {
IDocument parent= event.getDocument();
Position[] children= parent.getPositions(CHILDDOCUMENTS);
for (int i= 0; i < children.length; i++) {
Object o= children[i];
if (o instanceof ChildPosition) {
ChildPosition pos= (ChildPosition) o;
if (about)
pos.fChildDocument.parentDocumentAboutToBeChanged(event);
else
pos.fChildDocument.parentDocumentChanged(event);
}
}
} catch (BadPositionCategoryException x) {
// cannot happen
}
}
/*
* @see IDocumentListener#documentChanged(DocumentEvent)
*/
public void documentChanged(DocumentEvent event) {
fireDocumentEvent(false, event);
}
/*
* @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
fireDocumentEvent(true, event);
}
}