diff --git a/projectapi/apichanges.xml b/projectapi/apichanges.xml --- a/projectapi/apichanges.xml +++ b/projectapi/apichanges.xml @@ -108,6 +108,24 @@ + + + Added LookupProviderSupport.createActionProviderMerger factory method + + + + + +

+ Added LookupProviderSupport.createActionProviderMerger factory method to + create a LookupMerger for merging multiple instances of ActionProvider + in the project's Lookup. +

+
+ + +
+ Added ProjectIconAnnotator diff --git a/projectapi/manifest.mf b/projectapi/manifest.mf --- a/projectapi/manifest.mf +++ b/projectapi/manifest.mf @@ -1,7 +1,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.projectapi/1 OpenIDE-Module-Install: org/netbeans/modules/projectapi/Installer.class -OpenIDE-Module-Specification-Version: 1.37 +OpenIDE-Module-Specification-Version: 1.38 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/projectapi/layer.xml diff --git a/projectapi/src/org/netbeans/spi/project/support/LookupProviderSupport.java b/projectapi/src/org/netbeans/spi/project/support/LookupProviderSupport.java --- a/projectapi/src/org/netbeans/spi/project/support/LookupProviderSupport.java +++ b/projectapi/src/org/netbeans/spi/project/support/LookupProviderSupport.java @@ -45,11 +45,15 @@ package org.netbeans.spi.project.support; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.api.project.SourceGroup; import org.netbeans.api.project.Sources; +import org.netbeans.spi.project.ActionProvider; import org.netbeans.spi.project.LookupMerger; import org.netbeans.spi.project.LookupProvider; import org.openide.util.ChangeSupport; @@ -93,7 +97,19 @@ public static LookupMerger createSourcesMerger() { return new SourcesMerger(); } - + + /** + * Factory method for creating {@link org.netbeans.spi.project.LookupMerger} instance that merges + * {@link org.netbeans.spi.project.ActionProvider} instances in the project lookup. + * When more {@link org.netbeans.spi.project.ActionProvider}s support the same command the first + * {@link org.netbeans.spi.project.ActionProvider} is used to perform the command. + * @return instance to include in project lookup + * @since 1.38 + */ + public static LookupMerger createActionProviderMerger() { + return new ActionProviderMerger(); + } + private static class SourcesMerger implements LookupMerger { public @Override Class getMergeableClass() { return Sources.class; @@ -103,7 +119,7 @@ return new SourcesImpl(lookup); } } - + private static class SourcesImpl implements Sources, ChangeListener, LookupListener { private final ChangeSupport changeSupport = new ChangeSupport(this); private Lookup.Result delegates; @@ -174,5 +190,80 @@ changeSupport.fireChange(); } } - + + private static final class ActionProviderMerger implements LookupMerger { + @Override + public Class getMergeableClass() { + return ActionProvider.class; + } + + @Override + public ActionProvider merge(final Lookup lookup) { + return new MergedActionProvider(lookup); + } + } + + private static final class MergedActionProvider implements ActionProvider, LookupListener { + + private final Lookup.Result lkpResult; + @SuppressWarnings("VolatileArrayField") + private volatile String[] actionNamesCache; + + private MergedActionProvider(final Lookup lkp) { + this.lkpResult = lkp.lookupResult(ActionProvider.class); + this.lkpResult.addLookupListener(this); + } + + @Override + public String[] getSupportedActions() { + String[] result = actionNamesCache; + if (result == null) { + final Set actionNames = new LinkedHashSet (); + for (ActionProvider ap : lkpResult.allInstances()) { + actionNames.addAll(Arrays.asList(ap.getSupportedActions())); + } + result = actionNames.toArray(new String[actionNames.size()]); + actionNamesCache = result; + } + assert result != null; + return result; + } + + @Override + public boolean isActionEnabled(String command, Lookup context) throws IllegalArgumentException { + boolean found = false; + for (ActionProvider ap : lkpResult.allInstances()) { + if (Arrays.asList(ap.getSupportedActions()).contains(command)) { + if (ap.isActionEnabled(command, context)) { + return true; + } else { + found = true; + } + } + } + if (found) { + return false; + } else { + throw new IllegalArgumentException(command); + } + } + + @Override + public void invokeAction(String command, Lookup context) throws IllegalArgumentException { + for (ActionProvider ap : lkpResult.allInstances()) { + if (Arrays.asList(ap.getSupportedActions()).contains(command) && + ap.isActionEnabled(command, context)) { + ap.invokeAction(command, context); + return; + } + } + throw new IllegalArgumentException(String.format(command)); + } + + @Override + public void resultChanged(LookupEvent ev) { + actionNamesCache = null; + } + + } } diff --git a/projectapi/test/unit/src/org/netbeans/spi/project/support/LookupProviderSupportTest.java b/projectapi/test/unit/src/org/netbeans/spi/project/support/LookupProviderSupportTest.java --- a/projectapi/test/unit/src/org/netbeans/spi/project/support/LookupProviderSupportTest.java +++ b/projectapi/test/unit/src/org/netbeans/spi/project/support/LookupProviderSupportTest.java @@ -50,6 +50,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.swing.Icon; @@ -57,6 +58,8 @@ import org.netbeans.api.project.SourceGroup; import org.netbeans.api.project.Sources; import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.project.ActionProvider; +import org.netbeans.spi.project.LookupMerger; import org.netbeans.spi.project.LookupProvider; import org.openide.filesystems.FileObject; import org.openide.util.Lookup; @@ -142,7 +145,83 @@ assertEquals(3, grps.length); } - + + public void testActionProviderMerger() throws Exception { + final ActionProviderImpl ap1 = new ActionProviderImpl(new LinkedHashMap(){ + { + put(ActionProvider.COMMAND_CLEAN,Boolean.TRUE); + put(ActionProvider.COMMAND_BUILD,Boolean.TRUE); + put(ActionProvider.COMMAND_REBUILD,Boolean.TRUE); + put(ActionProvider.COMMAND_COMPILE_SINGLE,Boolean.FALSE); + } + }); + final ActionProviderImpl ap2 = new ActionProviderImpl(new LinkedHashMap(){ + { + put(ActionProvider.COMMAND_RUN,Boolean.TRUE); + put(ActionProvider.COMMAND_TEST,Boolean.TRUE); + put(ActionProvider.COMMAND_DEBUG,Boolean.FALSE); + put(ActionProvider.COMMAND_COMPILE_SINGLE,Boolean.TRUE); + } + }); + + final LookupMerger merger = LookupProviderSupport.createActionProviderMerger(); + assertEquals(ActionProvider.class,merger.getMergeableClass()); + final ActionProvider ap = merger.merge(Lookups.fixed(ap1,ap2)); + assertEquals(Arrays.asList(new String[] { + ActionProvider.COMMAND_CLEAN, + ActionProvider.COMMAND_BUILD, + ActionProvider.COMMAND_REBUILD, + ActionProvider.COMMAND_COMPILE_SINGLE, + ActionProvider.COMMAND_RUN, + ActionProvider.COMMAND_TEST, + ActionProvider.COMMAND_DEBUG, + }), + Arrays.asList(ap.getSupportedActions())); + assertTrue(ap.isActionEnabled(ActionProvider.COMMAND_CLEAN, Lookup.EMPTY)); + assertTrue(ap.isActionEnabled(ActionProvider.COMMAND_BUILD, Lookup.EMPTY)); + assertTrue(ap.isActionEnabled(ActionProvider.COMMAND_REBUILD, Lookup.EMPTY)); + assertTrue(ap.isActionEnabled(ActionProvider.COMMAND_COMPILE_SINGLE, Lookup.EMPTY)); + assertTrue(ap.isActionEnabled(ActionProvider.COMMAND_RUN, Lookup.EMPTY)); + assertTrue(ap.isActionEnabled(ActionProvider.COMMAND_TEST, Lookup.EMPTY)); + assertFalse(ap.isActionEnabled(ActionProvider.COMMAND_DEBUG, Lookup.EMPTY)); + try { + ap.isActionEnabled(ActionProvider.COMMAND_MOVE, Lookup.EMPTY); + throw new AssertionError("IAE should be thrown"); //NOI18N + } catch (IllegalArgumentException iae) { + assertEquals(ActionProvider.COMMAND_MOVE, iae.getMessage()); + } + ap.invokeAction(ActionProvider.COMMAND_CLEAN, Lookup.EMPTY); + assertEquals(ActionProvider.COMMAND_CLEAN,ap1.cleanInvokedTarget()); + assertNull(ap2.cleanInvokedTarget()); + ap.invokeAction(ActionProvider.COMMAND_BUILD, Lookup.EMPTY); + assertEquals(ActionProvider.COMMAND_BUILD,ap1.cleanInvokedTarget()); + assertNull(ap2.cleanInvokedTarget()); + ap.invokeAction(ActionProvider.COMMAND_REBUILD, Lookup.EMPTY); + assertEquals(ActionProvider.COMMAND_REBUILD,ap1.cleanInvokedTarget()); + assertNull(ap2.cleanInvokedTarget()); + ap.invokeAction(ActionProvider.COMMAND_COMPILE_SINGLE, Lookup.EMPTY); + assertNull(ap1.cleanInvokedTarget()); + assertEquals(ActionProvider.COMMAND_COMPILE_SINGLE,ap2.cleanInvokedTarget()); + ap.invokeAction(ActionProvider.COMMAND_RUN, Lookup.EMPTY); + assertNull(ap1.cleanInvokedTarget()); + assertEquals(ActionProvider.COMMAND_RUN,ap2.cleanInvokedTarget()); + ap.invokeAction(ActionProvider.COMMAND_TEST, Lookup.EMPTY); + assertNull(ap1.cleanInvokedTarget()); + assertEquals(ActionProvider.COMMAND_TEST,ap2.cleanInvokedTarget()); + try { + ap.invokeAction(ActionProvider.COMMAND_DEBUG, Lookup.EMPTY); + throw new AssertionError("IAE should be thrown"); //NOI18N + } catch (IllegalArgumentException iae) { + assertEquals(ActionProvider.COMMAND_DEBUG, iae.getMessage()); + } + try { + ap.invokeAction(ActionProvider.COMMAND_MOVE, Lookup.EMPTY); + throw new AssertionError("IAE should be thrown"); //NOI18N + } catch (IllegalArgumentException iae) { + assertEquals(ActionProvider.COMMAND_MOVE, iae.getMessage()); + } + } + public void testNonexistentPath() throws Exception { // #87544: don't choke on a nonexistent path! Just leave it empty. Lookup l = LookupProviderSupport.createCompositeLookup(Lookup.EMPTY, "nowhere"); @@ -237,5 +316,38 @@ return name; } } - + + private static class ActionProviderImpl implements ActionProvider { + private final Map supportedCommands; + private String invokedTarget; + + public ActionProviderImpl (final Map supportedCommands) { + this.supportedCommands = supportedCommands; + } + + @Override + public String[] getSupportedActions() { + return supportedCommands.keySet().toArray(new String[supportedCommands.size()]); + } + + @Override + public void invokeAction(String command, Lookup context) throws IllegalArgumentException { + invokedTarget = command; + } + + @Override + public boolean isActionEnabled(String command, Lookup context) throws IllegalArgumentException { + final Boolean res = supportedCommands.get(command); + if (res == null) { + throw new IllegalArgumentException(command); + } + return res; + } + + String cleanInvokedTarget() { + final String res = invokedTarget; + invokedTarget = null; + return res; + } + } }