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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to