blob: b9d30939fd64d7ddf898963cca39fc53aeb14f45 [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.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.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.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.ISourceViewer;
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(). This class should not know about
* IStructuredDocument, only IDocument.
*
* @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;
private IDocument fDocument = null;
// private IReconcileStep fFirstStep = null;
private IProgressMonitor fProgressMonitor = null;
private ISourceViewer fSourceViewer = null;
private Comparator fComparator;
// list of "validator" annotations
// for gray/un-gray capability
private HashSet fMarkerAnnotations = null;
/**
* Creates a new strategy. The editor parameter is for access to the
* annotation model.
*
* @param editor
*/
public AbstractStructuredTextReconcilingStrategy(ISourceViewer sourceViewer) {
fSourceViewer = sourceViewer;
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;
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 (fSourceViewer != null) {
model = fSourceViewer.getAnnotationModel();
}
return model;
}
protected TemporaryAnnotation[] getAnnotationsToRemove(DirtyRegion dr) {
List remove = new ArrayList();
IAnnotationModel annotationModel = getAnnotationModel();
// can be null when closing the editor
if (getAnnotationModel() != null) {
// clear validator annotations
getMarkerAnnotations().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 && annotation.getPosition().overlapsWith(dr.getOffset(), dr.getLength())) {
remove.add(annotation);
}
else if (key.getScope() == ReconcileAnnotationKey.TOTAL) {
remove.add(annotation);
}
}
}
}
return (TemporaryAnnotation[]) remove.toArray(new TemporaryAnnotation[remove.size()]);
}
protected abstract boolean containsStep(IReconcileStep step);
/**
* 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];
// }
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();
}
/**
* 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) {
// not used
// we only have validator strategy now
// // 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 = getMarkerAnnotations().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();
// if (getFirstStep() != null)
// getFirstStep().setInputModel(new DocumentAdapter(document));
fDocument = document;
}
public IDocument getDocument() {
return fDocument;
}
/**
* @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) {
// pa_TODO: investigate a better algorithm,
// also see if this is a bad performance hit.
Comparator comp = getTemporaryAnnotationComparator();
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;
}
if (getAnnotationModel() instanceof IAnnotationModelExtension) {
TemporaryAnnotation[] filteredRemovalArray = (TemporaryAnnotation[]) filteredRemovals.toArray(new TemporaryAnnotation[filteredRemovals.size()]);
// apply "grey"-ness
for (int i = 0; i < filteredRemovalArray.length; i++) {
if (isCanceled()) {
if (DEBUG)
System.out.println("[trace reconciler] >** replacing WAS CANCELLED **"); //$NON-NLS-1$
return;
}
StructuredMarkerAnnotation sma = getCorrespondingMarkerAnnotation(filteredRemovalArray[i]);
if (sma != null) {
// gray out the marker annotation
sma.setGrayed(true);
}
}
Map annotationsToAddMap = new HashMap();
for (int i = 0; i < filteredAdditions.size(); i++) {
TemporaryAnnotation temporaryAnnotation = (TemporaryAnnotation) filteredAdditions.get(i);
annotationsToAddMap.put(temporaryAnnotation, temporaryAnnotation.getPosition());
}
if (isCanceled()) {
if (DEBUG)
System.out.println("[trace reconciler] >** PROCESS (replacing) WAS CANCELLED **"); //$NON-NLS-1$
return;
}
/*
* Using the extension means we can't enforce the
* ELEMENT_ERROR_LIMIT limit.
*/
((IAnnotationModelExtension) getAnnotationModel()).replaceAnnotations(filteredRemovalArray, annotationsToAddMap);
}
else {
removeAnnotations((TemporaryAnnotation[]) filteredRemovals.toArray(new TemporaryAnnotation[filteredRemovals.size()]));
process((IReconcileResult[]) filteredAdditions.toArray(new IReconcileResult[filteredAdditions.size()]));
}
}
private Comparator getTemporaryAnnotationComparator() {
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 HashSet getMarkerAnnotations() {
if (fMarkerAnnotations == null)
fMarkerAnnotations = new HashSet();
return fMarkerAnnotations;
}
}