blob: 7e76a52a7ba0b5bc76d523df127e6ef758e3fd0e [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.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.text.IDocument;
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.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.StructuredModelManager;
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.ui.internal.IReleasable;
import org.eclipse.wst.sse.ui.internal.ITemporaryAnnotation;
import org.eclipse.wst.sse.ui.internal.Logger;
import org.eclipse.wst.sse.ui.internal.StructuredMarkerAnnotation;
/**
* A base ReconcilingStrategy. Subclasses must implement
* createReconcileSteps().
*
* @author pavery
*/
public abstract class AbstractStructuredTextReconcilingStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension, IReleasable {
/** debug flag */
protected static final boolean DEBUG;
static {
String value = Platform.getDebugOption("org.eclipse.wst.sse.ui/debug/reconcilerjob"); //$NON-NLS-1$
DEBUG = value != null && value.equalsIgnoreCase("true"); //$NON-NLS-1$
}
// these limits are safetys for "runaway" validation cases
// should be used to safeguard potentially dangerous loops or potentially
// long annotations
// (since the painter seems to affect performance when painting long
// annotations)
public static final int ANNOTATION_LENGTH_LIMIT = 25;
public static final int ELEMENT_ERROR_LIMIT = 25;
protected IDocument fDocument = null;
protected IReconcileStep fFirstStep = null;
protected IProgressMonitor fProgressMonitor = null;
protected ITextEditor fTextEditor = null;
private Comparator fComparator;
// list of "validator" annotations
// for gray/un-gray capability
private HashSet fMarkerAnnotations = new HashSet();
/**
* 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;
StructuredMarkerAnnotation sma = getCorrespondingMarkerAnnotation(tempAnnotation);
if(sma != null) {
// un-gray out the marker annotation
sma.setGrayed(false);
}
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 StructuredReconcileStep)
return ((StructuredReconcileStep) 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;
ReconcileAnnotationKey key = (ReconcileAnnotationKey) 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) {
// clear validator annotations
fMarkerAnnotations.clear();
Iterator i = annotationModel.getAnnotationIterator();
while (i.hasNext()) {
Object obj = i.next();
// check if it's a validator marker annotation
// if it is save it for comparision later (to "gray" icons)
if(obj instanceof StructuredMarkerAnnotation) {
StructuredMarkerAnnotation sma = (StructuredMarkerAnnotation)obj;
if(sma.getAnnotationType() == TemporaryAnnotation.ANNOT_ERROR || sma.getAnnotationType() == TemporaryAnnotation.ANNOT_WARNING)
fMarkerAnnotations.add(sma);
}
if (!(obj instanceof TemporaryAnnotation))
continue;
TemporaryAnnotation annotation = (TemporaryAnnotation) obj;
ReconcileAnnotationKey key = (ReconcileAnnotationKey) annotation.getKey();
// then if this strategy knows how to add/remove this
// partition type
if (canHandlePartition(key.getPartitionType()) && containsStep(key.getStep())) {
if (key.getScope() == ReconcileAnnotationKey.PARTIAL && overlaps(annotation.getPosition(), sdRegions)) {
remove.add(annotation);
}
else if (key.getScope() == ReconcileAnnotationKey.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 = StructuredModelManager.getModelManager().getExistingModelForRead(fDocument);
IndexedRegion indexedRegion = null;
try {
if (sModel != null)
indexedRegion = sModel.getIndexedRegion(sdRegion.getStart());
} finally {
if (sModel != null)
sModel.releaseFromRead();
}
return indexedRegion;
}
/**
* 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();
}
/**
* Gets partition types from all steps in this strategy.
*
* @return parition types from all steps
*/
public String[] getPartitionTypes() {
if (fFirstStep instanceof StructuredReconcileStep)
return ((StructuredReconcileStep) 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 (DEBUG && (fProgressMonitor != null && fProgressMonitor.isCanceled()))
System.out.println("** 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++) {
if(!sdRegions[i].isDeleted()) {
IndexedRegion corresponding = getCorrespondingNode(sdRegions[i]);
if(corresponding != null) {
if (start == -1 || start > corresponding.getStartOffset())
start = corresponding.getStartOffset();
if (end == -1 || end < corresponding.getEndOffset())
end = corresponding.getEndOffset();
}
}
}
return pos.overlapsWith(start, end - start);
}
/**
* Process the results from the reconcile steps in this strategy.
*
* @param results
*/
private void process(final IReconcileResult[] results) {
if (DEBUG)
System.out.println("[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<ELEMENT_ERROR_LIMIT && !isCanceled(); i++) {
if (isCanceled()) {
if(DEBUG)
System.out.println("[trace reconciler] >** PROCESS (adding) WAS CANCELLED **"); //$NON-NLS-1$
return;
}
addResultToAnnotationModel(results[i]);
}
if (DEBUG) {
StringBuffer traceString = new StringBuffer();
for (int j = 0; j < results.length; j++)
traceString.append("\n (+) :" + results[j] + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$
System.out.println("[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;
TemporaryAnnotation[] annotationsToRemove = new TemporaryAnnotation[0];
IReconcileResult[] annotationsToAdd = new IReconcileResult[0];
StructuredReconcileStep structuredStep = (StructuredReconcileStep) fFirstStep;
annotationsToRemove = getAnnotationsToRemove(dirtyRegion);
annotationsToAdd = structuredStep.reconcile(dirtyRegion, subRegion);
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
* StructuredRegionProcessor.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
}
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()) {
if(DEBUG)
System.out.println("[trace reconciler] >** REMOVAL WAS CANCELLED **"); //$NON-NLS-1$
return;
}
StructuredMarkerAnnotation sma = getCorrespondingMarkerAnnotation(annotationsToRemove[i]);
if(sma != null) {
// gray out the marker annotation
sma.setGrayed(true);
}
// remove the temp one
annotationModel.removeAnnotation(annotationsToRemove[i]);
}
}
if (DEBUG) {
StringBuffer traceString = new StringBuffer();
for (int i = 0; i < annotationsToRemove.length; i++)
traceString.append("\n (-) :" + annotationsToRemove[i] + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$
System.out.println("[trace reconciler] > REMOVED (" + annotationsToRemove.length + ") annotations in AbstractStructuredTextReconcilingStrategy :" + traceString); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private StructuredMarkerAnnotation getCorrespondingMarkerAnnotation(TemporaryAnnotation tempAnnotation) {
Iterator it = fMarkerAnnotations.iterator();
while (it.hasNext()) {
StructuredMarkerAnnotation markerAnnotation = (StructuredMarkerAnnotation) it.next();
String message = ""; //$NON-NLS-1$
try {
message = (String) markerAnnotation.getMarker().getAttribute(IMarker.MESSAGE);
}
catch (CoreException e) {
if(DEBUG)
Logger.logException(e);
}
// it would be nice to check line number here...
if(message != null && message.equals(tempAnnotation.getText()))
return markerAnnotation;
}
return null;
}
private void removeAllAnnotations() {
removeAnnotations(getAllAnnotationsToRemove());
}
/**
* 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
removeAllAnnotations();
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);
}
/**
* 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) {
// TODO: investigate a better algorithm,
// also see if this is a bad performance hit.
Comparator comp = getAnnotationComparator();
List sortedRemovals = Arrays.asList(annotationsToRemove);
Collections.sort(sortedRemovals, comp);
List sortedAdditions = Arrays.asList(annotationsToAdd);
Collections.sort(sortedAdditions, comp);
List filteredRemovals = new ArrayList(sortedRemovals);
List filteredAdditions = new ArrayList(sortedAdditions);
boolean ignore = false;
int lastFoundAdded = 0;
for(int i=0; i<sortedRemovals.size(); i++) {
TemporaryAnnotation removal = (TemporaryAnnotation)sortedRemovals.get(i);
for(int j=lastFoundAdded; j<sortedAdditions.size(); j++) {
TemporaryAnnotation addition = (TemporaryAnnotation)sortedAdditions.get(j);
// quick position check here
if(removal.getPosition().equals(addition.getPosition())) {
lastFoundAdded = j;
// remove performs TemporaryAnnotation.equals()
// which checks text as well
filteredAdditions.remove(addition);
ignore = true;
if(DEBUG)
System.out.println(" ~ smart process ignoring: " + removal.getPosition().getOffset()); //$NON-NLS-1$
break;
}
}
if(ignore) {
filteredRemovals.remove(removal);
}
ignore = false;
}
removeAnnotations((TemporaryAnnotation[])filteredRemovals.toArray(new TemporaryAnnotation[filteredRemovals.size()]));
process((IReconcileResult[])filteredAdditions.toArray(new IReconcileResult[filteredAdditions.size()]));
}
private Comparator getAnnotationComparator() {
if(fComparator == null) {
fComparator = new Comparator( ) {
public int compare(Object arg0, Object arg1) {
TemporaryAnnotation ta1 = (TemporaryAnnotation)arg0;
TemporaryAnnotation ta2 = (TemporaryAnnotation)arg1;
return ta1.getPosition().getOffset() - ta2.getPosition().getOffset();
}
};
}
return fComparator;
}
public boolean isTotalScope() {
return false;
}
}