blob: 5e25eeb350221019b5d425699a94a8ad4dd40667 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2000, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
# IBM Corporation - org.eclipse.jface.text: initial API and implementation of FastPartitioner
#=============================================================================*/
// org.eclipse.jface.text.rules.FastPartitioner
package org.eclipse.statet.ecommons.text.core.treepartitioner;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.DocumentPartitioningChangedEvent;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IDocumentPartitionerExtension;
import org.eclipse.jface.text.IDocumentPartitionerExtension2;
import org.eclipse.jface.text.IDocumentPartitionerExtension3;
import org.eclipse.jface.text.IDocumentPartitioningListenerExtension2;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.text.core.DocumentEnhancement;
import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNodeScan.BreakException;
/**
* A implementation of a document partitioner using a tree as data structure.
* <p>It uses an {@link TreePartitionNodeScanner} to scan the document and to determine the
* document's partitioning.
* The partitioner maintains the document's partitions in a tree of TreePartitionNode backed by
* positions in the document itself.
* </p>
* @see TreePartitionNodeScanner
*/
@NonNullByDefault
public class TreePartitioner implements IDocumentPartitioner,
IDocumentPartitionerExtension, IDocumentPartitionerExtension2, IDocumentPartitionerExtension3 {
static final boolean DEBUG= Boolean.parseBoolean(
Platform.getDebugOption("org.eclipse.statet.ecommons.text.core/TreePartitioner")); //$NON-NLS-1$
static final boolean DEBUG_PRINT= Boolean.parseBoolean(
Platform.getDebugOption("org.eclipse.statet.ecommons.text.core/TreePartitioner/printTree") ); //$NON-NLS-1$
private final class RootNode extends NodePosition {
public RootNode(final TreePartitionNodeType type) {
super(null, 0, Integer.MAX_VALUE, type, 0);
}
@Override
public void setOffset(final int offset) {
}
@Override
public int getStartOffset() {
return 0;
}
@Override
public int getEndOffset() {
return TreePartitioner.this.document.getLength();
}
@Override
public void setLength(final int length) {
}
@Override
public int getLength() {
return TreePartitioner.this.document.getLength();
}
}
private final class RewriteSessionUpdater implements IPositionUpdater {
private int startOffset= -1;
private int endOffset= -1;
public RewriteSessionUpdater() {
}
@Override
public void update(final DocumentEvent event) {
if (TreePartitioner.this.activeRewriteSession == null) {
return;
}
TreePartitioner.this.positionUpdater.update(event);
final int eventOffset= event.getOffset();
final int eventOldEndOffset= eventOffset + event.getLength();
final int eventNewLength= event.getText() == null ? 0 : event.getText().length();
final int eventNewEndOffset= eventOffset + eventNewLength;
if (this.startOffset < 0) {
this.startOffset= eventOffset;
this.endOffset= eventNewEndOffset;
}
else {
// update begin
if (this.startOffset > eventOffset) {
this.startOffset= eventOffset;
}
// update end
if (this.endOffset > eventOldEndOffset) {
this.endOffset+= eventNewLength - event.getLength();
}
else {
this.endOffset= eventNewEndOffset;
}
}
}
}
/**
* The position category this partitioner uses to store the document's partitioning information.
*/
private static final String CONTENT_TYPES_CATEGORY= "org.eclipse.statet.ecommons.text.TreePartitionerNodes"; //$NON-NLS-1$
private final String partitioningId;
/** The partitioner's scanner */
protected final TreePartitionNodeScanner scanner;
/** The legal content types of this partitioner */
protected final ImList<String> legalContentTypes;
/**
* Flag indicating whether this partitioner has been initialized.
*/
private boolean isInitialized= false;
/** The partitioner's document */
protected IDocument document;
private @Nullable DocumentEnhancement documentEnh;
/** The document length before a document change occurred */
protected int previousDocumentLength;
/** The position updater used to for the default updating of partitions */
protected final DefaultPositionUpdater positionUpdater;
/**
* The position category this partitioner uses to store the document's partitioning information.
*/
private final String positionCategory;
private final NodePosition rootPosition;
private TreePartitionNodeType startType;
private TreePartitionerScan scan;
/**
* The active document rewrite session.
*/
private @Nullable DocumentRewriteSession activeRewriteSession;
/**
* Tracks changes during small document rewrite session.
*/
private @Nullable RewriteSessionUpdater activeRewriteUpdater;
private @Nullable IDocumentPartitioningListenerExtension2 partitioningListener;
private @Nullable IRegion partitioningChangeRegion;
/**
* Creates a new partitioner that uses the given scanner and may return
* partitions of the given legal content types.
*
* @param id the id of the partitioning or <code>null</code>
* @param scanner the scanner this partitioner is supposed to use
* @param legalContentTypes the legal content types of this partitioner
*/
public TreePartitioner(final String partitioningId,
final TreePartitionNodeScanner scanner, final List<String> legalContentTypes) {
if (partitioningId == null) {
throw new NullPointerException("partitioningId"); //$NON-NLS-1$
}
if (scanner == null) {
throw new NullPointerException("scanner"); //$NON-NLS-1$
}
if (legalContentTypes == null) {
throw new NullPointerException("legalContentTypes"); //$NON-NLS-1$
}
this.partitioningId= partitioningId;
this.scanner= scanner;
this.legalContentTypes= ImCollections.toIdentityList(legalContentTypes);
this.positionCategory= CONTENT_TYPES_CATEGORY + ':' + this.partitioningId;
this.positionUpdater= new DefaultPositionUpdater(this.positionCategory);
this.rootPosition= new RootNode(scanner.getDefaultRootType());
}
protected final String getPartitioningId() {
return this.partitioningId;
}
@Override
public String[] getManagingPositionCategories() {
return new String[] { this.positionCategory };
}
public void setStartType(final TreePartitionNodeType type) {
this.startType= type;
clear();
}
@Override
public final void connect(final IDocument document) {
connect(document, false);
}
/**
* {@inheritDoc}
* <p>
* May be extended by subclasses.
* </p>
*/
@Override
public void connect(final IDocument document, final boolean delayInitialization) {
Assert.isNotNull(document);
Assert.isTrue(!document.containsPositionCategory(this.positionCategory));
this.document= document;
this.document.addPositionCategory(this.positionCategory);
this.documentEnh= DocumentEnhancement.get(this.document);
this.isInitialized= false;
if (!delayInitialization) {
checkInitialization();
}
}
/**
* Calls {@link #initialize()} if the receiver is not yet initialized.
*/
protected final void checkInitialization() {
if (!this.isInitialized) {
initialize();
}
}
protected void clear() {
// remove all position belonging to the partitioner position category
try {
this.rootPosition.children.clear();
this.document.removePositionCategory(this.positionCategory);
} catch (final BadPositionCategoryException x) {
}
this.document.addPositionCategory(this.positionCategory);
this.isInitialized= false;
}
/**
* Performs the initial partitioning of the partitioner's document.
* <p>
* May be extended by subclasses.
* </p>
*/
protected void initialize() {
this.isInitialized= true;
this.partitioningChangeRegion= null;
final DocumentEnhancement documentEnh= this.documentEnh;
if (documentEnh != null) {
IDocumentPartitioningListenerExtension2 partitioningListener= this.partitioningListener;
if (partitioningListener == null) {
partitioningListener= new IDocumentPartitioningListenerExtension2() {
@Override
public void documentPartitioningChanged(final DocumentPartitioningChangedEvent event) {
updatePartitioningChange(event);
}
};
documentEnh.addPrePartitioningListener(partitioningListener);
this.partitioningListener= partitioningListener;
}
}
if (this.scan == null) {
this.scan= new TreePartitionerScan(this);
}
this.scan.init(0, this.document.getLength(), this.rootPosition);
this.scan.markDirtyRegion(0, Integer.MAX_VALUE);
if (this.startType != null) {
this.scan.addBeginPosition(this.startType);
}
try {
this.scanner.execute(this.scan);
}
catch (final BreakException b) {
}
finally {
if (DEBUG) {
check(DEBUG_PRINT);
}
}
}
/**
* {@inheritDoc}
* <p>
* May be extended by subclasses.
* </p>
*/
@Override
public void disconnect() {
Assert.isTrue(this.document.containsPositionCategory(this.positionCategory));
try {
this.document.removePositionCategory(this.positionCategory);
}
catch (final BadPositionCategoryException x) {
// can not happen because of Assert
}
if (this.activeRewriteSession != null) {
final RewriteSessionUpdater rewriteUpdater= this.activeRewriteUpdater;
if (rewriteUpdater != null) {
this.activeRewriteUpdater= null;
this.document.removePositionUpdater(rewriteUpdater);
}
this.activeRewriteSession= null;
}
final DocumentEnhancement documentEnh= this.documentEnh;
if (documentEnh != null) {
this.documentEnh= null;
final IDocumentPartitioningListenerExtension2 partitioningListener= this.partitioningListener;
if (partitioningListener != null) {
this.partitioningListener= null;
documentEnh.removePrePartitioningListener(partitioningListener);
}
}
}
/**
* {@inheritDoc}
* <p>
* May be extended by subclasses.
* </p>
*/
@Override
public void documentAboutToBeChanged(final DocumentEvent e) {
if (this.isInitialized) {
Assert.isTrue(e.getDocument() == this.document);
this.previousDocumentLength= e.getDocument().getLength();
}
}
@Override
public final boolean documentChanged(final DocumentEvent e) {
if (this.isInitialized) {
final IRegion region= documentChanged2(e);
return (region != null);
}
return false;
}
/**
* {@inheritDoc}
* <p>
* May be extended by subclasses.
* </p>
*/
@Override
public @Nullable IRegion documentChanged2(final DocumentEvent event) {
if (!this.isInitialized) {
return null;
}
Assert.isTrue(event.getDocument() == this.document);
this.positionUpdater.update(event);
return updatePartitioning(event.getOffset(), event.getOffset() +
((event.getText() != null) ? event.getText().length() : 0) );
}
protected final @Nullable IRegion updatePartitioning(int startOffset, final int endOffset) {
try {
this.partitioningChangeRegion= null;
final int lineOfOffset= this.document.getLineOfOffset(startOffset);
// line start of previous line
/* This adds support of two-line partition separators.
* An alternative would be to implement it (depend on the position) in
* scanner.getRestartOffset(...) */
startOffset= this.document.getLineOffset((lineOfOffset > 0) ? lineOfOffset - 1 : 0);
NodePosition beginPosition= null;
if (startOffset > 0) {
beginPosition= findPositionPreferOpen(startOffset);
final int offset= this.scanner.getRestartOffset(beginPosition, this.document, startOffset);
if (startOffset != offset) {
startOffset= offset;
if (startOffset != 0) {
beginPosition= findPositionPreferOpen(startOffset);
}
}
}
if (startOffset == 0) {
beginPosition= this.rootPosition;
}
this.scan.init(startOffset, this.document.getLength(), beginPosition);
this.scan.markDirtyStart(startOffset);
this.scan.markDirtyEnd(endOffset);
if (startOffset == 0 && beginPosition == this.rootPosition && this.startType != null) {
this.scan.addBeginPosition(this.startType);
}
try {
this.scanner.execute(this.scan);
}
catch (final BreakException b) {
}
finally {
if (DEBUG) {
check(DEBUG_PRINT);
}
}
return this.scan.createDirtyRegion();
}
catch (final BadLocationException e) {
e.printStackTrace();
clear();
return new Region(0, this.document.getLength());
}
}
final void addPosition(final NodePosition position) throws BadLocationException, BadPositionCategoryException {
this.document.addPosition(this.positionCategory, position);
}
final void removePosition(final NodePosition position) throws BadPositionCategoryException {
this.document.removePosition(this.positionCategory, position);
}
final NodePosition findPosition(final int offset) {
NodePosition p= this.rootPosition;
while (true) {
assert (p.includes(offset));
final List<NodePosition> children= p.children;
final int childCount= children.size();
if (childCount > 0) {
final int idx= NodePosition.indexOf(children, offset);
if (idx >= 0) {
p= children.get(idx);
if (p.getLength() == 0) {
if (idx + 1 < children.size()) {
p= children.get(idx + 1);
if (p.includes(offset)) {
continue;
}
}
return p.parent;
}
continue;
}
}
return p;
}
}
@Override
public String getContentType(final int offset) {
checkInitialization();
final NodePosition position= findPosition(offset);
return position.type.getPartitionType();
}
private TreePartition createPartition(final NodePosition p, final int childIdx) {
final int startOffset;
final int endOffset;
if (childIdx > 0) {
startOffset= p.children.get(childIdx - 1).getEndOffset();
}
else {
startOffset= p.getOffset();
}
if (childIdx >= 0 && childIdx < p.children.size()) {
endOffset= p.children.get(childIdx).getOffset();
}
else {
endOffset= p.getEndOffset();
}
return new TreePartition(startOffset, endOffset, p);
}
@Override
public TreePartition getPartition(final int offset) {
checkInitialization();
NodePosition p= this.rootPosition;
while (true) {
assert (p.includes(offset));
final List<NodePosition> children= p.children;
final int childCount= children.size();
if (childCount > 0) {
final int idx= NodePosition.indexOf(children, offset);
if (idx >= 0) {
p= p.children.get(idx);
if (p.getLength() == 0) {
if (idx + 1 < children.size()) {
p= children.get(idx + 1);
if (p.includes(offset)) {
continue;
}
}
return createPartition(p.parent, idx);
}
continue;
}
return createPartition(p, -(idx + 1));
}
return createPartition(p, -1);
}
}
/**
* Recursively adds partitions to the given list.
*
* @param partitions the list of partitions
* @param p the position to add
* @param startOffset the min offset
* @param endOffset the max offset
* @return the end offset of the last added partition
*/
private int addPartition(final List<TreePartition> partitions, final NodePosition p,
int startOffset, int endOffset) {
startOffset= (Math.max(startOffset, p.getOffset()));
endOffset= (Math.min(endOffset, p.getEndOffset()));
final List<NodePosition> children= p.children;
int childIdx= 0;
final int childCount= children.size();
if (childCount > 0) {
if (p.getOffset() < startOffset) {
childIdx= NodePosition.indexOf(children, startOffset);
if (childIdx < 0) {
childIdx= -(childIdx + 1);
}
}
for (; childIdx < childCount; childIdx++) {
final NodePosition child= children.get(childIdx);
if (child.getOffset() > endOffset) {
break;
}
if (startOffset < child.getOffset()) {
partitions.add(new TreePartition(startOffset, child.getOffset(), p));
}
startOffset= addPartition(partitions, child, startOffset, endOffset);
}
}
if (startOffset < endOffset) {
partitions.add(new TreePartition(startOffset, endOffset, p));
}
return endOffset;
}
@Override
public final TreePartition[] computePartitioning(final int offset, final int length) {
checkInitialization();
final List<TreePartition> partitions= new ArrayList<>();
try {
addPartition(partitions, this.rootPosition, offset, offset + length);
}
catch (final RuntimeException ex) {
clear();
throw ex;
}
return partitions.toArray(new @NonNull TreePartition[partitions.size()]);
}
/**
* {@inheritDoc}
* <p>
* May be replaced or extended by subclasses.
* </p>
*/
@Override
public String[] getLegalContentTypes() {
return this.legalContentTypes.toArray(new @NonNull String[this.legalContentTypes.size()]);
}
/**
* Returns whether the given type is one of the legal content types.
*
* @param contentType the content type to check
* @return <code>true</code> if the content type is a legal content type
*/
protected final boolean isSupportedPartitionType(final String contentType) {
return (contentType != null && this.legalContentTypes.contains(contentType));
}
/* zero-length partition support */
final NodePosition findPositionPreferOpen(final int offset) {
NodePosition p= this.rootPosition;
while (true) {
assert (p.includes(offset) || p.getEndOffset() == offset);
final List<NodePosition> children= p.children;
int idx= -1;
final int childCount= children.size();
if (childCount > 0) {
idx= NodePosition.indexOf(children, offset);
if (idx >= 0) {
final NodePosition child= children.get(idx);
if (child.getOffset() < offset
|| (/*child.getOffset() == offset &&*/ child.type.prefereAtBegin(child, this.document)) ) {
p= child;
continue;
}
}
else {
idx= -(idx + 1);
}
if (idx > 0) {
final NodePosition child= children.get(idx - 1);
if (child.getEndOffset() == offset && child.type.prefereAtEnd(child, this.document)) {
p= child;
continue;
}
}
}
return p;
}
}
private TreePartition getPartitionPreferOpen(final int offset) {
checkInitialization();
NodePosition p= this.rootPosition;
while (true) {
assert (p.includes(offset) || p.getEndOffset() == offset);
final List<NodePosition> children= p.children;
int idx= -1;
final int childCount= children.size();
if (childCount > 0) {
idx= NodePosition.indexOf(children, offset);
if (idx >= 0) {
final NodePosition child= p.children.get(idx);
if (child.getOffset() < offset
|| (/*child.getOffset() == offset &&*/ child.type.prefereAtBegin(child, this.document)) ) {
p= child;
continue;
}
}
else {
idx= -(idx + 1);
}
if (idx > 0) {
final NodePosition child= p.children.get(idx - 1);
if (child.getEndOffset() == offset && child.type.prefereAtEnd(child, this.document)) {
p= child;
continue;
}
}
}
return createPartition(p, idx);
}
}
@Override
public String getContentType(final int offset, final boolean preferOpenPartitions) {
checkInitialization();
final NodePosition position= (preferOpenPartitions) ?
findPositionPreferOpen(offset) :
findPosition(offset);
return position.type.getPartitionType();
}
public TreePartitionNode getTreeNode(final int offset, final boolean preferOpenPartitions) {
checkInitialization();
return (preferOpenPartitions) ?
findPositionPreferOpen(offset) :
findPosition(offset);
}
@Override
public TreePartition getPartition(final int offset, final boolean preferOpenPartitions) {
return (preferOpenPartitions) ?
getPartitionPreferOpen(offset) :
getPartition(offset);
}
/**
* Recursively adds partitions to the given list.
*
* @param partitions the list of partitions
* @param p the position to add
* @param startOffset the min offset
* @param endOffset the max offset
* @return the end offset of the last added partition
*/
private int addPartitionIncludeZeroLength(final List<TreePartition> partitions,
final NodePosition p, int startOffset, int endOffset) {
startOffset= (Math.max(startOffset, p.getOffset()));
endOffset= (Math.min(endOffset, p.getEndOffset()));
final List<NodePosition> children= p.children;
int childIdx= 0;
final int childCount= children.size();
if (childCount > 0) {
if (p.getOffset() < startOffset) {
childIdx= NodePosition.indexOf(children, startOffset);
if (childIdx < 0) {
childIdx= -(childIdx + 1);
}
}
for (; childIdx < childCount; childIdx++) {
final NodePosition child= children.get(childIdx);
if (child.getOffset() > endOffset) {
break;
}
if (startOffset <= child.getOffset()) {
partitions.add(new TreePartition(startOffset, child.getOffset(), p));
}
startOffset= addPartitionIncludeZeroLength(partitions, child, startOffset, endOffset);
}
}
if (startOffset < endOffset || (startOffset == endOffset
&& (--childIdx < 0 || startOffset == children.get(childIdx).getEndOffset()) )) {
partitions.add(new TreePartition(startOffset, endOffset, p));
}
return endOffset;
}
private TreePartition[] computePartitioningIncludeZeroLength(final int offset, final int length) {
checkInitialization();
final List<TreePartition> partitions= new ArrayList<>();
try {
addPartitionIncludeZeroLength(partitions, this.rootPosition, offset, offset + length);
}
catch (final RuntimeException ex) {
// Make sure we clear the cache
clear();
throw ex;
}
return partitions.toArray(new @NonNull TreePartition[partitions.size()]);
}
@Override
public TreePartition[] computePartitioning(final int offset, final int length,
final boolean includeZeroLengthPartitions) {
return (includeZeroLengthPartitions) ?
computePartitioningIncludeZeroLength(offset, length) :
computePartitioning(offset, length);
}
@Override
public void startRewriteSession(final DocumentRewriteSession session) throws IllegalStateException {
if (this.activeRewriteSession != null) {
throw new IllegalStateException();
}
this.activeRewriteSession= session;
if (this.isInitialized
&& session.getSessionType() == DocumentRewriteSessionType.UNRESTRICTED_SMALL) {
final RewriteSessionUpdater rewriteUpdater= new RewriteSessionUpdater();
this.document.addPositionUpdater(rewriteUpdater);
this.activeRewriteUpdater= rewriteUpdater;
}
}
/**
* {@inheritDoc}
* <p>
* May be extended by subclasses.
* </p>
*/
@Override
public void stopRewriteSession(final DocumentRewriteSession session) {
if (this.activeRewriteSession == session) {
flushRewriteSession();
}
}
/**
* {@inheritDoc}
* <p>
* May be extended by subclasses.
* </p>
*/
@Override
public @Nullable DocumentRewriteSession getActiveRewriteSession() {
return this.activeRewriteSession;
}
/**
* Flushes the active rewrite session.
*/
protected final void flushRewriteSession() {
this.activeRewriteSession= null;
final RewriteSessionUpdater rewriteUpdater= this.activeRewriteUpdater;
if (rewriteUpdater != null) {
this.activeRewriteUpdater= null;
this.document.removePositionUpdater(rewriteUpdater);
if (this.isInitialized) {
this.partitioningChangeRegion= (rewriteUpdater.startOffset >= 0) ?
updatePartitioning(rewriteUpdater.startOffset, rewriteUpdater.endOffset) :
new Region(0, 0);
}
return;
}
clear();
}
private void updatePartitioningChange(final DocumentPartitioningChangedEvent event) {
final IRegion changeRegion= this.partitioningChangeRegion;
if (changeRegion != null && event.getChangedRegion(this.partitioningId) != null) {
event.setPartitionChange(this.partitioningId,
changeRegion.getOffset(), changeRegion.getLength() );
this.partitioningChangeRegion= null;
}
}
final void check(final boolean printInfo) {
try {
validateChildren(this.rootPosition);
if (printInfo) {
System.out.println("[TreePartitioner] INFO :"); //$NON-NLS-1$
System.out.println(toString());
}
}
catch (final Error e) {
synchronized (System.err) {
System.err.println("[TreePartitioner] ERROR Error in document partitioning:"); //$NON-NLS-1$
System.err.println(toString());
e.printStackTrace(System.err);
System.err.println("==== document content:\n" + this.document.get() + "\n===="); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
private void validateChildren(final NodePosition parent) {
int offset= parent.getOffset();
final int childCount= parent.children.size();
for (int childIdx= 0; childIdx < childCount; childIdx++) {
final NodePosition child= parent.children.get(childIdx);
if (child.parent != parent) {
throw new AssertionError("position.parent");
}
if (child.isDeleted()) {
throw new AssertionError("position.isDeleted");
}
if (child.getOffset() < offset || child.getOffset() > parent.getEndOffset()) {
throw new AssertionError("position.offset");
}
if (child.getLength() < 0 || child.getEndOffset() > parent.getEndOffset()) {
throw new AssertionError("position.length");
}
offset= child.getEndOffset();
validateChildren(child);
}
}
@Override
public String toString() {
final StringWriter writer= new StringWriter();
writer.append("TreePartitioner (scanner= " + this.scanner.getClass().getCanonicalName() + "):\n"); //$NON-NLS-1$ //$NON-NLS-2$
if (!this.isInitialized) {
writer.append("<no initialized>"); //$NON-NLS-1$
}
else {
final TreePartitionUtils.PartitionPrinter printer= new TreePartitionUtils.PartitionPrinter(writer);
try {
printer.print(this.rootPosition, this.document);
writer.append("====");
}
catch (final IOException e) {}
}
return writer.toString();
}
}