Over a million developers have joined DZone.

Tip: NetBeans Platform Lookups as Communication Method

· Java Zone

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

Traveling Salesman on NetBeansPlatform Today's post will be a super quick usage scenario for NetBeans Platform Lookups - the probably most essential part of the Platform. An super simple explanation of them is "an Map<Class, Object>". Sounds simple right? Well it is, and it's quite powerful at the same time. It enables you to loosely couple parts of your app, create extension points etc. Another use case is passing some date around in the app, but you don't know where the modules interested in this data are (because you're loosely coupled, right?). In an app I've been writing for my Uni since some days, I've used the Lookup to do exactly this - pass data from an algorithm to an LineChartDrawer that then will update it's chart each time the data is being updated.

This is going to be quite similar to an Observer Pattern implementation, but using the lookup as notification mechanism. Let's start out with our Algorithm class (and module - note that this module is not dependent on the chart drawing module!)

package pl.edu.netbeans.algorithms;

import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.LookupListener;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import pl.edu.netbeans.algorithms.genetic.Chromosom;
import pl.edu.netbeans.algorithms.genetic.Population;
import pl.edu.netbeans.toolbox.ChartDataDTO;
import prefuse.data.Graph;

/**
* The algorithm implementation, does some stuff and throws the data into an DTO which is packed into the lookup
*/
public class FirstTSSolverAction extends SolverAction implements TSSolverAction, Lookup.Provider {
private final Population population;

//just to get us some fancy names for this algorithm
private static int simcount = 1;
private final String SIMULATION_ID = "symulacja " + FirstTSSolverAction.simcount++;

/*
* Here we're setting up the Lookup, to work with dynamic content, thanks to this,
* we'll be able to modify it's contents and notify all listeners after some change has happened
*/
private InstanceContent dynamicContent = new InstanceContent();
private Lookup myLookup = new AbstractLookup(dynamicContent); //AbstractLookup is NOT an abstract class, it's an "general purpose lookup"
private Lookup.Result res; //this is only here because I want to use allItems down there bellow, otherwise it would be just a variable in the method bellow

public FirstTSSolverAction(Graph graph) {
super(graph);
//...
}

/**
* This is not an Runnable class but the run method will act quite the same as if it was,
* as our framework will call it in constant time delays. We do all the computing here.
*/
@Override
public void run(double frac) {
if (population.shouldStop()) {
//... stop the execution etc.
return;
}
//here we do the actual computing (by calling the genetic algorithm)
population.nextGeneration();
Chromosom ch = population.getBestChromosom();
int numerGeneracji = population.getNumerGeneracji();

double avgFitness = population.getAvgFitness();
double maxFitness = population.getWorstFittness();
double minFitness = population.getBestFitness();

log("Generation " + numerGeneracji + ": best chromosome: " + ch + " (" + avgFitness + ")");

/* Słuchający tego lookup zostaną powiadomieni o zmianie, przerysują wykres */
dynamicContent.add(new ChartDataDTO(SIMULATION_ID, numerGeneracji, avgFitness, maxFitness, minFitness));
res.allItems();//I found it quite helpful in order to be sure that the listeners will notice the change, if we are changing the res really quickly (which is the case with this algorithm)
}

//...

/**
* Since we're an Lookup.Provider, let's provide our lookup!
* It can be implemented in multiple ways, but let's just return our lookup this time.
*/
@Override
public Lookup getLookup() {
return myLookup;
}

/**
* Poszukuje oraz ustawia listenera implementującego LineChartDrawer
* aby ten reagował na każdorazową zmianę w naszym lookup - aktualizował wykres
*/
@SuppressWarnings("unchecked")
private void setupLineGraphDrawerListener() {
//a sucky but good enough implementation for our usecase
//Better solution: You could use another Lookup to find implementations of the "LineGrapghDrawer" interface and then use this instance here!
TopComponent drawer = WindowManager.getDefault().findTopComponent("FitnessGraphTopComponent");

if (drawer == null) {
//depending on if you need this or not, throw exceptions or just ignore this if you dont need it
//throw new RuntimeException("Nie znaleziono implementacji FitnessGraphTopComponent. Nikt mnie nie słucha!");
//or...
log("No FittnessGraphTopComponent found. That's OK, so I will continue without it...");
return;
}

//below is the actual settup of our "observer"
res = myLookup.lookup(new Lookup.Template(ChartDataDTO.class)); //setup the resultset to react to changes of ChartDataDTOs in it
res.allItems();//THIS IS IMPORTANT! Help out the lookup by "refreshing" its contents
res.addLookupListener((LookupListener) drawer);//setup the listener to the drawer we found
}
}

