/* * Copyright (c) 2013 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.ui.functions.groovy; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import org.codehaus.groovy.control.ErrorCollector; import org.codehaus.groovy.control.MultipleCompilationErrorsException; import org.codehaus.groovy.syntax.SyntaxException; import org.eclipse.jface.bindings.TriggerSequence; import org.eclipse.jface.bindings.keys.KeySequence; import org.eclipse.jface.bindings.keys.SWTKeySupport; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.AnnotationModel; import org.eclipse.jface.text.source.AnnotationRulerColumn; import org.eclipse.jface.text.source.CompositeRuler; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IOverviewRuler; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.LineNumberRulerColumn; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.wizard.Wizard; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.ToolBar; import com.google.common.collect.Iterators; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import eu.esdihumboldt.cst.functions.groovy.GroovyConstants; import eu.esdihumboldt.hale.ui.common.definition.DefinitionImages; import eu.esdihumboldt.hale.ui.functions.groovy.internal.GroovyASTTray; import eu.esdihumboldt.hale.ui.util.ColorManager; import eu.esdihumboldt.hale.ui.util.groovy.GroovyColorManager; import eu.esdihumboldt.hale.ui.util.groovy.GroovySourceViewerUtil; import eu.esdihumboldt.hale.ui.util.groovy.SimpleGroovySourceViewerConfiguration; import eu.esdihumboldt.hale.ui.util.groovy.ast.GroovyAST; import eu.esdihumboldt.hale.ui.util.groovy.ast.GroovyASTSourceCompiler; import eu.esdihumboldt.hale.ui.util.source.CompilingSourceViewer; import eu.esdihumboldt.hale.ui.util.source.SimpleAnnotationUtil; import eu.esdihumboldt.hale.ui.util.source.SimpleAnnotations; import groovy.lang.Script; /** * Base page for editing a Groovy script for type relations. * * @param <W> the wizard type * @author Simon Templer */ public class GroovyScriptPage<W extends Wizard> extends SourceViewerPage<GroovyAST, W> implements GroovyConstants { private static final ALogger log = ALoggerFactory.getLogger(GroovyScriptPage.class); /** * The Groovy color manager. */ protected final ColorManager colorManager = new GroovyColorManager(); /** * The definition images, e.g. for use with content assist. */ protected final DefinitionImages definitionImages = new DefinitionImages(); private final IAnnotationModel annotationModel = new AnnotationModel(); /** * Default constructor. */ public GroovyScriptPage() { super("groovyScript", PARAMETER_SCRIPT, // use empty default value because we don't know in advance if the // target is complex "", // BINDING_TARGET + " {\n\t\n}", new GroovyASTSourceCompiler()); } @Override protected SourceViewerConfiguration createConfiguration() { return new SimpleGroovySourceViewerConfiguration(colorManager); } @Override protected void createAndSetDocument(SourceViewer viewer) { IDocument doc = new Document(); GroovySourceViewerUtil.setupDocument(doc); annotationModel.connect(doc); doc.set(""); //$NON-NLS-1$ viewer.setDocument(doc, annotationModel); } @Override public void dispose() { colorManager.dispose(); definitionImages.dispose(); super.dispose(); } /** * Handle a completed validation and display the results. * * @param script the executed script if it could be compiled, may be * <code>null</code> * @param exception the error that occurred, <code>null</code> if the * validation was successful * @return if the script was validated successfully */ protected boolean handleValidationResult(Script script, final Exception exception) { setValidationError((exception == null) ? (null) : (exception.getMessage())); // add annotation based on exception if (exception != null) { addErrorAnnotation(script, exception); } // return valid if NPE, as this might be caused by null test values return exception == null || exception instanceof NullPointerException; } /** * Set or reset the validation error on the page. * * @param message the error message or <code>null</code> for no error * @return if the represented state is valid, i.e. if the message is * <code>null</code> */ protected boolean setValidationError(@Nullable final String message) { // set page message getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { if (message == null) { setMessage(null); } else { setMessage(message, ERROR); } } }); return message != null; } /** * Add an error annotation based on the given exception. * * @param script the Groovy script, may be <code>null</code> if it could not * be compiled * @param exception the occurred exception */ private void addErrorAnnotation(Script script, Exception exception) { addGroovyErrorAnnotation(annotationModel, getDocument(), script, exception); } @SuppressWarnings("unchecked") @Override protected boolean validate(String document) { // clear annotations List<Annotation> annotations = new ArrayList<>(); Iterators.addAll(annotations, annotationModel.getAnnotationIterator()); for (Annotation annotation : annotations) { annotationModel.removeAnnotation(annotation); } return super.validate(document); } @Override protected IOverviewRuler createOverviewRuler() { IOverviewRuler ruler = SimpleAnnotationUtil.createDefaultOverviewRuler(14, colorManager, annotationModel); return ruler; } @Override protected IVerticalRuler createVerticalRuler() { final Display display = Display.getCurrent(); CompositeRuler ruler = new CompositeRuler(3); AnnotationRulerColumn annotations = SimpleAnnotationUtil .createDefaultAnnotationRuler(annotationModel); ruler.addDecorator(0, annotations); LineNumberRulerColumn lineNumbers = new LineNumberRulerColumn(); lineNumbers.setBackground(display.getSystemColor(SWT.COLOR_GRAY)); // SWT.COLOR_INFO_BACKGROUND)); lineNumbers.setForeground(display.getSystemColor(SWT.COLOR_BLACK)); // SWT.COLOR_INFO_FOREGROUND)); lineNumbers.setFont(JFaceResources.getTextFont()); ruler.addDecorator(1, lineNumbers); return ruler; } /** * Add an error annotation to the given annotation model based on an * exception that occurred while compiling or executing the Groovy Script. * * @param annotationModel the annotation model * @param document the current document * @param script the executed script, or <code>null</code> * @param exception the occurred exception */ public static void addGroovyErrorAnnotation(IAnnotationModel annotationModel, IDocument document, Script script, Exception exception) { // handle multiple groovy compilation errors if (exception instanceof MultipleCompilationErrorsException) { ErrorCollector errors = ((MultipleCompilationErrorsException) exception) .getErrorCollector(); for (int i = 0; i < errors.getErrorCount(); i++) { SyntaxException ex = errors.getSyntaxError(i); if (ex != null) { addGroovyErrorAnnotation(annotationModel, document, script, ex); } } return; } Annotation annotation = new Annotation(SimpleAnnotations.TYPE_ERROR, false, exception.getLocalizedMessage()); Position position = null; // single syntax exception if (exception instanceof SyntaxException) { int line = ((SyntaxException) exception).getStartLine() - 1; if (line >= 0) { try { position = new Position(document.getLineOffset(line)); } catch (BadLocationException e1) { log.warn("Wrong error position in document", e1); } } } // try to determine position from stack trace of script execution if (position == null && script != null) { for (StackTraceElement ste : exception.getStackTrace()) { if (ste.getClassName().startsWith(script.getClass().getName())) { int line = ste.getLineNumber() - 1; if (line >= 0) { try { position = new Position(document.getLineOffset(line)); break; } catch (BadLocationException e1) { log.warn("Wrong error position in document", e1); } } } } } // fallback if (position == null) { position = new Position(0); } annotationModel.addAnnotation(annotation, position); } @Override protected void addActions(ToolBar toolbar, final CompilingSourceViewer<GroovyAST> viewer) { super.addActions(toolbar, viewer); // GroovyASTTray.createToolItem(toolbar, this, viewer); try { final TriggerSequence astTrigger = KeySequence.getInstance("F8"); viewer.appendVerifyKeyListener(new VerifyKeyListener() { @Override public void verifyKey(VerifyEvent event) { int accelerator = SWTKeySupport.convertEventToUnmodifiedAccelerator(event); KeySequence sequence = KeySequence.getInstance(SWTKeySupport .convertAcceleratorToKeyStroke(accelerator)); if (astTrigger.equals(sequence)) { GroovyASTTray.showTray(GroovyScriptPage.this, viewer); event.doit = false; } } }); } catch (Exception e) { log.error("Error installing AST view listener", e); } } }