/***************************************************************************** * Copyright (c) 2008 CEA LIST. * * * 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: * Remi Schnekenburger (CEA LIST) remi.schnekenburger@cea.fr - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.extensionpoints.editors.ui; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.antlr.runtime.MismatchedTokenException; import org.antlr.runtime.NoViableAltException; import org.antlr.runtime.RecognitionException; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; 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.IRegion; import org.eclipse.jface.text.ISynchronizable; import org.eclipse.jface.text.Position; 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.Annotation; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelExtension; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.papyrus.extensionpoints.editors.configuration.IModelGenerator; import org.eclipse.ui.texteditor.spelling.SpellingContext; /** * */ public class AntlrReconcileStrategy implements IReconcilingStrategy { /** * Spelling problem collector. */ private class ProblemCollector { /** Annotation model. */ private IAnnotationModel fAnnotationModel; /** Annotations to add. */ private Map fAddAnnotations; /** Lock object for modifying the annotations. */ private Object fLockObject; /** * Initializes this collector with the given annotation model. * * @param annotationModel * the annotation model */ public ProblemCollector(IAnnotationModel annotationModel) { Assert.isLegal(annotationModel != null); fAnnotationModel = annotationModel; if(fAnnotationModel instanceof ISynchronizable) fLockObject = ((ISynchronizable)fAnnotationModel).getLockObject(); else fLockObject = fAnnotationModel; } public void accept(Throwable exception) { if(exception instanceof RecognitionException) { fAddAnnotations.put(new ErrorAnnotation(false, exception.getLocalizedMessage()), new Position(((RecognitionException)exception).token.getCharPositionInLine(), ((RecognitionException)exception).token.getText().length())); } } public void accept(IStatus status) { // parse the message of the status to get the line number and position Throwable exception = status.getException(); if(exception instanceof MismatchedTokenException) { addErrorAnnotation((MismatchedTokenException)exception); } else if(exception instanceof NoViableAltException) { addErrorAnnotation((NoViableAltException)exception); } else if(exception instanceof RecognitionException) { addErrorAnnotation((RecognitionException)exception); } } protected Object addErrorAnnotation(RecognitionException exception) { int offset = exception.token.getCharPositionInLine(); String value = exception.token.getText(); int length = (value != null) ? value.length() : 0; StringBuffer buffer = new StringBuffer(); buffer.append('['); buffer.append(offset); buffer.append(','); buffer.append(offset + length); buffer.append("] "); return fAddAnnotations.put(new ErrorAnnotation(true, buffer.toString() + exception), new Position(offset, length)); } protected Object addErrorAnnotation(MismatchedTokenException exception) { int offset = exception.token.getCharPositionInLine(); String value = exception.token.getText(); int length = (value != null) ? value.length() : 0; StringBuffer buffer = new StringBuffer(); buffer.append('['); buffer.append(offset); buffer.append(','); buffer.append(offset + length); buffer.append("] "); buffer.append("Found \'"); buffer.append(value); buffer.append("\' "); buffer.append("excepting "); buffer.append(exception.expecting); return fAddAnnotations.put(new ErrorAnnotation(true, buffer.toString()), new Position(offset, length)); } protected Object addErrorAnnotation(NoViableAltException exception) { int offset = exception.token.getCharPositionInLine(); String value = exception.token.getText(); int length = (value != null) ? value.length() : 0; StringBuffer buffer = new StringBuffer(); buffer.append('['); buffer.append(offset); buffer.append(','); buffer.append(offset + length); buffer.append("] "); buffer.append("Found \'"); buffer.append(value); buffer.append("\' "); buffer.append("Excepting "); buffer.append(exception.grammarDecisionDescription); return fAddAnnotations.put(new ErrorAnnotation(true, buffer.toString()), new Position(offset, length)); } /* * @see org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector#beginCollecting() */ public void beginCollecting() { fAddAnnotations = new HashMap(); } /* * @see org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector#endCollecting() */ public void endCollecting() { List toRemove = new ArrayList(); synchronized(fLockObject) { Iterator iter = fAnnotationModel.getAnnotationIterator(); while(iter.hasNext()) { Annotation annotation = (Annotation)iter.next(); if(ErrorAnnotation.TYPE.equals(annotation.getType())) toRemove.add(annotation); } Annotation[] annotationsToRemove = (Annotation[])toRemove.toArray(new Annotation[toRemove.size()]); if(fAnnotationModel instanceof IAnnotationModelExtension) ((IAnnotationModelExtension)fAnnotationModel).replaceAnnotations(annotationsToRemove, fAddAnnotations); else { for(int i = 0; i < annotationsToRemove.length; i++) fAnnotationModel.removeAnnotation(annotationsToRemove[i]); for(iter = fAddAnnotations.keySet().iterator(); iter.hasNext();) { Annotation annotation = (Annotation)iter.next(); fAnnotationModel.addAnnotation(annotation, (Position)fAddAnnotations.get(annotation)); } } } fAddAnnotations = null; } } /** Text content type */ private static final IContentType TEXT_CONTENT_TYPE = Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT); /** The text editor to operate on. */ private ISourceViewer viewer; /** The document to operate on. */ private IDocument document; /** The progress monitor. */ private IProgressMonitor progressMonitor; /** The spelling context containing the Java source content type. */ private SpellingContext fSpellingContext; private ProblemCollector problemCollector; private IModelGenerator modelGenerator; /** * Creates a new comment reconcile strategy. * * @param viewer * the source viewer * @param spellingService * the spelling service to use */ public AntlrReconcileStrategy(ISourceViewer viewer, IModelGenerator modelGenerator) { Assert.isNotNull(viewer); this.viewer = viewer; this.modelGenerator = modelGenerator; fSpellingContext = new SpellingContext(); fSpellingContext.setContentType(getContentType()); } /* * @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#initialReconcile() */ public void initialReconcile() { reconcile(new Region(0, document.getLength())); } /* * @seeorg.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text. * reconciler.DirtyRegion,org.eclipse.jface.text.IRegion) */ public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { reconcile(new Region(0, document.getLength())); } /* * @see * org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.IRegion * ) */ public void reconcile(final IRegion region) { if(getAnnotationModel() == null) return; // launch the validation using ANTLR try { problemCollector.beginCollecting(); ISafeRunnable runnable = new ISafeRunnable() { public void run() throws Exception { // here, launch check on document String text = document.get(region.getOffset(), region.getLength()); IStatus status = modelGenerator.validate(text); if(!status.isOK()) { if(!status.isMultiStatus()) { problemCollector.accept(status); } else { IStatus[] children = ((MultiStatus)status).getChildren(); for(IStatus child : children) { problemCollector.accept(child); } } } } public void handleException(Throwable x) { } }; SafeRunner.run(runnable); } finally { problemCollector.endCollecting(); } } /** * Returns the content type of the underlying editor input. * * @return the content type of the underlying editor input or <code>null</code> if none could be * determined */ protected IContentType getContentType() { return TEXT_CONTENT_TYPE; } /** * Returns the document which is checked. * * @return the document on which the check is run */ protected final IDocument getDocument() { return document; } /** * Tells this reconciling strategy on which document it will work. This method will be called * before any other method and can be called multiple times. The regions passed to the other * methods always refer to the most recent document passed into this method. * * @param document * the document on which this strategy will work */ public void setDocument(IDocument document) { this.document = document; problemCollector = createProblemCollector(); } /** * Creates a new problem collector. * * @return the collector or <code>null</code> if none is available */ protected ProblemCollector createProblemCollector() { IAnnotationModel model = getAnnotationModel(); if(model == null) { return null; } return new ProblemCollector(model); } /** * Tells this reconciling strategy with which progress monitor it will work. This method will be * called before any other method and can be called multiple times. * * @param monitor * the progress monitor with which this strategy will work */ public final void setProgressMonitor(IProgressMonitor monitor) { progressMonitor = monitor; } /** * Returns the annotation model to be used by this reconcile strategy. * * @return the annotation model of the underlying editor input or <code>null</code> if none * could be determined */ protected IAnnotationModel getAnnotationModel() { return viewer.getAnnotationModel(); } }