org.openide.util.ContextGlobalProvider

In many scenarios with the NetBeans Platform, such as the NetBeans Platform CRUD Tutorial, you have two windows that need to interact with each other whilst being in two different modules.

The standard advice in this above scenario is to publish an object, e.g., Customer, into the Lookup, via one TopComponent (typically a viewer window), while the other TopComponent (typically an editor window) subscribes to that object. But to which Lookup should the second TopComponent subscribe? The global Lookup? In that case, when any TopComponent is selected that does not have the Customer in its Lookup, the global Lookup will not have the Customer available either. Normally, if this is a problem, you subscribe to the Lookup of the first TopComponent, which means your TopComponents are now tightly coupled to each other.

Tim Boudreau's blog, here, summarizes how you can replace the global Lookup with your own ContextGlobalProvider, while Bruce Schubert, here on NetBeans Zone and here on the NetBeans Wiki, goes into a lot more detail. Read the related javadoc, too.

Below, I've tried to simplify Bruce's code significantly, specifically for the Customer object in the NetBeans Platform CRUD Tutorial. Copy the code below, paste it into any module in your application, make sure to set an implementation dependency in that module on the Window System API, since an internal class is referenced. Then, whenever the context switches, e.g., a different TopComponent is selected, one that does not provide a Customer object in its Lookup, the last published Customer object is made available. And you can continue using Utilities.actionsGlobalContext().lookupResult(Customer.class) without a problem, i.e., your TopComponents will remain disconnected from each other.

More comments and explanations below, by Bruce, tweaked slightly here and there by me, all errors and mistakes my own responsibility.

package org.shop.editor;

import demo.Customer;
import java.util.Collection;
import org.netbeans.modules.openide.windows.GlobalActionContextImpl;
import org.openide.util.ContextGlobalProvider;
import org.openide.util.Lookup;
import org.openide.util.Lookup.Result;
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.util.lookup.ServiceProvider;

/**
 * This class proxies the original ContextGlobalProvider and ensures the current
 * customer remains in the GlobalContext regardless of the TopComponent
 * selection.
 *
 * To use this class you must edit the Windows System API module dependency:
 * change the dependency to an implementation version so that the
 * org.netbeans.modules.openide.windows package is on the classpath.
 *
 * @see ContextGlobalProvider
 * @see GlobalActionContextImpl
 * @author Bruce Schubert & Geertjan Wielenga
 */
@ServiceProvider(
        service = ContextGlobalProvider.class,
        supersedes = "org.netbeans.modules.openide.windows.GlobalActionContextImpl")
public class GlobalActionContextProxy implements ContextGlobalProvider {

    /**
     * The proxy lookup returned by this class:
     */
    private Lookup proxyLookup;
    /**
     * The native NetBeans global context lookup provider and
     * the official global lookup managed by the NetBeans Platform:
     */
    private final GlobalActionContextImpl globalContextProvider;
    private final Lookup globalContextLookup;
    private final LookupListener globalLookupListener;
    /**
     * Additional customer content for our custom global lookup:
     */
    private Lookup customerLookup;
    private final InstanceContent customerInstanceContent;
    private final Result<Customer> resultCustomers;
    /**
     * The last customer selected:
     */
    private Customer lastCustomer;
    /**
     * Critical section lock:
     */
    private final Object lock = new Object();

    public GlobalActionContextProxy() {
        this.customerInstanceContent = new InstanceContent();
        // The default GlobalContextProvider:
        this.globalContextProvider = new GlobalActionContextImpl();
        this.globalContextLookup = this.globalContextProvider.createGlobalContext();
        // Monitor the existance of a Customer in the official global lookup:
        this.resultCustomers = globalContextLookup.lookupResult(Customer.class);
        this.globalLookupListener = new LookupListenerImpl();
        this.resultCustomers.addLookupListener(this.globalLookupListener);
    }

    /**
     * Returns a ProxyLookup that adds the current Customer 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) {
            // Create the two lookups that will make up the proxy:
            customerLookup = new AbstractLookup(customerInstanceContent);
            proxyLookup = new ProxyLookup(globalContextLookup, customerLookup);
        }
        return proxyLookup;
    }

    /**
     * This class listens for changes in the Customer results, and ensures a
     * Customer remains in the Utilities.actionsGlobalContext() if a customer is
     * open.
     */
    private class LookupListenerImpl implements LookupListener {
        @Override
        public void resultChanged(LookupEvent event) {
            System.out.println("resultChanged: Entered...");
            synchronized (lock) {
                // First, handle customers in the principle lookup
                if (resultCustomers.allInstances().size() > 0) {
                    // Clear the proxy, and remember this customer. 
                    // Note: not handling multiple selection of customers.
                    clearCustomerLookup();
                    lastCustomer = resultCustomers.allInstances().iterator().next();
                    System.out.println("resultChanged: Found customer ["
                            + lastCustomer.getName()
                            + "] in the normal lookup.");
                } else {
                    // Add the last used customer to our internal lookup
                    if (lastCustomer != null) {
                        updateCustomerLookup(lastCustomer);
                    }
                }
            }
        }
    }

    /**
     * Unconditionally clears the customer lookup.
     */
    private void clearCustomerLookup() {
        Collection<? extends Customer> customers = customerLookup.lookupAll(Customer.class);
        for (Customer customer : customers) {
            customerInstanceContent.remove(customer);
        }
    }

    /**
     * Replaces the customer lookup content.
     *
     * @param customer to place in the customer lookup.
     */
    private void updateCustomerLookup(Customer customer) {
        if (customer == null) {
            throw new IllegalArgumentException("customer cannot be null.");
        }
        // Add the customer if an instance of it is not already in the lookup
        if (customerLookup.lookup(Customer.class) == null) {
            clearCustomerLookup();
            customerInstanceContent.add(customer);
            System.out.println("updateCustomerLookup: added ["
                    + lastCustomer.getName()
                    + "] to the proxy lookup.");
        }
    }

}
Comments:

This is a good example of extending the capabilities of the NetBeans Platform to fit your problem domain. Great work Geertjan!

Posted by Bruce on July 09, 2014 at 04:12 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« September 2015
SunMonTueWedThuFriSat
  
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today