Decorate a Map
with LazyMap
. Attempting to retrieve a value with a
key that is not in a Map
decorated
with LazyMap
will trigger the
creation of a value by a Transformer
associated with this LazyMap
. The
following example decorates a HashMap
with a Transformer
that reverses
strings; when a key is requested, a value is created and put into the
Map
using this Transformer
:
import java.util.*; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.lang.StringUtils; // Create a Transformer to reverse strings - defined below Transformer reverseString = new Transformer( ) { public Object transform( Object object ) { String name = (String) object; String reverse = StringUtils.reverse( name ); return reverse; } } // Create a LazyMap called lazyNames, which uses the above Transformer Map names = new HashMap( ); Map lazyNames = LazyMap.decorate( names, reverseString ); // Get and print two names String name = (String) lazyNames.get( "Thomas" ); System.out.println( "Key: Thomas, Value: " + name ); name = (String) lazyNames.get( "Susan" ); System.out.println( "Key: Susan, Value: " + name );
Whenever get( )
is called, the
decorated Map
passes the requested
key to a Transformer
, and, in this
case, a reversed string is put into a Map
as a value. The previous example requests
two strings and prints the following output:
Key: Thomas, Value: samohT Key: Susan, Value: nasuS
LazyMap
works best when a key
is a symbol or an abbreviation for a more complex object. If you are
working with a database, you could create a LazyMap
that retrieves objects by a primary
key value; in this case, the Transformer
simply retrieves a record from a
database table using the supplied key. Another example that springs to
mind is a stock quote; in the stock exchange, a company is represented
as a series of characters: "YHOO" represents the company Yahoo!, Inc.,
and "TSC" represents TheStreet.com. If your system deals with a quote
feed, use a LazyMap
to cache
frequently used entries. Example
5-17 uses a LazyMap
to create
a cache populated on demand, and it also demonstrates the LRUMap
—a fixed-size implementation of the
Map
introduced in Recipe 5.17.
Example 5-17. Example using a LazyMap
package com.discursive.jccook.collections.lazy; import java.net.URL; import java.util.Map; import org.apache.commons.collections.map.LRUMap; import org.apache.commons.collections.map.LazyMap; public class LazyMapExample { Map stockQuotes; public static void main(String[] args) throws Exception { LazyMapExample example = new LazyMapExample( ); example.start( ); } public void start( ) throws Exception { StockQuoteTransformer sqTransformer = new StockQuoteTransformer( ); sqTransformer.setQuoteURL( new URL("http://quotes.company.com") ); sqTransformer.setTimeout( 500 ); // Create a Least Recently Used Map with max size = 5 stockQuotes = new LRUMap( 5 ); // Decorate the LRUMap with the StockQuoteTransformer stockQuotes = LazyMap.decorate( stockQuotes, sqTransformer ); // Now use some of the entries in the cache Float price = (Float) stockQuotes.get( "CSCO" ); price = (Float) stockQuotes.get( "MSFT" ); price = (Float) stockQuotes.get( "TSC" ); price = (Float) stockQuotes.get( "TSC" ); price = (Float) stockQuotes.get( "LU" ); price = (Float) stockQuotes.get( "P" ); price = (Float) stockQuotes.get( "P" ); price = (Float) stockQuotes.get( "MSFT" ); price = (Float) stockQuotes.get( "LU" ); // Request another price to the Map, this should kick out the LRU item. price = (Float) stockQuotes.get( "AA" ); // CSCO was the first price requested, it is therefore the // least recently used. if( !stockQuotes.containsKey("CSCO") ) { System.out.println( "As expected CSCO was discarded" ); } } }
The Transformer
in Example 5-17 is an object that takes
a string and hits a URL using Apache HttpClient—a utility introduced in
Chapter 11. Every time a new symbol
is encountered, this Transformer
creates another thread with a timeout and hits a "quote server" that is
configured to return the latest price for that company's symbol. Example 5-18 defines a StockQuoteTransformer
that retrieves a quote by passing a stock symbol as a URL
parameter.
Example 5-18. A StockQuoteTransformer
package com.discursive.jccook.collections.lazy; import java.net.URL; import org.apache.commons.collections.Transformer; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpURL; import org.apache.commons.httpclient.methods.GetMethod; public class StockQuoteTransformer implements Transformer { protected URL quoteURL; protected long timeout; public Object transform(Object symbol) { QuoteRetriever retriever = new QuoteRetriever( (String) symbol ); try { Thread retrieveThread = new Thread( retriever ); retrieveThread.start( ); retrieveThread.join( timeout ); } catch( InterruptedException ie ) { System.out.println( "Quote request timed out."); } return retriever.getResult( ); } public URL getQuoteURL( ) { return quoteURL; } public void setQuoteURL(URL url) { quoteURL = url; } public long getTimeout( ) { return timeout; } public void setTimeout(long l) { timeout = l; } public class QuoteRetriever implements Runnable { private String symbol; private Float result = new Float( Float.NaN ); public QuoteRetriever(String symbol) { this.symbol = symbol; } public Float getResult( ) { return result; } public void run( ) { HttpClient client = new HttpClient( ); try { HttpURL url = new HttpURL( quoteURL.toString( ) ); url.setQuery( "symbol", symbol ); GetMethod getMethod = new GetMethod( url.toString( ) ); client.executeMethod( getMethod ); String response = getMethod.getResponseBodyAsString( ); result = new Float( response ); } catch( Exception e ) { System.out.println( "Error retrieving quote" ); } } } }
The point of this example is to demonstrate the power of
decorating an LRUMap
with LazyMap
and to write a Transformer
that can fetch a piece of data
from another server, not that the StockQuoteTransformer
uses Apache
HttpClient.
For more information about Apache HttpClient, see Chapter 11. For more information about the
LRUMap
, see Recipe 5.17.