/******************************************************************************* * Copyright (c) 2006, 2011 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 * David Carver (Intalio) - bug 307323 - remove extraneous call to spell check strategy *******************************************************************************/ package org.eclipse.wst.sse.ui.internal.reconcile; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.core.runtime.content.IContentTypeManager; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.reconciler.DirtyRegion; import org.eclipse.jface.text.reconciler.IReconcilingStrategy; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser; import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionHandler; import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionParser; 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.ExtendedConfigurationBuilder; import org.eclipse.wst.sse.ui.internal.IReleasable; import org.eclipse.wst.sse.ui.internal.Logger; import org.eclipse.wst.sse.ui.internal.SSEUIPlugin; import org.eclipse.wst.sse.ui.internal.projection.AbstractStructuredFoldingStrategy; import org.eclipse.wst.sse.ui.internal.provisional.preferences.CommonEditorPreferenceNames; import org.eclipse.wst.sse.ui.internal.reconcile.validator.ValidatorBuilder; import org.eclipse.wst.sse.ui.internal.reconcile.validator.ValidatorMetaData; import org.eclipse.wst.sse.ui.internal.reconcile.validator.ValidatorStrategy; import org.eclipse.wst.sse.ui.internal.spelling.SpellcheckStrategy; import org.eclipse.wst.sse.ui.reconcile.ISourceReconcilingListener; /** * Adds to DirtyRegionProcessor Job: - IDocumentListener - ValidatorStrategy - * Text viewer(dispose, input changed) listeners. - default, spelling, and * validator strategies - DirtyRegion processing logic. */ public class DocumentRegionProcessor extends DirtyRegionProcessor { private static final boolean DEBUG_VALIDATORS = Boolean.TRUE.toString().equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/debug/reconcilerValidators")); //$NON-NLS-1$ /** * Marks the entire document dirty when a parser-level change is notified * that might affect the entire document */ class DirtyRegionParseHandler implements StructuredDocumentRegionHandler { public void nodeParsed(IStructuredDocumentRegion aCoreStructuredDocumentRegion) { } public void resetNodes() { setEntireDocumentDirty(getDocument()); } } private DirtyRegionParseHandler fResetHandler = new DirtyRegionParseHandler(); /** * A strategy to use the defined default Spelling service. */ private IReconcilingStrategy fSpellcheckStrategy; /** * The strategy that runs validators contributed via * <code>org.eclipse.wst.sse.ui.extensions.sourcevalidation</code> * extension point */ private ValidatorStrategy fValidatorStrategy; private ISourceReconcilingListener[] fReconcileListeners = new ISourceReconcilingListener[0]; private IReconcilingStrategy fSemanticHighlightingStrategy; /** * The folding strategy for this processor */ private AbstractStructuredFoldingStrategy fFoldingStrategy; private final String SSE_UI_ID = "org.eclipse.wst.sse.ui"; //$NON-NLS-1$ /** * true if as you type validation is enabled, * false otherwise */ private boolean fValidationEnabled; public void addReconcilingListener(ISourceReconcilingListener listener) { Set listeners = new HashSet(Arrays.asList(fReconcileListeners)); listeners.add(listener); fReconcileListeners = (ISourceReconcilingListener[]) listeners.toArray(new ISourceReconcilingListener[listeners.size()]); } protected void beginProcessing() { super.beginProcessing(); ValidatorStrategy validatorStrategy = getValidatorStrategy(); if (validatorStrategy != null) { validatorStrategy.beginProcessing(); } if ((getTextViewer() instanceof ISourceViewer)) { for (int i = 0; i < fReconcileListeners.length; i++) { fReconcileListeners[i].aboutToBeReconciled(); } } } protected void endProcessing() { super.endProcessing(); ValidatorStrategy validatorStrategy = getValidatorStrategy(); if (validatorStrategy != null) { validatorStrategy.endProcessing(); } /* single spell-check for everything to ensure that SpellingProblem offsets are correct */ IReconcilingStrategy spellingStrategy = getSpellcheckStrategy(); IDocument document = getDocument(); if (spellingStrategy != null && document != null) { spellingStrategy.reconcile(new Region(0, document.getLength())); } IReconcilingStrategy semanticHighlightingStrategy = getSemanticHighlightingStrategy(); if (semanticHighlightingStrategy != null && document != null) { semanticHighlightingStrategy.reconcile(new Region(0, document.getLength())); } if ((getTextViewer() instanceof ISourceViewer)) { ISourceViewer sourceViewer = (ISourceViewer) getTextViewer(); IAnnotationModel annotationModel = sourceViewer.getAnnotationModel(); for (int i = 0; i < fReconcileListeners.length; i++) { fReconcileListeners[i].reconciled(document, annotationModel, false, new NullProgressMonitor()); } } } public void forceReconciling() { super.forceReconciling(); } protected String getContentType(IDocument doc) { if (doc == null) return null; String contentTypeId = null; IContentType ct = null; try { IContentDescription desc = Platform.getContentTypeManager().getDescriptionFor(new StringReader(doc.get()), null, IContentDescription.ALL); if (desc != null) { ct = desc.getContentType(); if (ct != null) contentTypeId = ct.getId(); } } catch (IOException e) { // just bail } return contentTypeId; } protected IReconcilingStrategy getSpellcheckStrategy() { if (fSpellcheckStrategy == null && getDocument() != null) { String contentTypeId = getContentType(getDocument()); if (contentTypeId == null) { contentTypeId = IContentTypeManager.CT_TEXT; } if (getTextViewer() instanceof ISourceViewer) { ISourceViewer viewer = (ISourceViewer) getTextViewer(); fSpellcheckStrategy = new SpellcheckStrategy(viewer, contentTypeId); fSpellcheckStrategy.setDocument(getDocument()); } } return fSpellcheckStrategy; } /** * <p>Get the folding strategy for this processor. Retrieved from the * extended configuration builder. The processor chosen is set by the plugin.</p> * * <p>EX:<br /> * <code><extension point="org.eclipse.wst.sse.ui.editorConfiguration"><br /> * <provisionalConfiguration<br /> * type="foldingstrategy"<br /> * class="org.eclipse.wst.xml.ui.internal.projection.XMLFoldingStrategy"<br /> * target="org.eclipse.core.runtime.xml, org.eclipse.wst.xml.core.xmlsource" /><br /> * </extension></code></p> * * <p>The type must be equal to <code>AbstractFoldingStrategy.ID</code> (AKA: foldingstrategy) * and the class must extend <code>org.eclipse.wst.sse.ui.internal.projection.AbstractFoldingStrategy</code> * and the target must be a structured editor content type ID</p> * * @return the requested folding strategy or null if none can be found */ protected IReconcilingStrategy getFoldingStrategy() { if(fFoldingStrategy == null && getDocument() != null) { String contentTypeId = getContentType(getDocument()); if (contentTypeId == null) { contentTypeId = IContentTypeManager.CT_TEXT; } ITextViewer viewer = getTextViewer(); if(viewer instanceof ProjectionViewer) { ExtendedConfigurationBuilder builder = ExtendedConfigurationBuilder.getInstance(); IContentType type = Platform.getContentTypeManager().getContentType(contentTypeId); while(fFoldingStrategy == null && type != null) { fFoldingStrategy =(AbstractStructuredFoldingStrategy) builder.getConfiguration( AbstractStructuredFoldingStrategy.ID, type.getId()); type = type.getBaseType(); } if(fFoldingStrategy != null) { fFoldingStrategy.setViewer((ProjectionViewer)viewer); fFoldingStrategy.setDocument(getDocument()); } } } return fFoldingStrategy; } /** * Enable or disable as you type validation. Typically set by a user preference * * @param enable true to enable as you type validation, false to disable */ public void setValidatorStrategyEnabled(boolean enable) { fValidationEnabled = enable; } /** * @return Returns the ValidatorStrategy. */ protected ValidatorStrategy getValidatorStrategy() { ValidatorStrategy validatorStrategy = null; if (fValidatorStrategy == null && fValidationEnabled) { if (getTextViewer() instanceof ISourceViewer) { ISourceViewer viewer = (ISourceViewer) getTextViewer(); String contentTypeId = null; IDocument doc = viewer.getDocument(); contentTypeId = getContentType(doc); if (contentTypeId != null) { validatorStrategy = new ValidatorStrategy(viewer, contentTypeId); ValidatorBuilder vBuilder = new ValidatorBuilder(); ValidatorMetaData[] vmds = vBuilder.getValidatorMetaData(SSE_UI_ID); List enabledValidators = new ArrayList(1); /* if any "must" handle this content type, just add them */ boolean foundSpecificContentTypeValidators = false; for (int i = 0; i < vmds.length; i++) { if (vmds[i].mustHandleContentType(contentTypeId)) { if (DEBUG_VALIDATORS) Logger.log(Logger.INFO, contentTypeId + " using specific validator " + vmds[i].getValidatorId()); //$NON-NLS-1$ foundSpecificContentTypeValidators = true; enabledValidators.add(vmds[i]); } } if (!foundSpecificContentTypeValidators) { for (int i = 0; i < vmds.length; i++) { if (vmds[i].canHandleContentType(contentTypeId)) { if (DEBUG_VALIDATORS) Logger.log(Logger.INFO, contentTypeId + " using inherited(?) validator " + vmds[i].getValidatorId()); //$NON-NLS-1$ enabledValidators.add(vmds[i]); } } } for (int i = 0; i < enabledValidators.size(); i++) { validatorStrategy.addValidatorMetaData((ValidatorMetaData) enabledValidators.get(i)); } } } fValidatorStrategy = validatorStrategy; } else if(fValidatorStrategy != null && fValidationEnabled) { validatorStrategy = fValidatorStrategy; } return validatorStrategy; } public void setSemanticHighlightingStrategy(IReconcilingStrategy semanticHighlightingStrategy) { fSemanticHighlightingStrategy = semanticHighlightingStrategy; fSemanticHighlightingStrategy.setDocument(getDocument()); } protected IReconcilingStrategy getSemanticHighlightingStrategy() { return fSemanticHighlightingStrategy; } /** * @param dirtyRegion */ protected void process(DirtyRegion dirtyRegion) { if (!isInstalled() || isInRewriteSession() || dirtyRegion == null || getDocument() == null) return; super.process(dirtyRegion); ITypedRegion[] partitions = computePartitioning(dirtyRegion); // call the validator strategy once for each effected partition DirtyRegion dirty = null; for (int i = 0; i < partitions.length; i++) { dirty = createDirtyRegion(partitions[i], DirtyRegion.INSERT); // [source]validator (extension) for this partition if (getValidatorStrategy() != null) { getValidatorStrategy().reconcile(partitions[i], dirty); } } /* if there is a folding strategy then reconcile it for the * entire dirty region. * NOTE: the folding strategy does not care about the sub regions. */ if(getFoldingStrategy() != null) { getFoldingStrategy().reconcile(dirtyRegion, null); } } public void removeReconcilingListener(ISourceReconcilingListener listener) { Set listeners = new HashSet(Arrays.asList(fReconcileListeners)); listeners.remove(listener); fReconcileListeners = (ISourceReconcilingListener[]) listeners.toArray(new ISourceReconcilingListener[listeners.size()]); } public void setDocument(IDocument doc) { if (getDocument() instanceof IStructuredDocument) { RegionParser parser = ((IStructuredDocument) getDocument()).getParser(); if (parser instanceof StructuredDocumentRegionParser) { ((StructuredDocumentRegionParser) parser).removeStructuredDocumentRegionHandler(fResetHandler); } } super.setDocument(doc); IReconcilingStrategy validatorStrategy = getValidatorStrategy(); if (validatorStrategy != null) { validatorStrategy.setDocument(doc); } if (fSemanticHighlightingStrategy != null) { fSemanticHighlightingStrategy.setDocument(doc); } fSpellcheckStrategy = null; if(fFoldingStrategy != null) { fFoldingStrategy.uninstall(); } fFoldingStrategy = null; if (getDocument() instanceof IStructuredDocument) { RegionParser parser = ((IStructuredDocument) doc).getParser(); if (parser instanceof StructuredDocumentRegionParser) { ((StructuredDocumentRegionParser) parser).addStructuredDocumentRegionHandler(fResetHandler); } } } protected void setEntireDocumentDirty(IDocument document) { super.setEntireDocumentDirty(document); // make the entire document dirty // this also happens on a "save as" if (document != null && isInstalled() && fLastPartitions != null && document.getLength() == 0) { /** * https://bugs.eclipse.org/bugs/show_bug.cgi?id=199053 * * Process the strategies for the last known-good partitions. */ for (int i = 0; i < fLastPartitions.length; i++) { ValidatorStrategy validatorStrategy = getValidatorStrategy(); if (validatorStrategy != null) { validatorStrategy.reconcile(fLastPartitions[i], createDirtyRegion(fLastPartitions[i], DirtyRegion.REMOVE)); } } IReconcilingStrategy spellingStrategy = getSpellcheckStrategy(); if (spellingStrategy != null) { spellingStrategy.reconcile(new Region(0, document.getLength())); } //if there is a folding strategy then reconcile it if(getFoldingStrategy() != null) { getFoldingStrategy().reconcile(new Region(0, document.getLength())); } } } /** * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#install(org.eclipse.jface.text.ITextViewer) */ public void install(ITextViewer textViewer) { super.install(textViewer); //determine if validation is enabled this.fValidationEnabled = SSEUIPlugin.getInstance().getPreferenceStore().getBoolean( CommonEditorPreferenceNames.EVALUATE_TEMPORARY_PROBLEMS); } /** * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#uninstall() */ public void uninstall() { if (isInstalled()) { IReconcilingStrategy validatorStrategy = getValidatorStrategy(); if (validatorStrategy != null) { if (validatorStrategy instanceof IReleasable) ((IReleasable) validatorStrategy).release(); } if (fSpellcheckStrategy != null) { fSpellcheckStrategy.setDocument(null); fSpellcheckStrategy = null; } fReconcileListeners = new ISourceReconcilingListener[0]; if (getDocument() instanceof IStructuredDocument) { RegionParser parser = ((IStructuredDocument) getDocument()).getParser(); if (parser instanceof StructuredDocumentRegionParser) { ((StructuredDocumentRegionParser) parser).removeStructuredDocumentRegionHandler(fResetHandler); } } } super.uninstall(); } }