From 1e269a4a7a700e15ed8c7071b74a8a53a5f0215e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sun, 2 Jun 2024 07:06:03 +0200 Subject: [PATCH] Add a project config option to have validators for all features Currently a validator has to be configured for each feature, if one wants to validate all features in a project this become cumbersome. This adds a configuration option to enable validators for the whole project. --- .../java/JavaBackendAdapterFactory.java | 2 +- .../properties/JavaBackendPropertyPage.java | 134 --------------- .../properties/CucumberPropertiesPage.java | 36 ---- .../.settings/cucumber.backend.java.prefs | 2 + .../examples/datatable/GlobalValidator.java | 24 +++ .../cucumber/examples/datatable2.feature | 47 ++++++ io.cucumber.eclipse.editor/plugin.xml | 10 +- .../properties/CucumberPropertiesPage.java | 50 ++++++ io.cucumber.eclipse.java/plugin.xml | 9 + .../properties/JavaBackendPropertyPage.java | 154 ++++++++++++++++++ .../validation/CucumberGlueValidator.java | 88 +++++++--- 11 files changed, 364 insertions(+), 192 deletions(-) delete mode 100644 cucumber.eclipse.backends.java/src/cucumber/eclipse/backends/java/properties/JavaBackendPropertyPage.java delete mode 100644 cucumber.eclipse.editor/src/main/java/cucumber/eclipse/editor/properties/CucumberPropertiesPage.java create mode 100644 examples/java-datatable/.settings/cucumber.backend.java.prefs create mode 100644 examples/java-datatable/src/main/java/cucumber/examples/datatable/GlobalValidator.java create mode 100644 examples/java-datatable/src/test/resources/cucumber/examples/datatable2.feature create mode 100644 io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/properties/CucumberPropertiesPage.java create mode 100644 io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/properties/JavaBackendPropertyPage.java diff --git a/cucumber.eclipse.backends.java/src/cucumber/eclipse/backends/java/JavaBackendAdapterFactory.java b/cucumber.eclipse.backends.java/src/cucumber/eclipse/backends/java/JavaBackendAdapterFactory.java index 11c65a43..1d62cb0b 100644 --- a/cucumber.eclipse.backends.java/src/cucumber/eclipse/backends/java/JavaBackendAdapterFactory.java +++ b/cucumber.eclipse.backends.java/src/cucumber/eclipse/backends/java/JavaBackendAdapterFactory.java @@ -23,13 +23,13 @@ import org.eclipse.jdt.launching.JavaRuntime; import cucumber.api.TypeRegistryConfigurer; -import cucumber.eclipse.backends.java.properties.JavaBackendPropertyPage; import cucumber.eclipse.steps.integration.KeyWordProvider; import cucumber.runtime.DefaultTypeRegistryConfiguration; import cucumber.runtime.Reflections; import cucumber.runtime.io.MultiLoader; import cucumber.runtime.io.ResourceLoaderClassFinder; import io.cucumber.cucumberexpressions.ExpressionFactory; +import io.cucumber.eclipse.java.properties.JavaBackendPropertyPage; import io.cucumber.stepexpression.TypeRegistry; public class JavaBackendAdapterFactory implements IAdapterFactory, IResourceChangeListener { diff --git a/cucumber.eclipse.backends.java/src/cucumber/eclipse/backends/java/properties/JavaBackendPropertyPage.java b/cucumber.eclipse.backends.java/src/cucumber/eclipse/backends/java/properties/JavaBackendPropertyPage.java deleted file mode 100644 index 39a05065..00000000 --- a/cucumber.eclipse.backends.java/src/cucumber/eclipse/backends/java/properties/JavaBackendPropertyPage.java +++ /dev/null @@ -1,134 +0,0 @@ -package cucumber.eclipse.backends.java.properties; - -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.ProjectScope; -import org.eclipse.core.runtime.preferences.IEclipsePreferences; -import org.eclipse.jface.preference.PreferencePage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.dialogs.PropertyPage; -import org.osgi.service.prefs.BackingStoreException; - -public class JavaBackendPropertyPage extends PropertyPage { - - private static final String KEY_GLUE = "glue"; - private static final String KEY_ENABLED = "enabled"; - private static final String NAMESPACE = "cucumber.backend.java"; - private Text glueOption; - private Button enableOption; - private Label glueLabel; - - /** - * @see PreferencePage#createContents(Composite) - */ - protected Control createContents(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - GridData data = new GridData(GridData.FILL_BOTH); - data.grabExcessHorizontalSpace = true; - composite.setLayoutData(data); - createInfo(composite); - enableOption = createEnableOption(composite); - glueOption = createGlueOption(composite); - updateUIState(); - return composite; - } - - private Button createEnableOption(Composite composite) { - Button button = new Button(composite, SWT.CHECK); - GridData gd = new GridData(); - button.setLayoutData(gd); - gd.horizontalSpan = 2; - button.setText("Enable Java Backend for project"); - button.setSelection(isBackendEnabled(getResource())); - button.addSelectionListener(new SelectionListener() { - - @Override - public void widgetSelected(SelectionEvent e) { - updateUIState(); - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - - } - }); - return button; - } - - private IResource getResource() { - return getElement().getAdapter(IResource.class); - } - - public static boolean isBackendEnabled(IResource resource) { - IEclipsePreferences node = getNode(resource); - return node.getBoolean(KEY_ENABLED, false); - } - - public static String getGlueOption(IResource resource) { - IEclipsePreferences node = getNode(resource); - return node.get(KEY_GLUE, ""); - } - - private static IEclipsePreferences getNode(IResource resource) { - ProjectScope scope = new ProjectScope(resource.getProject()); - IEclipsePreferences node = scope.getNode(NAMESPACE); - return node; - } - - protected void updateUIState() { - if (enableOption != null && !enableOption.isDisposed()) { - boolean enabled = enableOption.getSelection(); - glueOption.setEnabled(enabled); - glueLabel.setEnabled(enabled); - } - } - - private Text createGlueOption(Composite composite) { - glueLabel = new Label(composite, SWT.NONE); - glueLabel.setText("Glue: "); - Text text = new Text(composite, SWT.BORDER); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - text.setLayoutData(gd); - text.setText(getGlueOption(getResource())); - text.setToolTipText( - "The Glue option defines which packages are scanned for glue code, separate multiple packages by comma"); - return text; - } - - private void createInfo(Composite parent) { - Label label = new Label(parent, SWT.WRAP); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - label.setLayoutData(gd); - gd.grabExcessHorizontalSpace = true; - gd.horizontalSpan = 2; - label.setText( - "Here you can configure the Options for the Java Backend, options relate closely to those used in the CLI"); - new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(gd); - } - - protected void performDefaults() { - super.performDefaults(); - glueOption.setText(""); - } - - public boolean performOk() { - IEclipsePreferences node = getNode(getResource()); - node.put(KEY_GLUE, glueOption.getText()); - node.putBoolean(KEY_ENABLED, enableOption.getSelection()); - try { - node.flush(); - } catch (BackingStoreException e) { - } - return true; - } - -} diff --git a/cucumber.eclipse.editor/src/main/java/cucumber/eclipse/editor/properties/CucumberPropertiesPage.java b/cucumber.eclipse.editor/src/main/java/cucumber/eclipse/editor/properties/CucumberPropertiesPage.java deleted file mode 100644 index c9fb1e6d..00000000 --- a/cucumber.eclipse.editor/src/main/java/cucumber/eclipse/editor/properties/CucumberPropertiesPage.java +++ /dev/null @@ -1,36 +0,0 @@ -package cucumber.eclipse.editor.properties; - -import org.eclipse.jface.preference.PreferencePage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.ui.dialogs.PropertyPage; - -public class CucumberPropertiesPage extends PropertyPage { - - public CucumberPropertiesPage() { - super(); - } - - - /** - * @see PreferencePage#createContents(Composite) - */ - protected Control createContents(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); - composite.setLayout(new GridLayout()); - GridData data = new GridData(GridData.FILL); - data.grabExcessHorizontalSpace = true; - composite.setLayoutData(data); - Label label = new Label(composite, SWT.WRAP); - label.setText("You can configure Cucumber related properties for your project here"); - label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - return composite; - } - - - -} \ No newline at end of file diff --git a/examples/java-datatable/.settings/cucumber.backend.java.prefs b/examples/java-datatable/.settings/cucumber.backend.java.prefs new file mode 100644 index 00000000..aa2d89b2 --- /dev/null +++ b/examples/java-datatable/.settings/cucumber.backend.java.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +validationPlugins=cucumber.examples.datatable.GlobalValidator diff --git a/examples/java-datatable/src/main/java/cucumber/examples/datatable/GlobalValidator.java b/examples/java-datatable/src/main/java/cucumber/examples/datatable/GlobalValidator.java new file mode 100644 index 00000000..32fdd577 --- /dev/null +++ b/examples/java-datatable/src/main/java/cucumber/examples/datatable/GlobalValidator.java @@ -0,0 +1,24 @@ +package cucumber.examples.datatable; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.cucumber.plugin.Plugin; + +/** + * This validator is enabled globally in the preferences + */ +public class GlobalValidator implements Plugin { + + private ConcurrentHashMap errors = new ConcurrentHashMap<>(); + + public GlobalValidator() { + errors.put(1, "Tis is an error from the global validator, but you can't do anything about it!"); + } + + // This is a magic method called by cucumber-eclipse to fetch the final errors and display them in the document + public Map getValidationErrors() { + return errors; + } + +} diff --git a/examples/java-datatable/src/test/resources/cucumber/examples/datatable2.feature b/examples/java-datatable/src/test/resources/cucumber/examples/datatable2.feature new file mode 100644 index 00000000..0a4a7db0 --- /dev/null +++ b/examples/java-datatable/src/test/resources/cucumber/examples/datatable2.feature @@ -0,0 +1,47 @@ +#language: en +#This Feature has no validator enabled in the feature file but in the project preferences! +Feature: Connection between DataTable Key and a specific Step Value + + + Scenario: All Keys are related to a Step Value. Example 1 + Given the animal "Cat" + | Key | Value | + | Color | Black | + | Lifespan | 3 | + | Whiskers | 24 | + + Then the food is "fish" + + + Scenario: All Keys are related to a Step Value. Example 2 + Given the animal "Elephant" + | Key | Value | + | Color | Grey | + | Lifespan | 70 | + | Trunk | 1.8 | + | Tusk | 1.3 | + + Then the food is "leaves" + + + Scenario: There are some unrelated Keys to a Step Value. This Keys are available for other Step Value + Given the animal "Cat" + | Key | Value | + | Color | Black | + | Lifespan | 3 | + | Whiskers | 24 | + | Trunk | 1.8 | + + Then the food is "fish" + + + Scenario: There are some unrelated Keys to a Step Value. This Keys are not available for each Step Value + Given the animal "Cat" + | Key | Value | + | Color | Black | + | Lifespan | 3 | + | Whiskers | 24 | + | Wings | 2 | + + Then the food is "fish" + diff --git a/io.cucumber.eclipse.editor/plugin.xml b/io.cucumber.eclipse.editor/plugin.xml index cb437a59..41f94a91 100644 --- a/io.cucumber.eclipse.editor/plugin.xml +++ b/io.cucumber.eclipse.editor/plugin.xml @@ -323,6 +323,14 @@ - + + + + diff --git a/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/properties/CucumberPropertiesPage.java b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/properties/CucumberPropertiesPage.java new file mode 100644 index 00000000..457bac6b --- /dev/null +++ b/io.cucumber.eclipse.editor/src/io/cucumber/eclipse/editor/properties/CucumberPropertiesPage.java @@ -0,0 +1,50 @@ +package io.cucumber.eclipse.editor.properties; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.PropertyPage; + +import io.cucumber.eclipse.editor.Images; + +public class CucumberPropertiesPage extends PropertyPage { + + private static final String NAMESPACE = "io.cucumber.eclipse.editor"; + private Text validationPlugins; + + public CucumberPropertiesPage() { + setTitle("Cucumber"); + setDescription("You can configure Cucumber related properties for your project here"); + setImageDescriptor(Images.getCukesIconDescriptor()); + noDefaultAndApplyButton(); + } + + /** + * @see PreferencePage#createContents(Composite) + */ + @Override + protected Control createContents(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + composite.setLayout(layout); + return composite; + } + + private IResource getResource() { + return getElement().getAdapter(IResource.class); + } + + public static IEclipsePreferences getNode(IResource resource) { + ProjectScope scope = new ProjectScope(resource.getProject()); + IEclipsePreferences node = scope.getNode(NAMESPACE); + return node; + } + +} \ No newline at end of file diff --git a/io.cucumber.eclipse.java/plugin.xml b/io.cucumber.eclipse.java/plugin.xml index f99374ae..f839965b 100644 --- a/io.cucumber.eclipse.java/plugin.xml +++ b/io.cucumber.eclipse.java/plugin.xml @@ -77,4 +77,13 @@ name="Java"> + + + + diff --git a/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/properties/JavaBackendPropertyPage.java b/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/properties/JavaBackendPropertyPage.java new file mode 100644 index 00000000..5aedcede --- /dev/null +++ b/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/properties/JavaBackendPropertyPage.java @@ -0,0 +1,154 @@ +package io.cucumber.eclipse.java.properties; + +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.internal.ui.actions.ActionMessages; +import org.eclipse.jdt.internal.ui.dialogs.OpenTypeSelectionDialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.BorderData; +import org.eclipse.swt.layout.BorderLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.PropertyPage; +import org.osgi.service.prefs.BackingStoreException; + +public class JavaBackendPropertyPage extends PropertyPage { + + private static final String KEY_VALIDATION_PLUGINS = "validationPlugins"; + private static final String NAMESPACE = "cucumber.backend.java"; + private Text validationPlugins; + + public JavaBackendPropertyPage() { + setTitle("Cucumber Java Options"); + setDescription("Here you can configure Java related Cucumber options"); + } + + /** + * @see PreferencePage#createContents(Composite) + */ + @Override + protected Control createContents(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + composite.setLayout(layout); + addValidationOption(composite); + return composite; + } + + @SuppressWarnings("restriction") + private void addValidationOption(Composite parent) { + Label label = new Label(parent, SWT.NONE); + label.setText("Validation Plugins "); + label.setToolTipText( + "A comma seperated list of plugins that should be used for validation regardless of feature settings"); + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new BorderLayout()); + validationPlugins = new Text(composite, SWT.BORDER); + validationPlugins.setLayoutData(new BorderData(SWT.CENTER)); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + composite.setLayoutData(data); + validationPlugins.setText(getValidationPluginsOption(getResource()).collect(joinPlugins())); + Button button = new Button(composite, SWT.PUSH); + button.setText("Add"); + button.setLayoutData(new BorderData(SWT.RIGHT)); + button.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + OpenTypeSelectionDialog dialog = new OpenTypeSelectionDialog(parent.getShell(), false, + PlatformUI.getWorkbench().getProgressService(), SearchEngine.createWorkspaceScope(), + IJavaSearchConstants.TYPE); + + dialog.setTitle(ActionMessages.OpenTypeInHierarchyAction_dialogTitle); + dialog.setMessage(ActionMessages.OpenTypeInHierarchyAction_dialogMessage); + int result = dialog.open(); + if (result != IDialogConstants.OK_ID) + return; + + Object[] types = dialog.getResult(); + if (types != null && types.length > 0) { + validationPlugins + .setText(Stream + .concat(parsePlugins(validationPlugins.getText()), + Stream.of(types).filter(IType.class::isInstance).map(IType.class::cast) + .map(IType::getFullyQualifiedName)) + .collect(joinPlugins())); + } + + } + + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + + } + }); + + } + + private IResource getResource() { + return getElement().getAdapter(IResource.class); + } + + private static Collector joinPlugins() { + return Collectors.joining(", "); + } + + public static Stream getValidationPluginsOption(IResource resource) { + if (resource == null) { + return Stream.empty(); + } + IEclipsePreferences node = getNode(resource); + String string = node.get(KEY_VALIDATION_PLUGINS, ""); + return parsePlugins(string); + } + + private static Stream parsePlugins(String string) { + return Arrays.stream(string.split(",")).map(String::trim).filter(Predicate.not(String::isBlank)); + } + + public static IEclipsePreferences getNode(IResource resource) { + ProjectScope scope = new ProjectScope(resource.getProject()); + IEclipsePreferences node = scope.getNode(NAMESPACE); + return node; + } + + @Override + protected void performDefaults() { + super.performDefaults(); + validationPlugins.setText(""); + } + + @Override + public boolean performOk() { + IEclipsePreferences node = getNode(getResource()); + node.put(KEY_VALIDATION_PLUGINS, validationPlugins.getText()); + try { + node.flush(); + } catch (BackingStoreException e) { + } + return true; + } + +} diff --git a/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/validation/CucumberGlueValidator.java b/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/validation/CucumberGlueValidator.java index cbf3d15d..095767c2 100644 --- a/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/validation/CucumberGlueValidator.java +++ b/io.cucumber.eclipse.java/src/io/cucumber/eclipse/java/validation/CucumberGlueValidator.java @@ -7,8 +7,10 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; @@ -26,6 +28,9 @@ import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; @@ -45,6 +50,7 @@ import io.cucumber.eclipse.java.plugins.CucumberStepDefinition; import io.cucumber.eclipse.java.plugins.CucumberStepParserPlugin; import io.cucumber.eclipse.java.plugins.MatchedStep; +import io.cucumber.eclipse.java.properties.JavaBackendPropertyPage; import io.cucumber.eclipse.java.runtime.CucumberRuntime; import io.cucumber.plugin.Plugin; @@ -97,7 +103,11 @@ public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) { public void bufferDisposed(IFileBuffer buffer) { if (buffer instanceof ITextFileBuffer) { IDocument document = ((ITextFileBuffer) buffer).getDocument(); - jobMap.remove(document); + GlueJob remove = jobMap.remove(document); + if (remove != null) { + remove.cancel(); + remove.disposeListener(); + } } } @@ -120,7 +130,7 @@ public void bufferContentAboutToBeReplaced(IFileBuffer buffer) { } public static void revalidate(IDocument document) { - validate(document, 0, false); + validate(document, 0); } @Override @@ -130,7 +140,7 @@ public void setup(IDocument document) { @Override public void documentChanged(DocumentEvent event) { // TODO configurable - validate(document, 1000, false); + validate(document, 1000); } @Override @@ -138,15 +148,16 @@ public void documentAboutToBeChanged(DocumentEvent event) { } }); - validate(document, 0, false); + validate(document, 0); } - private static void validate(IDocument document, int delay, boolean persistent) { + private static void validate(IDocument document, int delay) { jobMap.compute(document, (key, oldJob) -> { - if (oldJob != null && !oldJob.persistent) { + if (oldJob != null) { oldJob.cancel(); + oldJob.disposeListener(); } - GlueJob verificationJob = new GlueJob(oldJob, document, persistent); + GlueJob verificationJob = new GlueJob(oldJob, document); verificationJob.setUser(false); verificationJob.setPriority(Job.DECORATE); if (delay > 0) { @@ -206,17 +217,16 @@ public static Collection getAvaiableSteps(IDocument docu private static final class GlueJob extends Job { private GlueJob oldJob; - private IDocument document; - private boolean persistent; + private final IDocument document; + private Runnable listenerRegistration; - transient Collection> matchedSteps; - transient Collection parsedSteps; + volatile Collection> matchedSteps; + volatile Collection parsedSteps; - public GlueJob(GlueJob oldJob, IDocument document, boolean persistent) { + public GlueJob(GlueJob oldJob, IDocument document) { super("Verify Cucumber Glue Code"); this.oldJob = oldJob; this.document = document; - this.persistent = persistent; if (oldJob != null) { this.matchedSteps = oldJob.matchedSteps; this.parsedSteps = oldJob.parsedSteps; @@ -226,11 +236,28 @@ public GlueJob(GlueJob oldJob, IDocument document, boolean persistent) { } } + @Override + protected void canceling() { + disposeListener(); + } + + protected void disposeListener() { + synchronized (this) { + if (listenerRegistration != null) { + listenerRegistration.run(); + listenerRegistration = () -> { + // dummy to prevent further registration... + }; + } + } + } + @Override protected IStatus run(IProgressMonitor monitor) { if (oldJob != null) { try { oldJob.join(); + oldJob = null; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return Status.CANCEL_STATUS; @@ -267,8 +294,8 @@ protected IStatus run(IProgressMonitor monitor) { addErrors(plugin, validationErrors); } Map> snippets = missingStepsPlugin.getSnippets(); - MarkerFactory.validationErrorOnStepDefinition(resource, validationErrors, persistent); - MarkerFactory.missingSteps(resource, snippets, Activator.PLUGIN_ID, persistent); + MarkerFactory.validationErrorOnStepDefinition(resource, validationErrors, false); + MarkerFactory.missingSteps(resource, snippets, Activator.PLUGIN_ID, false); Collection steps = stepParserPlugin.getStepList(); matchedSteps = Collections.unmodifiableCollection(matchedStepsPlugin.getMatchedSteps()); parsedSteps = Collections.unmodifiableCollection(stepParserPlugin.getStepList()); @@ -312,6 +339,7 @@ private Collection addValidationPlugins(GherkinEditorDocument editorDocu List validationPlugins = new ArrayList<>(); IDocument doc = editorDocument.getDocument(); int lines = doc.getNumberOfLines(); + Set plugins = new LinkedHashSet<>(); for (int i = 0; i < lines; i++) { try { IRegion firstLine = document.getLineInformation(i); @@ -319,16 +347,36 @@ private Collection addValidationPlugins(GherkinEditorDocument editorDocu if (line.startsWith("#")) { String[] split = line.split("validation-plugin:", 2); if (split.length == 2) { - String validationPlugin = split[1].trim(); - Plugin classpathPlugin = rt.addPluginFromClasspath(validationPlugin); - if (classpathPlugin != null) { - validationPlugins.add(classpathPlugin); - } + plugins.add(split[1].trim()); } } } catch (BadLocationException e) { } } + JavaBackendPropertyPage.getValidationPluginsOption(editorDocument.getResource()).forEach(plugins::add); + for (String plugin : plugins) { + Plugin classpathPlugin = rt.addPluginFromClasspath(plugin); + if (classpathPlugin != null) { + validationPlugins.add(classpathPlugin); + } + } + IResource resource = editorDocument.getResource(); + if (resource != null) { + synchronized (this) { + if (listenerRegistration == null) { + IEclipsePreferences node = JavaBackendPropertyPage.getNode(resource); + IPreferenceChangeListener listener = new IPreferenceChangeListener() { + + @Override + public void preferenceChange(PreferenceChangeEvent event) { + schedule(); + } + }; + node.addPreferenceChangeListener(listener); + listenerRegistration = () -> node.removePreferenceChangeListener(listener); + } + } + } return validationPlugins; }