поиска по платформе NetBeans — вероятно, самой важной части платформы. Супер простое их объяснение — «Карта <Класс, Объект>». Звучит
просто, правда? Ну,
это так, и это довольно мощный в то же время. Это позволяет вам свободно связывать части вашего приложения, создавать точки расширения и т. Д. Другой вариант использования — передача некоторой даты в приложении, но вы не знаете, где находятся модули, заинтересованные в этих данных (потому что вы слабо связаны, верно?). В приложении, которое я писал для своего Uni уже несколько дней, я использовал Lookup для того, чтобы сделать именно это — передать данные из алгоритма в LineChartDrawer, который будет обновлять свою диаграмму каждый раз при обновлении данных.
Это будет очень похоже на реализацию шаблона наблюдателя , но с использованием поиска в качестве механизма уведомления. Давайте начнем с нашего класса Algorithm (и модуля — обратите внимание, что этот модуль не зависит от модуля рисования диаграмм!)
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
}
}
Код хорошо прокомментирован для этого поста, так что найдите минутку и прочитайте его. Это всего лишь некоторые части реального класса, который мы используем — поэтому здесь не показано никаких реальных вычислений, давайте сосредоточимся на поиске. Обратите внимание, что я получаю TopComponent, который я добавлю в качестве прослушивателя «по имени», это очень просто, но вам нужно «найти какую-то реализацию некоторого интерфейса ящика», просто используйте поиск и получите экземпляр, или даже все экземпляры таких реализаций интерфейса. Тем не менее, это решение здесь не самое универсальное (получение примера), но оно достаточно хорошо для нашего приложения, поскольку оно очень маленькое. Нет, давайте посмотрим на «Observer», лучше названный «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...
}
//...
}
Вот и все. У нас просто есть метод resultChanged (), который будет вызываться при изменении содержимого набора результатов. Мы могли бы также захотеть собрать больше просмотров, чтобы их можно было наблюдать, поэтому лучше проверить этот экземпляр, я думаю, чем нет, поскольку в нем могут быть некоторые неожиданные вещи …
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:
- a nice post by Anton Epple on dzone.com
- Geertjan’s post with some good links
- http://www.antonioshome.net/blog/2008/20081023-1.php