/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.alexfalappa.nbspringboot.actions; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collection; import java.util.logging.Logger; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.ui.OpenProjects; import org.netbeans.modules.openide.windows.GlobalActionContextImpl; import org.openide.explorer.ExplorerManager; import org.openide.loaders.DataObject; import org.openide.nodes.Node; import org.openide.util.ContextGlobalProvider; import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.Lookup.Result; import org.openide.util.Lookup.Template; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; import org.openide.util.lookup.AbstractLookup; import org.openide.util.lookup.InstanceContent; import org.openide.util.lookup.ProxyLookup; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINER; import static java.util.logging.Level.SEVERE; /** * This class proxies the original ContextGlobalProvider and ensures the current project remains in the GlobalContext regardless * of the TopComponent selection. The class also ensures that when a child node is selected within the in Projects tab, the parent * Project will be in the lookup. * <p> * To use this class you must have an implementation dependency on org.openide.windows module. * <p> * Taken from http://wiki.netbeans.org/DevFaqAddGlobalContext * * @see ContextGlobalProvider * @see GlobalActionContextImpl * @author Bruce Schubert */ //@ServiceProvider(service = ContextGlobalProvider.class, supersedes = "org.netbeans.modules.openide.windows.GlobalActionContextImpl") public class GlobalActionContextProxy implements ContextGlobalProvider { /** The native NetBeans global context Lookup provider */ private GlobalActionContextImpl globalContextProvider = null; /** Additional content for our proxy lookup */ private InstanceContent content; /** The primary lookup managed by the platform */ private Lookup globalContextLookup; /** The project lookup managed by resultChanged */ private Lookup projectLookup; /** The actual proxyLookup returned by this class */ private Lookup proxyLookup; /** A lookup result that we listen to for Projects */ private Result<Project> resultProjects; /** Listener for changes resultProjects */ private final LookupListener resultListener = new LookupListenerImpl(); /** Listener for changes on the TopComponent registry */ private final PropertyChangeListener registryListener = new RegistryPropertyChangeListener(); /** The last project selected */ private Project lastProject; /** Critical section lock */ private final Object lock = new Object(); private static final Logger logger = Logger.getLogger(GlobalActionContextProxy.class.getName()); public static final String PROJECT_LOGICAL_TAB_ID = "projectTabLogical_tc"; public static final String PROJECT_FILE_TAB_ID = "projectTab_tc"; public GlobalActionContextProxy() { try { this.content = new InstanceContent(); // The default GlobalContextProvider this.globalContextProvider = new GlobalActionContextImpl(); this.globalContextLookup = this.globalContextProvider.createGlobalContext(); // Monitor the activation of the Projects Tab TopComponent TopComponent.getRegistry().addPropertyChangeListener(this.registryListener); // Monitor the existance of a Project in the principle lookup this.resultProjects = globalContextLookup.lookupResult(Project.class); this.resultProjects.addLookupListener(this.resultListener); } catch (Exception e) { Exceptions.printStackTrace(e); } WindowManager.getDefault().invokeWhenUIReady(new Runnable() { @Override public void run() { // Hack to force the current Project selection when the application starts up TopComponent tc = WindowManager.getDefault().findTopComponent(PROJECT_LOGICAL_TAB_ID); if (tc != null) { tc.requestActive(); } } }); } /** * Returns a ProxyLookup that adds the current Project instance to the global selection returned by * Utilities.actionsGlobalContext(). * * @return a ProxyLookup that includes the original global context lookup. */ @Override public Lookup createGlobalContext() { if (proxyLookup == null) { logger.config("Creating a proxy for Utilities.actionsGlobalContext()"); // Create the two lookups that will make up the proxy projectLookup = new AbstractLookup(content); proxyLookup = new ProxyLookup(globalContextLookup, projectLookup); } return proxyLookup; } /** * This class populates the proxy lookup with the currently selected project found in the Projects tab. */ private class RegistryPropertyChangeListener implements PropertyChangeListener { private TopComponent projectsTab = null; @Override public void propertyChange(PropertyChangeEvent event) { if (event.getPropertyName().equals(TopComponent.Registry.PROP_ACTIVATED_NODES) || event.getPropertyName().equals(TopComponent.Registry.PROP_ACTIVATED)) { // Get a reference to the Projects window if (projectsTab == null) { projectsTab = WindowManager.getDefault().findTopComponent(PROJECT_LOGICAL_TAB_ID); if (projectsTab == null) { logger.log(SEVERE, "propertyChange: cannot find the Projects logical window ({0})", PROJECT_LOGICAL_TAB_ID); return; } } // Look for the current project in the Projects window when activated and handle // special case at startup when lastProject hasn't been initialized. Node[] nodes = null; TopComponent activated = TopComponent.getRegistry().getActivated(); if (activated != null && activated.equals(projectsTab)) { logger.finer("propertyChange: processing activated nodes"); nodes = projectsTab.getActivatedNodes(); } else if (lastProject == null) { logger.finer("propertyChange: processing selected nodes"); ExplorerManager em = ((ExplorerManager.Provider) projectsTab).getExplorerManager(); nodes = em.getSelectedNodes(); } // Find and use the first project that owns a node if (nodes != null) { for (Node node : nodes) { Project project = findProjectThatOwnsNode(node); if (project != null) { synchronized (lock) { // Remember this project for when the Project Tab goes out of focus lastProject = project; // Add this project to the proxy if it's not in the global lookup if (!resultProjects.allInstances().contains(lastProject)) { logger.log(FINER, "propertyChange: Found project [{0}] that owns current node.", ProjectUtils.getInformation(lastProject).getDisplayName()); updateProjectLookup(lastProject); } } break; } } } } } } /** * This class listens for changes in the Project results, and ensures a Project remains in the * Utilities.actionsGlobalContext() if a project is open. */ private class LookupListenerImpl implements LookupListener { @Override public void resultChanged(LookupEvent event) { logger.finer("resultChanged: Entered..."); synchronized (lock) { // First, handle projects in the principle lookup if (resultProjects.allInstances().size() > 0) { // Clear the proxy, and remember this project. // Note: not handling multiple selection of projects. clearProjectLookup(); lastProject = resultProjects.allInstances().iterator().next(); logger.log(FINER, "resultChanged: Found project [{0}] in the normal lookup.", ProjectUtils.getInformation(lastProject).getDisplayName()); } else if (OpenProjects.getDefault().getOpenProjects().length == 0) { clearProjectLookup(); lastProject = null; } else { if (lastProject == null) { // Find the project that owns the current Node Node currrentNode = globalContextLookup.lookup(Node.class); Project project = findProjectThatOwnsNode(currrentNode); if (project != null) { lastProject = project; logger.log(FINER, "resultChanged: Found project [{0}] that owns current node.", ProjectUtils.getInformation(lastProject).getDisplayName()); } } // Add the last used project to our internal lookup if (lastProject != null) { updateProjectLookup(lastProject); } } } } } /** * Unconditionally clears the project lookup. */ private void clearProjectLookup() { if (projectLookup != null) { Collection<? extends Project> projects = projectLookup.lookupAll(Project.class); for (Project project : projects) { content.remove(project); } } } /** * Replaces the project lookup content. * * @param project to place in the project lookup. */ private void updateProjectLookup(Project project) { if (project == null) { throw new IllegalArgumentException("project cannot be null."); } // Add the project if an instance of it is not already in the lookup Template<Project> template = new Template<>(Project.class, null, project); if (projectLookup != null && projectLookup.lookupItem(template) == null) { clearProjectLookup(); content.add(project); logger.log(FINE, "updateProjectLookup: added [{0}] to the proxy lookup.", ProjectUtils.getInformation(lastProject).getDisplayName()); } } /** * Recursively searches the node hierarchy for the project that owns a node. * * @param node a node to test for a Project in its or its ancestor's lookup. * @return the Project that owns the node, or null if not found */ private static Project findProjectThatOwnsNode(Node node) { if (node != null) { Project project = node.getLookup().lookup(Project.class); if (project == null) { DataObject dataObject = node.getLookup().lookup(DataObject.class); if (dataObject != null) { project = FileOwnerQuery.getOwner(dataObject.getPrimaryFile()); } } return (project == null) ? findProjectThatOwnsNode(node.getParentNode()) : project; } else { return null; } } }