Tuesday, January 20, 2009

TransformedMap and Transformers: Decorated "put" strategy

This is a long example regarding decorated maps and transformers. It’s important to understand the concepts I will cover, because I will use them in the next example, and continue to build on them.

The object TransformedMap has a static method called decorate (http://commons.apache.org/collections/api-release/org/apache/commons/collections/map/TransformedMap.html), this method allows you to load a backing map and two transformers (http://commons.apache.org/collections/api-release/org/apache/commons/collections/Transformer.html), one for the key and the other for the value. The returning object is a decorated map. So, when you put an item into this new decorated map, the key and values are transformed. Unfortunately, the transformer interface doesn’t allow you to gain access to the backing object through the interface. You could be cleaver and jam it into a constructor ( more on this later ). Be forewarned, modifying a map while in the midst of another modification method will result in a big ugly run time exception ( just in case you thought you could us this method to build your own MultiValueMap ).

Enough chatter… lets look at some code

The code:

package com;
import java.util.*;
import org.apache.commons.collections.map.*;
import org.apache.commons.collections.*;
public class MapDecorator
{
public static void main(String[] args)
{
int i = 0;
List<DTO> list = Arrays.asList(new DTO(i++,"Bob",Gender.Male,State.WI), new DTO(i++,"Larry",Gender.Male,State.WI),
new DTO(i++,"Bill", Gender.Male, State.WI), new DTO(i++,"Sue", Gender.Female, State.AZ),
new DTO(i++,"Joe", Gender.Male, State.AZ), new DTO(i++,"Zoe", Gender.Female, State.WI));
// Decorate a map where the key is the id, and the value is the object.
Map exampleOne = TransformedMap.decorate( new HashMap(),
TransformerUtils.invokerTransformer("getId"),
TransformerUtils.nopTransformer());
// Decorate a map where the key is the name and the value is the id
Map exampleTwo = TransformedMap.decorate( new HashMap(),
new Transformer() {
public Object transform(Object o)
{
return ((DTO)o).getName();
}
},
new Transformer() {
public Object transform(Object o)
{
return ((DTO)o).getId();
}
} );
// load up the maps.
for (DTO dto : list)
{
exampleOne.put(dto, dto);
exampleTwo.put(dto, dto);
}
printTheMap("exampleOne",exampleOne);
printTheMap("exampleTwo",exampleTwo);
}

private static void printTheMap(String mapName, Map exampleOne)
{
System.out.println("Map Name = " + mapName );
System.out.println("------------ Keys ------------");
for (Object key : exampleOne.keySet())
{
System.out.println("key = " + key);
}
System.out.println("------------ Values ------------");
for (Object value : exampleOne.values())
{
System.out.println("value = " + value);
}
System.out.println("------------------------------");
}
public static class DTO
{
private int id;
private String name;
private Gender gender;
private State state;
public DTO(int id, String name, Gender gender, State state)
{
this.id = id;
this.name = name;
this.gender = gender;
this.state = state;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Gender getGender()
{
return gender;
}
public void setGender(Gender gender)
{
this.gender = gender;
}
public State getState()
{
return state;
}
public void setState(State state)
{
this.state = state;
}
@Override public String toString()
{
return "DTO{id=" + id + ", name='" + name + '\'' + ", gender=" + gender + ", state=" + state + '}';
}
}
public static enum Gender
{
Male, Female
}
/**
* Official USPS Abbreviations
*/
public static enum State
{
AL("ALABAMA"), AK("ALASKA"), AS("AMERICAN SAMOA"),
AZ("ARIZONA "), AR("ARKANSAS"), CA("CALIFORNIA "),
CO("COLORADO "), CT("CONNECTICUT"), DE("DELAWARE"),
DC("DISTRICT OF COLUMBIA"), FM("FEDERATED STATES OF MICRONESIA"), FL("FLORIDA"),
GA("GEORGIA"), GU("GUAM "), HI("HAWAII"),
ID("IDAHO"), IL("ILLINOIS"), IN("INDIANA"),
IA("IOWA"), KS("KANSAS"), KY("KENTUCKY"), LA("LOUISIANA"),
ME("MAINE"), MH("MARSHALL ISLANDS"), MD("MARYLAND"),
MA("MASSACHUSETTS"), MI("MICHIGAN"), MN("MINNESOTA"),
MS("MISSISSIPPI"), MO("MISSOURI"), MT("MONTANA"),
NE("NEBRASKA"), NV("NEVADA"), NH("NEW HAMPSHIRE"),
NJ("NEW JERSEY"), NM("NEW MEXICO"), NY("NEW YORK"),
NC("NORTH CAROLINA"), ND("NORTH DAKOTA"), MP("NORTHERN MARIANA ISLANDS"),
OH("OHIO"), OK("OKLAHOMA"), OR("OREGON"),
PW("PALAU"), PA("PENNSYLVANIA"), PR("PUERTO RICO"),
RI("RHODE ISLAND"), SC("SOUTH CAROLINA"), SD("SOUTH DAKOTA"),
TN("TENNESSEE"), TX("TEXAS"), UT("UTAH"),
VT("VERMONT"), VI("VIRGIN ISLANDS"), VA("VIRGINIA "),
WA("WASHINGTON"), WV("WEST VIRGINIA"), WI("WISCONSIN"), WY("WYOMING");
private String fullyQualifiedName;
State(String fullyQualifiedName)
{
this.fullyQualifiedName = fullyQualifiedName;
}
public String getFullyQualifiedName()
{
return fullyQualifiedName;
}
}
}



The results:

Map Name = exampleOne
------------ Keys ------------
key = 2
key = 4
key = 1
key = 3
key = 5
key = 0
------------ Values ------------
value = DTO{id=2, name='Bill', gender=Male, state=WI}
value = DTO{id=4, name='Joe', gender=Male, state=AZ}
value = DTO{id=1, name='Larry', gender=Male, state=WI}
value = DTO{id=3, name='Sue', gender=Female, state=AZ}
value = DTO{id=5, name='Zoe', gender=Female, state=WI}
value = DTO{id=0, name='Bob', gender=Male, state=WI}
------------------------------
Map Name = exampleTwo
------------ Keys ------------
key = Bob
key = Larry
key = Zoe
key = Joe
key = Sue
key = Bill
------------ Values ------------
value = 0
value = 1
value = 5
value = 4
value = 3
value = 2
------------------------------


The decorated Map, exampleOne, is backed by a HashMap and when put is called on the map, it calls the TransformerUtils.invokerTransformer("getId") (http://commons.apache.org/collections/api-release/org/apache/commons/collections/TransformerUtils.html#invokerTransformer(java.lang.String)) on the Object for the key. This results in the invoker using reflections to pull the id off the bean. In the same stroke, the value is transformed by TransformerUtils.nopTransformer() (http://commons.apache.org/collections/api-release/org/apache/commons/collections/TransformerUtils.html#nopTransformer()). This literally does nothing to the object used as the value; it is a kind of pass through.

// Decorate a map where the key is the id, and the value is the object.
Map exampleOne = TransformedMap.decorate( new HashMap(),
TransformerUtils.invokerTransformer("getId"),
TransformerUtils.nopTransformer());


For example

DTO dto = new DTO(100,"Bob",Gender.Male,State.WI);
exampleOne.put(dto, dto);

Results in an entry that has a key of 100 and a value of DTO(100,"Bob",Gender.Male,State.WI).

You might want to get a couple of values out and build a multi-value key or something else. The Map, exampleTwo, shows two anonymous inner implementations of the transformer interface.

Map exampleTwo = TransformedMap.decorate( new HashMap(),
new Transformer() {
public Object transform(Object o)
{
return ((DTO)o).getName();
}
},
new Transformer() {
public Object transform(Object o)
{
return ((DTO)o).getId();
}
} );


For example if you did this.

DTO dto = new DTO(100,"Bob",Gender.Male,State.WI);
exampleTwo.put(dto, dto);

The results would be a key of Bob pointing to a value of 100… are you beginning to see how awesome this api is? Are your gears grinding yet? Wait to you see what we do next time.
Author: Philip A Senger

1 comment:

  1. Thanks for the guide and descriptive post regarding decorated maps and transformers.Its quite hard to understand all,but its helpful for me for many point of view.

    ReplyDelete