5. Bags and Rabbits (Lookup I/II)

Own only what you can carry with you; know language, know countries, know people. Let your memory be your travel bag.

The more I learn about the NetBeans Platform the more I like it. These NetBeans guys are admirable. And, you know what? I can even use tiny parts of the NetBeans Platform in my own applications!

In this section (and in the next one, there's lots to say about the topic) we'll learn about what I call "NetBeans' Bags and Rabbits", also known as the "NetBeans Lookup Library". A nice piece of software worth being in your bag of tricks.

5.1. So, where's my object?

When you build a complex object-oriented system you usually face the same problem: finding object instances in between all those thousand instances around in the system.

Figure 20. A bag with objects

A bag with objects

Most complex systems provide a mechanism for looking-up objects, so you can find the instance you need easily.

JavaEE application servers, for instance, provide you with a JNDI Context that you can use to locate JDBC DataSources, JMS Topics and Queues and lots of other stuff. Similar mechanisms exist in other systems. JINI, for instance, contains a Service Lookup service, used to locate JINI services.

So all complex systems have some sort of "bag" you use to store objects, and have also some sort of "look-up" mechanism you use to find objects in the bag.

And so does NetBeans! it contains a "Lookup Library" you can use to look-up objects in bags. Let's see how.

5.2. Looking-up objects

NetBeans "object bags" are represented by the "org.openide.util.Lookup" class. Looking-up objects in such a bag is quite simple, all you need is know what type of object you need. The code is as follows:

import org.openide.util.Lookup;
import my.application.MyClass;
...
Lookup bag= ... 1
...
MyClass myInstance = bag.lookup( MyClass.class ); 2
...
Collection<MyClass> myInstances = bag.lookupAll( MyClass.class ); 3

That's quote simple, isn't it?

1

the declaration of "bag", a "bag of objects", represented as a "Lookup" object (we'll see later on how to get one of these).

2

once you have a "bag" you just invoke the "lookup" method to seek objects by type. In the example we're seeking for an object (of possibly many objects) extending the "MyClass" class.

3

you can also get all objects extending "MyClass", you use the "lookupAll" method for that.

[Note]Use the Lookup Library in your own applications!

Since the library is licensed under the CDDL v 1.0 license, you can use it both in open-source and closed-source projects. Just locate the "org-openide-util.jar" file (from your NetBeans IDE installation) and add it to the classpath of your project. That's easy, isn't it?

5.3. Complex queries, performance and... thieves!

If you now a little bit about SQL, and/or about JDBC, then you know you can submit a query to the database, and that the database in turn gives you a cursor (or a ResultSet).

NetBeans Lookups (i.e., NetBeans "bags") do also allow you to make queries and to obtain results for the queries.

Queries are represented by the "Lookup.Template" class, and results are represented by the "Lookup.Result" class. You use them like this:

import org.openide.util.Lookup;
...
Lookup bag = ... // the bag of objects
...
// A query that looks up instances extending "MyClass"...
Lookup.Template<MyClass> pattern = new Lookup.Template(MyClass.class); 1
...
// The result of the query
Lookup.Result<MyClass> result = bolsa.lookup( pattern ); 2
...
// Get all instances in the bag...
Collection<MyClass> objects = result.allInstances(); 3
...
Collection<Lookup.Item<MyClass>> objects2 = result.allItems(); 4

1

here we create a "Lookup.Template", a query that looks-up all objects in the bag that extend MyClass.

2

Here we get the result, a Lookup.Result.

3

We get all instances in the bag that extend "MyClass".

4

Here we don't get all instances, but a collection of Lookup.Item. This object is used to depher instantiation until the real instance is needed. This is a nice trick for performance sensitive applications.

5.3.1. Thieves, you say?

Yes. Thieves. The NetBeans Lookup Library has thieves. People that keep an eye on you: that watch you while storing things in your bag.

And those thieves are called LookupListeners, and they fire events whenever your Lookup.Result changes. Warning others that the contents of your bag have changed.

You add LookupListeners to Lookup.Results to be informed of changes in a bag. As we'll see later on this allows you to do very powerful things.

5.4. Creating bags

So we now know how to look-up stuff in our bags. We know how to retrieve objects by class. We can find a single instance of different instances, and we can use queries to obtain Lookup.Results. Good. Now, how do we create bags?

The simplest way to create a bag, a Lookup object, is to use the Lookups class (note there's a final "s" there). This is a utility class you use to create certain types of lookups. Let's see some examples (note there're many others out there, check the API!)


  // An immutable bag with one string
  Lookup bag1 = Lookups.singleton("Hello"); 1

  // An immutable bag with two strings
  Lookup bag2 = Lookups.fixed("Hello", "World!"); 2

  // An immutable bag without strings
  Lookup bag3 = Lookups.exclude( bag2, String.class );3

  // Join two bags into a single one
  Lookup bag4 = new ProxyLookup( bag1, bag2 );4

  // A bag that can change
  InstanceContent bagContents = new InstanceContent();5
  Lookup bag5 = new AbstractLookup( bagContents );
  bagContents.add( "Hello" );
  bagContents.add( "World!");

  System.out.println( bag1.lookupAll( String.class ) );
  System.out.println( bag2.lookupAll( String.class ) );
  System.out.println( bag3.lookupAll( String.class ) );
  System.out.println( bag4.lookupAll( String.class ) );
  System.out.println( bag5.lookupAll( String.class ) );

    

1

this is the way to create a bag with a single object. The stuff in the bag cannot be changed.

2

bag2 is an immutable bag with fixed content (two strings)

3

You can create a bag from another one by excluding some elements.

4

This is the way to create a bag with the same elements of two other bags. If the content of any bag changes, the contents of the resulting bag will also change.

5

This is the way to create a bag with contents that can be changed dinamically during the execution of the program. You create an InstanceContent object, which holds all the stuff in the bag. You can add and remove elements from it during program execution.

5.4.1. La bolsa global

Una de las "bolsas de viaje" más importantes de NetBeans es la bolsa global, conocida en inglés como el "Global Lookup". Es un objeto que contiene todos los servicios declarados en los directorios "META-INF/services" de todos los archivos jar de nuestra aplicación.

Para acceder a la "blosa global" escribimos el siguiente código:

  Lookup bolsaGlobal = Lookup.getDefault();

Para almacenar objetos en la "bolsa global" no tenemos más que añadir una entrada en el directorio "META-INF/services". Basta incluir un fichero de texto plano que tenga el nombre de un interfaz, y luego dentro del fichero escribir el nombre de una clase que implemente dicho interfaz. Si no lo tenemos claro podemos acudir a la información en línea sobre el mecanismo estándar para añadir servicios

5.4.2. Otros tipos de bolsas

A veces necesitamos una bolsa con un único objeto. En este caso podemos hacer uso de una clase muy útil llamada "org.openide.util.Lookups" (fíjense en la "s" al final). Esta clase nos permite construir una bolsa con un objeto, tal que así:

...
Lookup bolsa = Lookups.singleton( miobjeto );
...

O, si queremos hacer una bolsa con varios objetos, podemos usar el método "fixed", tal que así:

...
Lookup bolsa = Lookups.fixed( "Hola", new Integer(3) );
...

También podemos hacer una bolsa que cambie dinámicamente, añadiendo y eliminando objetos a nuestro antojo. Para ello necesitamos un contenedor especial, denominado "InstanceObject" y una bolsa especial de tipo "AbstractLookup". Haríamos lo siguiente:

  InstanceContent contenedor = new InstanceContent();
  contenedor.add( "Hola" );
  contenedor.remove( "Hola" );
  contenedor.add( new Integer(3) );

  Lookup bolsa = new AbstractLookup( contenedor );

Finalmente podemos hacer también bolsas específicas que modifiquen el contenido de una o más bolsas. Es como meter una bolsa dentro de otra, filtrando, posiblemente, los contenidos. Esta funcionalidad se consigue usando una bolsa especial llamada "ProxyLookup". Así:

  Lookup a = ...
  Lookup b = ...
  Lookup a_y_b = new ProxyLookup( a, b );

5.5. ¿Y para qué vale todo esto?

Figure 21. ¡Convierta su bolsa en un sombrero!

¡Convierta su bolsa en un sombrero!

Imagínese que diseña una clase con una serie de propiedades (un entero, una cadena, etcétera). Imagínese ahora que, una vez la ha creado y publicado en un API, necesita cambiarla. ¿Qué sucederá con los usuarios de su API? Tendrán que cambiar su código, ¿verdad? ¡Una verdadera lata!

Lo que puede hacer es añadir una bolsa a su objeto, e ir poniendo en la bolsa diferentes propiedades. De este modo se pueden añadir objetos a la bolsa sin cambiar para nada su API. ¡Es como poder modificar las clases en tiempo de ejecución! ¡Es como sacarse un conejo del sombrero!

5.6. Aprender más

En esta sección hemos visto por encima la librería de Lookup de NetBeans. Hay muchísimo más. Por ejemplo, ¿sabía que podemos informarnos de cambios en el resultado de un Lookup? ¡Hay listeners que nos permiten hacer justamente eso! ¿sabía que puede construir sus propias bolsas? La verdad es que hay mucho que aprender (y que aprovechar).

Para más información puede consultarse el javadoc de la librería, o el API y, cómo no, las listas de correo de NetBeans.


blog comments powered by Disqus