commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rwaldh...@apache.org
Subject cvs commit: jakarta-commons-sandbox/functor/src/test/org/apache/commons/functor/example FlexiMapExample.java
Date Sat, 29 Mar 2003 01:14:23 GMT
rwaldhoff    2003/03/28 17:14:22

  Modified:    functor/src/test/org/apache/commons/functor/example
                        FlexiMapExample.java
  Log:
  adding commentary
  
  Revision  Changes    Path
  1.5       +245 -29   jakarta-commons-sandbox/functor/src/test/org/apache/commons/functor/example/FlexiMapExample.java
  
  Index: FlexiMapExample.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/functor/src/test/org/apache/commons/functor/example/FlexiMapExample.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- FlexiMapExample.java	5 Mar 2003 01:12:47 -0000	1.4
  +++ FlexiMapExample.java	29 Mar 2003 01:14:22 -0000	1.5
  @@ -81,6 +81,20 @@
   import org.apache.commons.functor.core.RightIdentityFunction;
   import org.apache.commons.functor.core.composite.ConditionalUnaryFunction;
   
  +/*
  + * ----------------------------------------------------------------------------
  + * INTRODUCTION:
  + * ----------------------------------------------------------------------------
  + */
  +
  +/*
  + * In this example, we'll demonstrate how we can use "pluggable" functors
  + * to create specialized Map implementations via composition.
  + * 
  + * All our specializations will use the same basic Map implementation.
  + * Once it is built, we'll only need to define the specialized behaviors. 
  + */
  + 
   /**
    * @version $Revision$ $Date$
    * @author Rodney Waldhoff
  @@ -95,19 +109,46 @@
           return new TestSuite(FlexiMapExample.class);
       }
   
  +    /*
  +     * ----------------------------------------------------------------------------
  +     * UNIT TESTS:
  +     * ----------------------------------------------------------------------------
  +     */
  +
  +    /* 
  +     * In a "test first" style, let's first specify the Map behaviour we'd like
  +     * to implement via unit tests. 
  +     */
  +     
  +    /*
  +     * First, let's review the basic Map functionality.
  +     */
  +    
  +    /*
  +     * The basic Map interface lets one associate keys and values:
  +     */
       public void testBasicMap() {
  -        Map map = makeBasicMap();
  +        /* (We'll define these make*Map functions below.) */
  +        Map map = makeBasicMap(); 
           Object key = "key";
           Object value = new Integer(3);
           map.put(key,value);
           assertEquals(value, map.get(key) );
       }    
   
  +    /*
  +     * If there is no value associated with a key,
  +     * the basic Map will return null for that key:
  +     */
       public void testBasicMapReturnsNullForMissingKey() {
           Map map = makeBasicMap();
           assertNull( map.get("key") );
       }    
   
  +    /*
  +     * One can also explicitly store a null value for
  +     * some key: 
  +     */
       public void testBasicMapAllowsNull() {
           Map map = makeBasicMap();
           Object key = "key";
  @@ -116,6 +157,10 @@
           assertNull( map.get(key) );
       }    
   
  +    /*
  +     * The basic Map deals with Objects--it can store keys
  +     * and values of multiple or differing types:
  +     */
       public void testBasicMapAllowsMultipleTypes() {
           Map map = makeBasicMap();
           map.put("key-1","value-1");
  @@ -129,6 +174,11 @@
           assertEquals(new Integer(4), map.get(new Integer(4)) );
       }    
   
  +    /*
  +     * Finally, note that putting a second value for a given
  +     * key will overwrite the first value--the basic Map only
  +     * stores the most recently put value for each key: 
  +     */
       public void testBasicMapStoresOnlyOneValuePerKey() {
           Map map = makeBasicMap();
   
  @@ -138,7 +188,14 @@
           assertEquals("value-2", map.get("key") );
       }    
       
  +    /*
  +     * Now let's look at some specializations of the Map behavior. 
  +     */
       
  +    /*
  +     * One common specialization is to forbid null values, 
  +     * like our old friend Hashtable: 
  +     */
       public void testForbidNull() {
           Map map = makeNullForbiddenMap();
           
  @@ -152,14 +209,32 @@
           }                
       }
   
  +    /*
  +     * Alternatively, we may want to provide a default
  +     * value to return when null is associated with some 
  +     * key. (This might be useful, for example, when the Map
  +     * contains a counter--when there's no count yet, we'll 
  +     * want to treat it as zero.):  
  +     */
       public void testNullDefaultsToZero() {
  -        Map map = makeNullAsZeroMap();        
  +        Map map = makeDefaultValueForNullMap(new Integer(0));        
  +        /*
  +         * We expect 0 when no value has been associated with "key".
  +         */
  +        assertEquals( new Integer(0), map.get("key") );
  +        /*
  +         * We also expect 0 when a null value has been associated with "key".
  +         */
           map.put("key", null);
           assertEquals( new Integer(0), map.get("key") );
       }
   
  +    /*
  +     * Another common specialization is to constrain the type of values
  +     * that may be stored in the Map:
  +     */
   	public void testIntegerValuesOnly() {
  -		Map map = makeIntegerValuedMap();
  +		Map map = makeTypeConstrainedMap(Integer.class);
   		map.put("key", new Integer(2));        
   		assertEquals( new Integer(2), map.get("key") );
   		try {
  @@ -170,6 +245,15 @@
   		}                		
   	}
   
  +    /*
  +     * A more interesting specialization is that used by the 
  +     * Jakarta Commons Collections MultiMap class, which allows 
  +     * one to associate multiple values with each key.  The put
  +     * function still accepts a single value, but the get function
  +     * will return a Collection of values.  Associating multiple values
  +     * with a key adds to that collection, rather than overwriting the
  +     * previous value: 
  +     */
   	public void testMultiMap() {
   		Map map = makeMultiMap();
   
  @@ -204,6 +288,13 @@
   
   	}
   
  +    /*
  +     * Here's another variation on the MultiMap theme.  
  +     * Rather than adding elements to a Collection, let's 
  +     * concatenate String values together, delimited by commas.
  +     * (Such a Map might be used by the Commons Collection's
  +     * ExtendedProperties type.): 
  +     */
   	public void testStringConcatMap() {
   		Map map = makeStringConcatMap();
   		map.put("key", "value 1");
  @@ -214,8 +305,28 @@
   		assertEquals("value 1, value 2, value 3",map.get("key"));
   	}
   
  +    /*
  +     * ----------------------------------------------------------------------------
  +     * THE GENERIC MAP IMPLEMENTATION:
  +     * ----------------------------------------------------------------------------
  +     */
  +
  +    /*
  +     * How can one Map implementation support all these behaviors?
  +     * Using functors and composition, of course.  
  +     * 
  +     * In order to keep our example small, we'll just consider the 
  +     * primary Map.put and Map.get methods here, although the remaining
  +     * Map methods could be handled similiarly.    
  +     */
       static class FlexiMap implements Map {
   
  +        /*
  +         * Our FlexiMap will accept two BinaryFunctions, one 
  +         * that's used to transform objects being put into the Map,
  +         * and one that's used to transforms objects being retrieved 
  +         * from the map.
  +         */
           public FlexiMap(BinaryFunction putfn, BinaryFunction getfn) {
               if(null == putfn) {
                   onPut = new RightIdentityFunction();
  @@ -231,17 +342,36 @@
               
               proxiedMap = new HashMap();
           }        
  +
           
  +        /*
  +         * The arguments to our "onGet" function will be the 
  +         * key and the value associated with that key in the 
  +         * underlying Map.  We'll return whatever the function
  +         * returns.
  +         */
  +        public Object get(Object key) {
  +            return onGet.evaluate( key, proxiedMap.get(key) );
  +        }
  +
  +        /*
  +         * The arguments to our "onPut" function will be the 
  +         * value previously associated with that key (if any),
  +         * as well as the new value being associated with that key.
  +         * 
  +         * Since put returns the previously associated value, 
  +         * we'll invoke onGet here as well. 
  +         */
           public Object put(Object key, Object value) {
               Object oldvalue = proxiedMap.get(key);
               proxiedMap.put(key, onPut.evaluate(oldvalue, value));
               return onGet.evaluate(key,oldvalue);
           }
   
  -        public Object get(Object key) {
  -            return onGet.evaluate( key, proxiedMap.get(key) );
  -        }
  -
  +       /* 
  +        * We'll skip the remaining Map methods for now.    
  +        */
  +        
           public void clear() {
               throw new UnsupportedOperationException("Left as an exercise for the reader.");
           }
  @@ -287,16 +417,46 @@
           private Map proxiedMap = null;
       }
   
  +    /*
  +     * ----------------------------------------------------------------------------
  +     * MAP SPECIALIZATIONS:
  +     * ----------------------------------------------------------------------------
  +     */
  +
  +    /*
  +     * For the "basic" Map, we'll simply create a HashMap.
  +     * Note that using a RightIdentityFunction for onPut and onGet
  +     * would yield the same behavior. 
  +     */
       private Map makeBasicMap() {
           return new HashMap();
       }
       
  +    /*
  +     * To prohibit null values, we'll only need to 
  +     * provide an onPut function.
  +     */
       private Map makeNullForbiddenMap() {
           return new FlexiMap(
  -            IgnoreLeftFunction.adapt(                        
  +            /*
  +             * We simply ignore the left-hand argument,
  +             */
  +            IgnoreLeftFunction.adapt(                  
  +                /*
  +                 * and for the right-hand,
  +                 */      
                   new ConditionalUnaryFunction(
  +                    /*
  +                     * we'll test for null,
  +                     */      
                       IsNull.getIsNullPredicate(),
  +                    /*
  +                     * throwing a NullPointerException when the value is null,
  +                     */      
                       UnaryProcedureUnaryFunction.adapt(throwNPE),
  +                    /*
  +                     * and passing through all non-null values.
  +                     */      
                       IdentityFunction.getIdentityFunction()
                   )
               ),
  @@ -304,25 +464,61 @@
           );
       }
   
  -	private Map makeNullAsZeroMap() {
  +    /*
  +     * To provide a default for null values, we'll only need to 
  +     * provide an onGet function, simliar to the onPut method used
  +     * above.
  +     */
  +	private Map makeDefaultValueForNullMap(Object defaultValue) {
   		return new FlexiMap(
  +            null,
  +            /*
  +             * We ignore the left-hand argument,
  +             */
   			IgnoreLeftFunction.adapt(                        
  +                /*
  +                 * and for the right-hand,
  +                 */      
   				new ConditionalUnaryFunction(
  +                    /*
  +                     * we'll test for null,
  +                     */      
   					IsNull.getIsNullPredicate(),
  -					new ConstantFunction(new Integer(0)),
  +                    /*
  +                     * returning our default when the value is otherwise null,
  +                     */      
  +					new ConstantFunction(defaultValue),
  +                    /*
  +                     * and passing through all non-null values.
  +                     */      
   					IdentityFunction.getIdentityFunction()
   				)
  -			),
  -			null
  +			)
   		);
   	}
   
  -	private Map makeIntegerValuedMap() {
  +    /*
  +     * To constrain the value types, we'll 
  +     * provide an onPut function,
  +     */
  +	private Map makeTypeConstrainedMap(Class clazz) {
   		return new FlexiMap(
  +            /*
  +             * ignore the left-hand argument,
  +             */
   			IgnoreLeftFunction.adapt(                        
   				new ConditionalUnaryFunction(
  -					new IsInstanceOf(Integer.class),
  +                    /*
  +                     * we'll test the type of the right-hand argument,
  +                     */      
  +					new IsInstanceOf(clazz),
  +                    /*
  +                     * and either pass the given value through,
  +                     */      
   					IdentityFunction.getIdentityFunction(),
  +                    /*
  +                     * or throw a ClassCastException.
  +                     */      
   					UnaryProcedureUnaryFunction.adapt(throwCCE)
   				)
   			),
  @@ -330,6 +526,11 @@
   		);
   	}
   
  +    /*
  +     * The MultiMap is a bit more interesting, since we'll
  +     * need to consider both the old and new values during
  +     * onPut:
  +     */
   	private Map makeMultiMap() {
   		return new FlexiMap(
   			new BinaryFunction() {
  @@ -348,8 +549,15 @@
   		);
   	}
   
  +    /*
  +     * The StringConcatMap is more interesting still.
  +     */
   	private Map makeStringConcatMap() {
   		return new FlexiMap(
  +            /*
  +             * The onPut function looks similiar to the MultiMap
  +             * method:
  +             */
   			new BinaryFunction() {
   				public Object evaluate(Object oldval, Object newval) {
   					StringBuffer buf = null;
  @@ -363,6 +571,10 @@
   					return buf;
   				}
   			},
  +            /*
  +             * but we'll also need an onGet functor to convert
  +             * the StringBuffer to a String:
  +             */
   			new BinaryFunction() {
   				public Object evaluate(Object key, Object val) {
   					if(null == val) {
  @@ -375,29 +587,33 @@
   		);
   	}
   
  -    private interface UniversalProcedure extends Procedure, UnaryProcedure, BinaryProcedure
{ }
  +    /*
  +     * (This "UniversalProcedure" type provides a procedure 
  +     * that takes the same action regardless of the number of
  +     * parameters. We used it above to throw Exceptions when 
  +     * needed.)
  +     */
  +     
  +    private abstract class UniversalProcedure implements Procedure, UnaryProcedure, BinaryProcedure
{
  +        public abstract void run();
  +
  +        public void run(Object obj) {
  +            run();
  +        }
  +        public void run(Object left, Object right) {
  +            run();
  +        }
  +    }
   
   	private UniversalProcedure throwNPE = new UniversalProcedure() {
   		public void run() {
   			throw new NullPointerException();
   		}
  -		public void run(Object obj) {
  -			run();
  -		}
  -		public void run(Object left, Object right) {
  -			run();
  -		}
   	};
       
   	private UniversalProcedure throwCCE = new UniversalProcedure() {
   		public void run() {
   			throw new ClassCastException();
  -		}
  -		public void run(Object obj) {
  -			run();
  -		}
  -		public void run(Object left, Object right) {
  -			run();
   		}
   	};
       
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message