5.18. Using a Lazy Map

Problem

You need a Map that can populate a value when its corresponding key is requested.

Solution

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

Discussion

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 Jakarta 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 Jakarta HttpClient.

See Also

For more information about Jakarta HttpClient, see Chapter 11. For more information about the LRUMap, see Recipe 5.17.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.107.152