The code is well commented for this post so take a moment and read through it. It's just some pieces of the actual class we're using - so no actual computing stuff is shown here, let's focus on the lookups. Note that I'm getting the TopComponent that I'll add as an listener "by name", it's super simple but would you need to "find some implementation of some drawer interface" just use an lookup and get the instance, or even all the instances of such interface implementations. That said, this solution is not the most universal here (getting the instance) but it's good enough for our app since it's a very small application. No let's look at the "Observer", better named as "LookupListener":

package pl.edu.netbeans.visualization;

import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import pl.edu.netbeans.toolbox.ChartDataDTO;
import pl.edu.netbeans.toolbox.LineChartDrawer;

/**
* Top component which will draw graphs for all the data it gets...
*/
@ConvertAsProperties(dtd = "-//pl.edu.netbeans.visualization//FitnessGraph//EN",
autostore = false)
public final class FitnessGraphTopComponent extends TopComponent implements LookupListener, LineChartDrawer {

private static FitnessGraphTopComponent instance;
private static final String PREFERRED_ID = "FitnessGraphTopComponent";
//... skipped all the computing/drawing stuff

public FitnessGraphTopComponent() {
initComponents();
setupChart();

setName(NbBundle.getMessage(FitnessGraphTopComponent.class, "CTL_FitnessGraphTopComponent"));
setToolTipText(NbBundle.getMessage(FitnessGraphTopComponent.class, "HINT_FitnessGraphTopComponent")); //note how easy it will be to internationalize this app :-)
// setIcon(ImageUtilities.loadImage(ICON_PATH, true));
putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.TRUE);
putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, Boolean.FALSE);
putClientProperty(TopComponent.PROP_SLIDING_DISABLED, Boolean.TRUE);
}
//...
//skipped graph drawing/setup parts, this post is about lookups only ;-)
//...

@Override
protected String preferredID() {
return PREFERRED_ID;
}

/**
* This is our resultChanged listener. It will be called if the resultSet we're observing is changed.
* Note that "changed" means all CRUD operations on it.
*/
@Override
public void resultChanged(LookupEvent ev) {
Lookup.Result res = (Lookup.Result) ev.getSource(); //this is always an safe cast!
Collection instances = res.allInstances(); //we get all instances from the lookup

if (!instances.isEmpty()) {
Iterator it = instances.iterator();
while (it.hasNext()) {
Object o = it.next();
if(o instanceof ChartDataDTO){//you might want to use this - better safe than sorry, check if you got what you expected!
ChartDataDTO o = (ChartDataDTO) it.next(); //If not sure if this will always be the case, use an if( instanceof ) here!
addDTO2Series(o);
}
}
}
}

private void addDTO2Series(ChartDataDTO chartDataDTO) {
//...add the stuff to the graphs...
}
//...
}

That's about it. We simply have an resultChanged() method that will be called if the contents of the resultset are changed. We might also want to arr more lookups to be observed, so it's better to check this instanceof I think than not, since there might be some unexpected stuff inside of it...

All in all, you write an Lookup.Provider and LookupListener, implement both's methods and then at some place in your app hook them up. It doesn't really matter "who looks for who" as long as it's consistent and logical. In this case, the algorithm is looking for people interested in his data. Note that at any moment in time, we can add another listener, no problems here. Note that the Lookup.Provider and LookupListener are quite similar to the Observer and Observables mentioned before. And the good thing is, the algorithm does not know about any concrete drawer, and the drawer has no idea about any algorithm - it just displays some data it gets. They just have one shared API module - the toolbox, in which there is the DataTransferObject we're using to pass the data from one to another - yay, an loosely coupled system. (yeah, I know our code is tightly coupled at some other places, but this part is quite ok :-))

If you have anything you'd like to comment on this code, feel free to do so - we're still just students and are more than willing to learn how to write better code! The code is just some parts taken from http://github.com/ktoso/TravelingSalesman-NBP - our implementation and visualization of the Traveling Salesman Problem being solved by Genetic Algorithms.

For more information about lookups goto:

 

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}