| /******************************************************************************* |
| * Copyright (c) 2001, 2006 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.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.jface.text.source.SourceViewer; |
| 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 IProgressMonitor fProgressMonitor = null; |
| private ISourceViewer fSourceViewer = null; |
| |
| // list of "validator" annotations |
| // for gray/un-gray capability |
| private HashSet fMarkerAnnotations = null; |
| |
| /** |
| * Creates a new strategy. The source viewer must be set manually |
| * after creation before a reconciler using this constructor will work. |
| */ |
| public AbstractStructuredTextReconcilingStrategy() { |
| init(); |
| } |
| |
| /** |
| * Creates a new strategy. |
| * |
| * @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 stepsRun) { |
| |
| 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()) && stepsRun.contains(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()); |
| } |
| |
| /** |
| * The user needs to manually set the viewer if the default |
| * constructor was used. |
| * |
| * @param viewer |
| */ |
| public void setViewer(SourceViewer viewer) { |
| fSourceViewer = viewer; |
| } |
| |
| /** |
| * 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) { |
| // 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); |
| |
| Map annotationsToAddMap = new HashMap(); |
| for (int i = 0; i < annotationsToAdd.length; i++) { |
| TemporaryAnnotation temporaryAnnotation = (TemporaryAnnotation) annotationsToAdd[i]; |
| annotationsToAddMap.put(temporaryAnnotation, temporaryAnnotation.getPosition()); |
| } |
| if (isCanceled()) { |
| if (DEBUG) |
| System.out.println("[trace reconciler] >** PROCESS (replacing) WAS CANCELLED **"); //$NON-NLS-1$ |
| return; |
| } |
| ((IAnnotationModelExtension) getAnnotationModel()).replaceAnnotations(annotationsToRemove, annotationsToAddMap); |
| } |
| else { |
| // removeAnnotations((TemporaryAnnotation[]) filteredRemovals.toArray(new TemporaryAnnotation[filteredRemovals.size()])); |
| // process((IReconcileResult[]) filteredAdditions.toArray(new IReconcileResult[filteredAdditions.size()])); |
| removeAnnotations(annotationsToRemove); |
| process(annotationsToAdd); |
| } |
| } |
| |
| // private Comparator getTemporaryAnnotationComparator() { |
| // if (fComparator == null) { |
| // fComparator = new Comparator() { |
| // public int compare(Object arg0, Object arg1) { |
| // TemporaryAnnotation ta1 = (TemporaryAnnotation) arg0; |
| // TemporaryAnnotation ta2 = (TemporaryAnnotation) arg1; |
| // int result = ta1.getPosition().getOffset() - ta2.getPosition().getOffset(); |
| // if(result != 0) |
| // return result; |
| // return Collator.getInstance().compare(ta1.getText(), ta2.getText()); |
| // } |
| // }; |
| // } |
| // return fComparator; |
| // } |
| |
| public HashSet getMarkerAnnotations() { |
| if (fMarkerAnnotations == null) |
| fMarkerAnnotations = new HashSet(); |
| return fMarkerAnnotations; |
| } |
| } |