blob: c4f10f1e14cf49a5cb9ebd768c38be191feed7b2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2004 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.ui.internal.reconcile;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcileResult;
import org.eclipse.jface.text.reconciler.IReconcileStep;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.wst.sse.core.IModelManagerPlugin;
import org.eclipse.wst.sse.core.IStructuredModel;
import org.eclipse.wst.sse.core.IndexedRegion;
import org.eclipse.wst.sse.core.text.IStructuredDocument;
import org.eclipse.wst.sse.core.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.util.Assert;
import org.eclipse.wst.sse.ui.IReleasable;
import org.eclipse.wst.sse.ui.ITemporaryAnnotation;
import org.eclipse.wst.sse.ui.Logger;
import org.eclipse.wst.sse.ui.StructuredTextReconciler;
/**
* A base ReconcilingStrategy. Subclasses must implement
* createReconcileSteps().
*
* @author pavery
*/
public abstract class AbstractStructuredTextReconcilingStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension, IReleasable, IStructuredReconcilingStrategy {
protected boolean fAlreadyRemovedAllThisRun = false;
protected IDocument fDocument = null;
protected IReconcileStep fFirstStep = null;
protected IProgressMonitor fProgressMonitor = null;
protected ITextEditor fTextEditor = null;
/**
* Creates a new strategy. The editor parameter is for access to the
* annotation model.
*
* @param editor
*/
public AbstractStructuredTextReconcilingStrategy(ITextEditor editor) {
fTextEditor = editor;
init();
}
/**
* This is where we add results to the annotationModel, doing any special
* "extra" processing.
*/
protected void addResultToAnnotationModel(IReconcileResult result) {
if (!(result instanceof TemporaryAnnotation))
return;
// can be null when closing the editor
if (getAnnotationModel() != null) {
TemporaryAnnotation tempAnnotation = (TemporaryAnnotation) result;
getAnnotationModel().addAnnotation(tempAnnotation, tempAnnotation.getPosition());
}
}
/**
* @param object
* @return if this strategy is responisble for adding this type of key
*/
protected boolean canHandlePartition(String partition) {
String[] haystack = getPartitionTypes();
for (int i = 0; i < haystack.length; i++) {
if (haystack[i].equals(partition))
return true;
}
return false;
}
/**
* @param step
* @return
*/
protected boolean containsStep(IReconcileStep step) {
if (fFirstStep instanceof IStructuredReconcileStep)
return ((IStructuredReconcileStep) fFirstStep).isSiblingStep(step);
return false;
}
/**
* This is where you should create the steps for this strategy
*/
abstract public void createReconcileSteps();
/**
* Remove ALL temporary annotations that this strategy can handle.
*/
protected TemporaryAnnotation[] getAllAnnotationsToRemove() {
List removals = new ArrayList();
IAnnotationModel annotationModel = getAnnotationModel();
if (annotationModel != null) {
Iterator i = annotationModel.getAnnotationIterator();
while (i.hasNext()) {
Object obj = i.next();
if (!(obj instanceof ITemporaryAnnotation))
continue;
ITemporaryAnnotation annotation = (ITemporaryAnnotation) obj;
IReconcileAnnotationKey key = (IReconcileAnnotationKey) annotation.getKey();
// then if this strategy knows how to add/remove this
// partition type
if (canHandlePartition(key.getPartitionType()) && containsStep(key.getStep()))
removals.add(annotation);
}
}
return (TemporaryAnnotation[]) removals.toArray(new TemporaryAnnotation[removals.size()]);
}
protected IAnnotationModel getAnnotationModel() {
IAnnotationModel model = null;
if (fTextEditor != null && fTextEditor.getEditorInput() != null) {
model = fTextEditor.getDocumentProvider().getAnnotationModel(fTextEditor.getEditorInput());
}
return model;
}
protected TemporaryAnnotation[] getAnnotationsToRemove(DirtyRegion dr) {
IStructuredDocumentRegion[] sdRegions = getStructuredDocumentRegions(dr);
List remove = new ArrayList();
IAnnotationModel annotationModel = getAnnotationModel();
// can be null when closing the editor
if (getAnnotationModel() != null) {
Iterator i = annotationModel.getAnnotationIterator();
while (i.hasNext()) {
Object obj = i.next();
if (!(obj instanceof TemporaryAnnotation))
continue;
TemporaryAnnotation annotation = (TemporaryAnnotation) obj;
IReconcileAnnotationKey key = (IReconcileAnnotationKey) annotation.getKey();
// first check if this annotation is still relevant for the
// current partition
if (sdRegions.length > 0) {
if (!partitionsMatch(key, annotation.getPosition().offset, sdRegions[0])) {
remove.add(annotation);
continue;
}
}
// then if this strategy knows how to add/remove this
// partition type
if (canHandlePartition(key.getPartitionType()) && containsStep(key.getStep())) {
if (key.getScope() == IReconcileAnnotationKey.PARTIAL && overlaps(annotation.getPosition(), sdRegions)) {
remove.add(annotation);
} else if (key.getScope() == IReconcileAnnotationKey.TOTAL) {
remove.add(annotation);
}
}
}
}
return (TemporaryAnnotation[]) remove.toArray(new TemporaryAnnotation[remove.size()]);
}
/**
* Returns the corresponding node for the StructuredDocumentRegion.
*
* @param sdRegion
* @return the corresponding node for sdRegion
*/
protected IndexedRegion getCorrespondingNode(IStructuredDocumentRegion sdRegion) {
IStructuredModel sModel = ((IModelManagerPlugin) Platform.getPlugin(IModelManagerPlugin.ID)).getModelManager().getExistingModelForRead(fDocument);
IndexedRegion xmlNode = sModel.getIndexedRegion(sdRegion.getStart());
sModel.releaseFromRead();
return xmlNode;
}
/**
* The IFile that this strategy is operating on (the file input for the
* TextEditor)
*
* @return the IFile that this strategy is operating on
*/
protected IFile getFile() {
if (fTextEditor == null)
return null;
IEditorInput input = fTextEditor.getEditorInput();
if (!(input instanceof IFileEditorInput))
return null;
return ((IFileEditorInput) input).getFile();
}
/**
* pa_TODO - should be temporary until we figure out a way to send in
* partition with "reconcile()" call
*
* @param sdRegion
*/
protected IDocumentPartitioner getPartitioner(IStructuredDocumentRegion sdRegion) {
Assert.isNotNull(fDocument, "document was null when partitioning information was sought"); //$NON-NLS-1$
IDocumentPartitioner partitioner = fDocument.getDocumentPartitioner();
return partitioner;
}
/**
* Gets partition types from all steps in this strategy.
*
* @return parition types from all steps
*/
public String[] getPartitionTypes() {
if (fFirstStep instanceof IStructuredReconcileStep)
return ((IStructuredReconcileStep) fFirstStep).getPartitionTypes();
return new String[0];
}
/**
* Returns the appropriate (first) IStructuredDocumentRegion for the given
* dirtyRegion.
*
* @param dirtyRegion
* @return the appropriate StructuredDocumentRegion for the given
* dirtyRegion.
*/
private IStructuredDocumentRegion getStructuredDocumentRegion(int offset) {
IStructuredDocumentRegion sdRegion = null;
if (fDocument instanceof IStructuredDocument) {
sdRegion = ((IStructuredDocument) fDocument).getRegionAtCharacterOffset(offset);
}
return sdRegion;
}
private IStructuredDocumentRegion[] getStructuredDocumentRegions(DirtyRegion dr) {
int offset = dr.getOffset();
int end = offset + dr.getLength();
List regions = new ArrayList();
IStructuredDocumentRegion r = getStructuredDocumentRegion(offset);
while (r != null && r.getStartOffset() <= end) {
if (!r.isDeleted())
regions.add(r);
r = r.getNext();
}
return (IStructuredDocumentRegion[]) regions.toArray(new IStructuredDocumentRegion[regions.size()]);
}
public void init() {
createReconcileSteps();
}
/**
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#initialReconcile()
*/
public void initialReconcile() {
// do nothing
}
/**
* @return
*/
protected boolean isCanceled() {
if (Logger.isTracing(StructuredTextReconciler.TRACE_FILTER) && (fProgressMonitor != null && fProgressMonitor.isCanceled()))
Logger.trace(StructuredTextReconciler.TRACE_FILTER, "** STRATEGY CANCELED **:" + this.getClass().getName()); //$NON-NLS-1$
return fProgressMonitor != null && fProgressMonitor.isCanceled();
}
/**
* Checks if this position overlaps any of the StructuredDocument regions'
* correstponding IndexedRegion.
*
* @param pos
* @param sdRegions
* @return true if the position overlaps any of the regions, otherwise
* false.
*/
protected boolean overlaps(Position pos, IStructuredDocumentRegion[] sdRegions) {
int start = -1;
int end = -1;
for (int i = 0; i < sdRegions.length; i++) {
IndexedRegion corresponding = getCorrespondingNode(sdRegions[i]);
if (start == -1 || start > corresponding.getStartOffset())
start = corresponding.getStartOffset();
if (end == -1 || end < corresponding.getEndOffset())
end = corresponding.getEndOffset();
}
//System.out.println("checking overlap: [node:" + start + ":" + end +
// " pos:" + pos.getOffset() + ":" + pos.getLength() + "> " +
// pos.overlapsWith(start, end - start));
return pos.overlapsWith(start, end - start);
}
/**
* Checks to make sure that the annotation key (partition type for which
* it was originally added) matches the current document partition at that
* offset. This can occur when the character you just typed caused the
* previous (or subsequent) partition type to change.
*
* @param key
* @param sdRegion
* @return the partition type for this annotation matches the current
* document partition type
*/
private boolean partitionsMatch(IReconcileAnnotationKey key, int annotationPos, IStructuredDocumentRegion sdRegion) {
String keyPartitionType = key.getPartitionType();
IDocumentPartitioner p = getPartitioner(sdRegion);
String partitionType = p.getPartition(annotationPos).getType();
return keyPartitionType.equals(partitionType);
}
/**
* Process the results from the reconcile steps in this strategy.
*
* @param results
*/
private void process(final IReconcileResult[] results) {
if (Logger.isTracing(StructuredTextReconciler.TRACE_FILTER))
Logger.trace(StructuredTextReconciler.TRACE_FILTER, "[trace reconciler] > STARTING PROCESS METHOD with (" + results.length + ") results"); //$NON-NLS-1$ //$NON-NLS-2$
if (results == null)
return;
for (int i = 0; i < results.length; i++) {
if (isCanceled()) {
Logger.trace(StructuredTextReconciler.TRACE_FILTER, "[trace reconciler] >** PROCESS (adding) WAS CANCELLED **"); //$NON-NLS-1$
return;
}
addResultToAnnotationModel(results[i]);
}
// tracing
// --------------------------------------------------------------------
if (Logger.isTracing(StructuredTextReconciler.TRACE_FILTER)) {
StringBuffer traceString = new StringBuffer();
for (int j = 0; j < results.length; j++)
traceString.append("\n (+) :" + results[j] + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$
Logger.trace(StructuredTextReconciler.TRACE_FILTER, "[trace reconciler] > PROCESSING (" + results.length + ") results in AbstractStructuredTextReconcilingStrategy " + traceString); //$NON-NLS-1$ //$NON-NLS-2$
}
//------------------------------------------------------------------------------
}
/**
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.reconciler.DirtyRegion,
* org.eclipse.jface.text.IRegion)
*/
public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
// external files may be null
if (isCanceled() || fFirstStep == null)
return;
reconcile(dirtyRegion, subRegion, false);
}
/**
* Like IReconcileStep.reconcile(DirtyRegion dirtyRegion, IRegion
* subRegion) but also aware of the fact that the reconciler is running a
* processAll() operation, and short circuits removal and reconcile calls
* accordingly.
*
* @param dirtyRegion
* @param refreshAll
* @param subRegion
* @see IStructuredReconcilingStrategy#reconcile(DirtyRegion, IRegion,
* boolean)
*/
public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion, boolean refreshAll) {
// external files may be null
if (isCanceled() || fFirstStep == null)
return;
IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(dirtyRegion.getOffset());
if (sdRegion == null)
return;
TemporaryAnnotation[] annotationsToRemove = new TemporaryAnnotation[0];
IReconcileResult[] annotationsToAdd = new IReconcileResult[0];
IStructuredReconcileStep structuredStep = (IStructuredReconcileStep) fFirstStep;
if (!refreshAll) {
// regular reconcile
annotationsToRemove = getAnnotationsToRemove(dirtyRegion);
annotationsToAdd = structuredStep.reconcile(dirtyRegion, subRegion);
fAlreadyRemovedAllThisRun = false;
} else {
// the entire document is being reconciled (strategies may be
// called multiple times)
if (!fAlreadyRemovedAllThisRun) {
annotationsToRemove = getAllAnnotationsToRemove();
fAlreadyRemovedAllThisRun = true;
}
annotationsToAdd = structuredStep.reconcile(dirtyRegion, subRegion, true);
}
smartProcess(annotationsToRemove, annotationsToAdd);
}
/**
* @param partition
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.IRegion)
*/
public void reconcile(IRegion partition) {
// not used, we use - reconcile(DirtyRegion dirtyRegion, IRegion
// subRegion)
}
/**
* Calls release() on all the steps in this strategy. Currently done in
* StructuredTextReconciler.SourceWidgetDisposeListener#widgetDisposed(...)
*/
public void release() {
// release steps (each step calls release on the next)
if (fFirstStep != null && fFirstStep instanceof IReleasable)
((IReleasable) fFirstStep).release();
// we don't to null out the steps, in case
// it's reconfigured later
//fFirstStep = null;
}
private void removeAnnotations(TemporaryAnnotation[] annotationsToRemove) {
IAnnotationModel annotationModel = getAnnotationModel();
// can be null when closing the editor
if (annotationModel != null) {
for (int i = 0; i < annotationsToRemove.length; i++) {
if (isCanceled()) {
Logger.trace(StructuredTextReconciler.TRACE_FILTER, "[trace reconciler] >** REMOVAL WAS CANCELLED **"); //$NON-NLS-1$
return;
}
annotationModel.removeAnnotation(annotationsToRemove[i]);
}
}
// tracing
// --------------------------------------------------------------------
if (Logger.isTracing(StructuredTextReconciler.TRACE_FILTER)) {
StringBuffer traceString = new StringBuffer();
for (int i = 0; i < annotationsToRemove.length; i++)
traceString.append("\n (-) :" + annotationsToRemove[i] + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$
Logger.trace(StructuredTextReconciler.TRACE_FILTER, "[trace reconciler] > REMOVED (" + annotationsToRemove.length + ") annotations in AbstractStructuredTextReconcilingStrategy :" + traceString); //$NON-NLS-1$ //$NON-NLS-2$
}
//------------------------------------------------------------------------------
}
/**
* Resets any specially set for an operation such as processAll() from the
* reconciler.
*/
public void reset() {
fAlreadyRemovedAllThisRun = false;
if (fFirstStep instanceof IStructuredReconcileStep)
((IStructuredReconcileStep) fFirstStep).reset();
}
/**
* Set the document for this strategy.
*
* @param document
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#setDocument(org.eclipse.jface.text.IDocument)
*/
public void setDocument(IDocument document) {
// remove all old annotations since it's a new document
removeAnnotations(getAllAnnotationsToRemove());
if (document == null)
release();
fDocument = document;
if (fFirstStep != null)
fFirstStep.setInputModel(new DocumentAdapter(document));
}
/**
* @param monitor
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#setProgressMonitor(org.eclipse.core.runtime.IProgressMonitor)
*/
public void setProgressMonitor(IProgressMonitor monitor) {
fProgressMonitor = monitor;
if (fFirstStep != null)
fFirstStep.setProgressMonitor(fProgressMonitor);
}
/**
* pa_TODO make adding/removing smarter... Check if the annotation is
* already there, if it is, no need to remove or add again. this will
* avoid a lot of flickering behavior...
*
* @param annotationsToRemove
* @param annotationsToAdd
*/
protected void smartProcess(TemporaryAnnotation[] annotationsToRemove, IReconcileResult[] annotationsToAdd) {
removeAnnotations(annotationsToRemove);
process(annotationsToAdd);
}
}