| /******************************************************************************* |
| * 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.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentPartitioner; |
| import org.eclipse.jface.text.ITextInputListener; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.ITypedRegion; |
| import org.eclipse.jface.text.reconciler.DirtyRegion; |
| import org.eclipse.jface.text.reconciler.IReconcilingStrategy; |
| import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; |
| import org.eclipse.wst.sse.core.internal.model.ModelLifecycleEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.IModelLifecycleListener; |
| 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.events.IStructuredDocumentListener; |
| import org.eclipse.wst.sse.core.internal.provisional.events.NewDocumentEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent; |
| 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.core.internal.provisional.text.IStructuredDocumentRegionList; |
| import org.eclipse.wst.sse.ui.internal.IReleasable; |
| import org.eclipse.wst.sse.ui.internal.reconcile.validator.ValidatorStrategy; |
| |
| /** |
| * Adds StructuredDocument and StructuredModel listeners. Adds Text viewer |
| * (dispose, input changed) listeners. |
| * |
| * Implements a smarter "contains" method. |
| * |
| * Adds default and validator strategies. Adds DirtyRegion processing logic. |
| */ |
| public class StructuredRegionProcessor extends DirtyRegionProcessor implements IStructuredDocumentListener, IModelLifecycleListener { |
| |
| /** |
| * Reconclies the entire document when the document in the viewer is |
| * changed. This happens when the document is initially opened, as well as |
| * after a save-as. |
| * |
| * Also see processPostModelEvent(...) for similar behavior when document |
| * for the model is changed. |
| */ |
| private class SourceTextInputListener implements ITextInputListener { |
| |
| public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { |
| // do nothing |
| } |
| |
| public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { |
| handleInputDocumentChanged(oldInput, newInput); |
| } |
| } |
| |
| /** for initital reconcile when document is opened */ |
| private SourceTextInputListener fTextInputListener = null; |
| |
| |
| /** strategy called for unmapped partitions */ |
| private IReconcilingStrategy fDefaultStrategy; |
| |
| /** |
| * The strategy that runs validators contributed via |
| * <code>org.eclipse.wst.sse.ui.extensions.sourcevalidation</code> |
| * extension point |
| */ |
| private ValidatorStrategy fValidatorStrategy; |
| |
| /** |
| * @return Returns the fDefaultStrategy. |
| */ |
| public IReconcilingStrategy getDefaultStrategy() { |
| return fDefaultStrategy; |
| } |
| |
| /** |
| * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#getAppropriateStrategy(org.eclipse.jface.text.reconciler.DirtyRegion) |
| */ |
| protected IReconcilingStrategy getStrategy(DirtyRegion dirtyRegion) { |
| IReconcilingStrategy strategy = super.getStrategy(dirtyRegion); |
| if (strategy == null) |
| strategy = getDefaultStrategy(); |
| return strategy; |
| } |
| |
| /** |
| * @return Returns the fValidatorStrategy. |
| */ |
| public ValidatorStrategy getValidatorStrategy() { |
| return fValidatorStrategy; |
| } |
| |
| /** |
| * @param dirtyRegion |
| */ |
| protected void process(DirtyRegion dirtyRegion) { |
| if (!isInstalled()) |
| return; |
| |
| ITypedRegion[] unfiltered = computePartitioning(dirtyRegion); |
| |
| // remove duplicate typed regions |
| // that are handled by the same "total scope" strategy |
| ITypedRegion[] filtered = filterTotalScopeRegions(unfiltered); |
| |
| IReconcilingStrategy s; |
| DirtyRegion dirty = null; |
| for (int i = 0; i < filtered.length; i++) { |
| |
| dirty = createDirtyRegion(filtered[i], DirtyRegion.INSERT); |
| s = getReconcilingStrategy(filtered[i].getType()); |
| if (s != null && dirty != null) { |
| s.reconcile(dirty, dirty); |
| } |
| |
| // validator for this partition |
| if (fValidatorStrategy != null) |
| fValidatorStrategy.reconcile(filtered[i], dirty); |
| } |
| } |
| |
| /** |
| * Removes multiple "total-scope" regions (and leaves one) for a each |
| * partitionType. This improves performance by preventing unnecessary full |
| * document validations. |
| * |
| * @param unfiltered |
| * @return |
| */ |
| private ITypedRegion[] filterTotalScopeRegions(ITypedRegion[] unfiltered) { |
| IReconcilingStrategy s = null; |
| // ensure there is only one typed region in the list |
| // for regions handled by "total scope" strategies |
| HashMap totalScopeRegions = new HashMap(); |
| HashMap partialScopeRegions = new HashMap(); |
| List allRegions = new ArrayList(); |
| for (int i = 0; i < unfiltered.length; i++) { |
| |
| String partitionType = unfiltered[i].getType(); |
| |
| // short circuit loop |
| if (totalScopeRegions.containsKey(partitionType) || partialScopeRegions.containsKey(partitionType)) |
| continue; |
| |
| s = getReconcilingStrategy(partitionType); |
| |
| // might be the validator strategy |
| if (s == null) { |
| ValidatorStrategy validatorStrategy = getValidatorStrategy(); |
| if (validatorStrategy != null) { |
| if (validatorStrategy.canValidatePartition(partitionType)) |
| s = validatorStrategy; |
| } |
| } |
| |
| if (s instanceof AbstractStructuredTextReconcilingStrategy) { |
| // only allow one dirty region for a strategy |
| // that has "total scope" |
| if (((AbstractStructuredTextReconcilingStrategy) s).isTotalScope()) |
| totalScopeRegions.put(partitionType, unfiltered[i]); |
| else |
| partialScopeRegions.put(partitionType, unfiltered[i]); |
| } |
| else |
| partialScopeRegions.put(partitionType, unfiltered[i]); |
| } |
| allRegions.addAll(totalScopeRegions.values()); |
| allRegions.addAll(partialScopeRegions.values()); |
| ITypedRegion[] filtered = (ITypedRegion[]) allRegions.toArray(new ITypedRegion[allRegions.size()]); |
| |
| if (DEBUG) |
| System.out.println("filtered out this many 'total-scope' regions: " + (unfiltered.length - filtered.length)); //$NON-NLS-1$ |
| |
| return filtered; |
| } |
| |
| /** |
| * @param defaultStrategy |
| * The fDefaultStrategy to set. |
| */ |
| public void setDefaultStrategy(IReconcilingStrategy defaultStrategy) { |
| fDefaultStrategy = defaultStrategy; |
| if (fDefaultStrategy != null) { |
| fDefaultStrategy.setDocument(getDocument()); |
| if (fDefaultStrategy instanceof IReconcilingStrategyExtension) |
| ((IReconcilingStrategyExtension) fDefaultStrategy).setProgressMonitor(getLocalProgressMonitor()); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#setDocumentOnAllStrategies(org.eclipse.jface.text.IDocument) |
| */ |
| protected void setDocumentOnAllStrategies(IDocument document) { |
| |
| super.setDocumentOnAllStrategies(document); |
| |
| IReconcilingStrategy defaultStrategy = getDefaultStrategy(); |
| IReconcilingStrategy validatorStrategy = getValidatorStrategy(); |
| |
| // default strategies |
| if (defaultStrategy != null) |
| defaultStrategy.setDocument(document); |
| |
| // external validator strategy |
| if (validatorStrategy != null) |
| validatorStrategy.setDocument(document); |
| } |
| |
| /** |
| * @param validatorStrategy |
| * The fValidatorStrategy to set. |
| */ |
| public void setValidatorStrategy(ValidatorStrategy validatorStrategy) { |
| fValidatorStrategy = validatorStrategy; |
| if (fValidatorStrategy != null) { |
| fValidatorStrategy.setDocument(getDocument()); |
| fValidatorStrategy.setProgressMonitor(getLocalProgressMonitor()); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#contains(org.eclipse.jface.text.reconciler.DirtyRegion, |
| * org.eclipse.jface.text.reconciler.DirtyRegion) |
| */ |
| protected boolean contains(DirtyRegion root, DirtyRegion possible) { |
| |
| // this method is a performance hit |
| // look for alternatives |
| |
| boolean contains = false; |
| IStructuredModel sModel = getStructuredModelForRead(getDocument()); |
| try { |
| IndexedRegion rootRegion = sModel.getIndexedRegion(root.getOffset()); |
| IndexedRegion possRegion = sModel.getIndexedRegion(possible.getOffset()); |
| if (rootRegion != null && possRegion != null) { |
| int rootStart = rootRegion.getStartOffset(); |
| int rootEnd = rootRegion.getEndOffset(); |
| int possStart = possRegion.getStartOffset(); |
| int possEnd = possRegion.getEndOffset(); |
| |
| if (rootStart <= possStart && rootEnd >= possEnd) |
| contains = true; |
| |
| if (DEBUG) |
| System.out.println("checking if [" + rootStart + ":" + rootEnd + "] contains [" + possStart + ":" + possEnd + "] ... " + contains); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ |
| } |
| } |
| finally { |
| if (sModel != null) |
| sModel.releaseFromRead(); |
| } |
| return contains; |
| } |
| |
| /** |
| * Remember to release model after use!! |
| * |
| * @return |
| */ |
| public IStructuredModel getStructuredModelForRead(IDocument doc) { |
| |
| IStructuredModel sModel = null; |
| if (doc != null) |
| sModel = StructuredModelManager.getModelManager().getExistingModelForRead(doc); |
| return sModel; |
| } |
| |
| /** |
| * |
| * @param oldInput |
| * @param newInput |
| */ |
| public void handleInputDocumentChanged(IDocument oldInput, IDocument newInput) { |
| // don't bother if reconciler not installed |
| if (isInstalled()) { |
| |
| reconcilerDocumentChanged(newInput); |
| |
| setDocument(newInput); |
| setDocumentOnAllStrategies(newInput); |
| setEntireDocumentDirty(newInput); |
| } |
| } |
| |
| /** |
| * @param document |
| */ |
| private void hookUpModelLifecycleListener(IDocument document) { |
| IStructuredModel sModel = getStructuredModelForRead(document); |
| try { |
| if (sModel != null) { |
| sModel.addModelLifecycleListener(this); |
| } |
| } |
| finally { |
| if (sModel != null) |
| sModel.releaseFromRead(); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#install(org.eclipse.jface.text.ITextViewer) |
| */ |
| public void install(ITextViewer textViewer) { |
| |
| super.install(textViewer); |
| fTextInputListener = new SourceTextInputListener(); |
| textViewer.addTextInputListener(fTextInputListener); |
| } |
| |
| public void newModel(NewDocumentEvent structuredDocumentEvent) { |
| // happens on a revert |
| reconcilerDocumentChanged(structuredDocumentEvent.getDocument()); |
| } |
| |
| public void noChange(NoChangeEvent structuredDocumentEvent) { |
| // do nothing |
| } |
| |
| public void nodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) { |
| if (DEBUG) |
| System.out.println("[trace reconciler] >StructuredRegionProcessor: *NODES REPLACED"); //$NON-NLS-1$ |
| |
| DirtyRegion dr = partitionChanged(structuredDocumentEvent) ? createDirtyRegion(0, getDocument().getLength(), DirtyRegion.INSERT) : createDirtyRegion(structuredDocumentEvent.getOffset(), structuredDocumentEvent.getLength(), DirtyRegion.INSERT); |
| processDirtyRegion(dr); |
| } |
| |
| /** |
| * Checks if the StructuredDocumentEvent involved a partition change. If |
| * there's a partition change, we know we should run all strategies just |
| * to be sure we cover the new regions and remove obsolete annotations. |
| * |
| * A primitive check for now. |
| * |
| * @param structuredDocumentEvent |
| * @return |
| */ |
| private boolean partitionChanged(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) { |
| boolean changed = false; |
| |
| IDocumentPartitioner partitioner = structuredDocumentEvent.getStructuredDocument().getDocumentPartitioner(); |
| if (partitioner != null) { |
| IStructuredDocumentRegionList oldNodes = structuredDocumentEvent.getOldStructuredDocumentRegions(); |
| IStructuredDocumentRegionList newNodes = structuredDocumentEvent.getNewStructuredDocumentRegions(); |
| |
| IStructuredDocumentRegion oldNode = (oldNodes.getLength() > 0) ? oldNode = oldNodes.item(0) : null; |
| IStructuredDocumentRegion newNode = (newNodes.getLength() > 0) ? newNodes.item(0) : null; |
| |
| if (oldNode != null && newNode != null) |
| changed = partitioner.getContentType(oldNode.getStartOffset()).equals(partitioner.getContentType(newNode.getStartOffset())); |
| } |
| return changed; |
| } |
| |
| /** |
| * @see org.eclipse.wst.sse.core.internal.provisional.IModelLifecycleListener#processPostModelEvent(org.eclipse.wst.sse.core.internal.model.ModelLifecycleEvent) |
| */ |
| public void processPostModelEvent(ModelLifecycleEvent event) { |
| |
| // if underlying StructuredDocument changed, need to reconnect it |
| // here... |
| // ex. file is modified outside the workbench |
| if (event.getType() == ModelLifecycleEvent.MODEL_DOCUMENT_CHANGED) { |
| |
| // check that it's this model that changed |
| IStructuredModel thisModel = getStructuredModelForRead(getDocument()); |
| try { |
| if (thisModel != null && event.getModel().equals(thisModel)) { |
| |
| IStructuredDocument sDoc = event.getModel().getStructuredDocument(); |
| |
| if (DEBUG) { |
| System.out.println("======================================================"); //$NON-NLS-1$ |
| System.out.println("StructuredRegionProcessor: DOCUMENT MODEL CHANGED TO: "); //$NON-NLS-1$ |
| System.out.println(sDoc.get()); |
| System.out.println("======================================================"); //$NON-NLS-1$ |
| } |
| setDocument(sDoc); |
| // propagate document change |
| setDocumentOnAllStrategies(sDoc); |
| // ensure that the document is re-reconciled |
| setEntireDocumentDirty(sDoc); |
| } |
| } |
| finally { |
| if (thisModel != null) |
| thisModel.releaseFromRead(); |
| } |
| } |
| } |
| |
| /** |
| * @see org.eclipse.wst.sse.core.internal.provisional.IModelLifecycleListener#processPreModelEvent(org.eclipse.wst.sse.core.internal.model.ModelLifecycleEvent) |
| */ |
| public void processPreModelEvent(ModelLifecycleEvent event) { |
| |
| if (event.getType() == ModelLifecycleEvent.MODEL_DOCUMENT_CHANGED) { |
| |
| getDirtyRegionQueue().clear(); |
| // note: old annotations are removed via the strategies on |
| // AbstractStructuredTextReconcilingStrategy#setDocument(...) |
| } |
| } |
| |
| /** |
| * Reinitializes listeners and sets new document onall strategies. |
| * |
| * @see org.eclipse.jface.text.reconciler.AbstractReconciler#reconcilerDocumentChanged(IDocument) |
| */ |
| protected void reconcilerDocumentChanged(IDocument newDocument) { |
| |
| IDocument currentDoc = getDocument(); |
| |
| // unhook old lifecycle listner |
| unhookModelLifecycleListener(currentDoc); |
| // add new lifecycle listener |
| if (newDocument != null) |
| hookUpModelLifecycleListener(newDocument); |
| |
| // unhook old document listener |
| if (currentDoc != null && currentDoc instanceof IStructuredDocument) |
| ((IStructuredDocument) currentDoc).removeDocumentChangedListener(this); |
| // hook up new document listener |
| if (newDocument != null && newDocument instanceof IStructuredDocument) |
| ((IStructuredDocument) newDocument).addDocumentChangedListener(this); |
| |
| // sets document on all strategies |
| super.reconcilerDocumentChanged(newDocument); |
| } |
| |
| public void regionChanged(RegionChangedEvent structuredDocumentEvent) { |
| if (DEBUG) |
| System.out.println("[trace reconciler] >StructuredRegionProcessor: *REGION CHANGED: \r\n\r\n created dirty region from flat model event >> :" + structuredDocumentEvent.getOffset() + ":" + structuredDocumentEvent.getLength() + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| |
| String dirtyRegionType = structuredDocumentEvent.getDeletedText().equals("") ? DirtyRegion.INSERT : DirtyRegion.REMOVE; //$NON-NLS-1$ |
| DirtyRegion dr = createDirtyRegion(structuredDocumentEvent.getOffset(), structuredDocumentEvent.getLength(), dirtyRegionType); |
| processDirtyRegion(dr); |
| } |
| |
| public void regionsReplaced(RegionsReplacedEvent structuredDocumentEvent) { |
| if (DEBUG) |
| System.out.println("[trace reconciler] >StructuredRegionProcessor: *REGIONS REPLACED: \r\n\r\n created dirty region from flat model event >> :" + structuredDocumentEvent.getOffset() + ":" + structuredDocumentEvent.getLength() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| |
| DirtyRegion dr = createDirtyRegion(structuredDocumentEvent.getOffset(), structuredDocumentEvent.getLength(), DirtyRegion.INSERT); |
| processDirtyRegion(dr); |
| } |
| |
| protected void setEntireDocumentDirty(IDocument document) { |
| |
| // make the entire document dirty |
| // this also happens on a "save as" |
| if (document != null && isInstalled()) { |
| |
| // since we're marking the entire doc dirty |
| getDirtyRegionQueue().clear(); |
| DirtyRegion entireDocument = createDirtyRegion(0, document.getLength(), DirtyRegion.INSERT); |
| processDirtyRegion(entireDocument); |
| } |
| } |
| |
| /** |
| * @param document |
| */ |
| private void unhookModelLifecycleListener(IDocument document) { |
| IStructuredModel sModel = getStructuredModelForRead(document); |
| try { |
| if (sModel != null) |
| sModel.removeModelLifecycleListener(this); |
| |
| } |
| finally { |
| if (sModel != null) |
| sModel.releaseFromRead(); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#uninstall() |
| */ |
| public void uninstall() { |
| if (isInstalled()) { |
| |
| getLocalProgressMonitor().setCanceled(true); |
| cancel(); |
| |
| // removes model listeners |
| unhookModelLifecycleListener(getDocument()); |
| |
| // removes document listeners |
| reconcilerDocumentChanged(null); |
| |
| // removes widget listener |
| getTextViewer().removeTextInputListener(fTextInputListener); |
| // getTextViewer().getTextWidget().removeDisposeListener(fDisposeListener); |
| |
| // release all strategies |
| List strategyTypes = getStrategyTypes(); |
| if (!strategyTypes.isEmpty()) { |
| Iterator it = strategyTypes.iterator(); |
| IReconcilingStrategy strategy = null; |
| while (it.hasNext()) { |
| strategy = getReconcilingStrategy((String) it.next()); |
| if (strategy instanceof IReleasable) { |
| ((IReleasable) strategy).release(); |
| strategy = null; |
| } |
| } |
| } |
| } |
| super.uninstall(); |
| } |
| } |