View Javadoc

1   package net.sf.collections15.functors.closure;
2   
3   import net.sf.collections15.Closure;
4   import net.sf.collections15.functors.FunctorException;
5   
6   import java.io.Serializable;
7   import java.util.Collections;
8   import java.util.Comparator;
9   import java.util.HashMap;
10  import java.util.Map;
11  import java.util.SortedMap;
12  import java.util.TreeMap;
13  
14  /***
15   * <code>Closure</code> implementation that executes one of a number of
16   * <code>Closure</code>s in a map depending on which key in the map matches the
17   * input object.
18   *
19   * @author Chris Lambrou
20   * @since Collections15 1.0
21   */
22  public class SwitchMapClosure <E> implements Closure<E>, Serializable
23  {
24      /***
25       * Maps keys to the <code>Closure</code>s to be executed on input objects
26       * that match the keys.
27       */
28      private Map<E, Closure<? super E>> map;
29  
30      /***
31       * The default executed on any input object the doesn't match any of the
32       * keys.
33       */
34      private Closure<? super E> defaultClosure;
35  
36      /***
37       * Creates a new instance of a <code>SwitchMapClosure</code> that executes
38       * one of a number of <code>Closure</code>s in a map depending on which key
39       * in the map matches the input object, throwing an exception if the input
40       * object doesn't match any of the keys.
41       * <p/>
42       * The map consists of <code>Object</code> keys and <code>Closure</code>
43       * values. If the input object is mapped to a <code>Closure</code>, that
44       * <code>Closure</code> is executed on the input object. In addition, a
45       * default <code>Closure</code> can be specified that will be executed on an
46       * input object if it doesn't match any of the keys in the map.
47       *
48       * @param objectsAndClosures A map of <code>Objects</code>s to
49       *                           <code>Closure</code>s. If an input object is
50       *                           mapped to a <code>Closure</code> in the map,
51       *                           that <code>Closure</code> is executed.
52       *                           <p/>
53       *                           This map is defensively copied into either a
54       *                           <code>TreeMap</code> (if this map is sorted) or
55       *                           a <code>HashMap</code> (if it's not).
56       *
57       * @return A new instance of a <code>SwitchMapClosure</code> that executes
58       *         one of a number of <code>Closure</code>s in a map depending on
59       *         which key in the map matches the input object, throwing an
60       *         exception if the input object doesn't match any of the keys.
61       *
62       * @throws IllegalArgumentException If the map is <code>null</code>.
63       * @throws IllegalArgumentException If any <code>Closure</code> in the map
64       *                                  is <code>null</code>.
65       */
66      public static <T> SwitchMapClosure<T> getInstance(Map<T, Closure<? super T>> objectsAndClosures)
67      {
68          return getInstance(objectsAndClosures, ExceptionClosure.getInstance(
69                  "Unknown case - SwitchMapClosure cannot execute the specified target object"));
70      }
71  
72      /***
73       * Creates a new instance of a <code>SwitchMapClosure</code> that executes
74       * one of a number of <code>Closure</code>s in a map depending on which key
75       * in the map matches the input object, or the default <code>Closure</code>
76       * if the input object doesn't match any of the keys.
77       * <p/>
78       * The map consists of <code>Object</code> keys and <code>Closure</code>
79       * values. If the input object is mapped to a <code>Closure</code>, that
80       * <code>Closure</code> is executed on the input object. In addition, a
81       * default <code>Closure</code> can be specified that will be executed on an
82       * input object if it doesn't match any of the keys in the map.
83       *
84       * @param objectsAndClosures A map of <code>Objects</code>s to
85       *                           <code>Closure</code>s. If an input object is
86       *                           mapped to a <code>Closure</code> in the map,
87       *                           that <code>Closure</code> is executed.
88       *                           <p/>
89       *                           This map is defensively copied into either a
90       *                           <code>TreeMap</code> (if this map is sorted) or
91       *                           a <code>HashMap</code> (if it's not).
92       * @param defaultClosure     If an input object doesn't map to a
93       *                           <code>Closure</code>, then this <code>Closure</code>,
94       *                           if non-<code>null</code>, will be executed on
95       *                           the input object. If this default is
96       *                           <code>null</code> then the {@link #execute}
97       *                           method will throw an <code>IllegalArgumentException</code>.
98       *
99       * @return A new instance of a <code>SwitchMapClosure</code> that executes
100      *         one of a number of <code>Closure</code>s in a map depending on
101      *         which key in the map matches the input object, or the default
102      *         <code>Closure</code> if the input object doesn't match any of the
103      *         keys.
104      *
105      * @throws IllegalArgumentException If the map is <code>null</code>.
106      * @throws IllegalArgumentException If any <code>Closure</code> in the map
107      *                                  is <code>null</code>.
108      */
109     public static <T> SwitchMapClosure<T> getInstance(Map<T, Closure<? super T>> objectsAndClosures, Closure<? super T> defaultClosure)
110     {
111         if (defaultClosure == null) {
112             throw new IllegalArgumentException("null defaultClosure not allowed");
113         }
114         return new SwitchMapClosure<T>(objectsAndClosures, defaultClosure);
115     }
116 
117     /***
118      * Creates a new instance that executes one of a number of
119      * <code>Closure</code>s in a map depending on which key in the map matches
120      * the input object.
121      * <p/>
122      * The map consists of <code>Object</code> keys and <code>Closure</code>
123      * values. If the input object is mapped to a <code>Closure</code>, that
124      * <code>Closure</code> is executed on the input object. In addition, a
125      * default <code>Closure</code> can be specified that will be executed on an
126      * input object if it doesn't match any of the keys in the map.
127      *
128      * @param objectsAndClosures A map of <code>Objects</code>s to
129      *                           <code>Closure</code>s. If an input object is
130      *                           mapped to a <code>Closure</code> in the map,
131      *                           that <code>Closure</code> is executed.
132      *                           <p/>
133      *                           This map is defensively copied into either a
134      *                           <code>TreeMap</code> (if this map is sorted) or
135      *                           a <code>HashMap</code> (if it's not).
136      * @param defaultClosure     If an input object doesn't map to a
137      *                           <code>Closure</code>, then this <code>Closure</code>,
138      *                           if non-<code>null</code>, will be executed on
139      *                           the input object. If this default is
140      *                           <code>null</code> then the {@link #execute}
141      *                           method will throw an <code>IllegalArgumentException</code>.
142      *
143      * @throws IllegalArgumentException If the map is <code>null</code>.
144      * @throws IllegalArgumentException If any <code>Closure</code> in the map
145      *                                  is <code>null</code>.
146      */
147     protected SwitchMapClosure(Map<E, Closure<? super E>> objectsAndClosures, Closure<? super E> defaultClosure)
148     {
149         if (objectsAndClosures == null) {
150             throw new IllegalArgumentException("null objectsAndClosures argument not allowed");
151         }
152         this.defaultClosure = defaultClosure;
153 
154         //create a new internal map to hold the objects and closures
155         if (objectsAndClosures instanceof SortedMap) {
156             Comparator<? super E> comparator = ((SortedMap<E, Closure<? super E>>) objectsAndClosures).comparator();
157             map = new TreeMap<E, Closure<? super E>>(comparator);
158         }
159         else {
160             int size = (2 + (objectsAndClosures.size() * 4)) / 3; //this calulation is due to HashMap's misleading definition of initial capacity
161             map = new HashMap<E, Closure<? super E>>(size);
162         }
163 
164         //copy the objects and arguments into the new map, validating them on the fly
165         for (Map.Entry<E, Closure<? super E>> entry : objectsAndClosures.entrySet()) {
166             Closure<? super E> closure = entry.getValue();
167             if (closure == null) {
168                 throw new IllegalArgumentException("null closure encountered in objectsAndClosures argument");
169             }
170             map.put(entry.getKey(), closure);
171         }
172     }
173 
174     /***
175      * Executes one of the <code>Closure</code>s on the input object, using the
176      * input object to look up which <code>Closure</code> to execute.
177      *
178      * @param input The input to execute the <code>Closure</code> on.
179      *
180      * @throws FunctorException Thrown if the input object doesn't match any of
181      *                          the keys and no default <code>Closure</code> is
182      *                          defined.
183      */
184     public void execute(E input) throws FunctorException
185     {
186         Closure<? super E> closure = map.get(input);
187         if (closure == null) {
188             if (defaultClosure == null) {
189                 throw new FunctorException("specified input doesn't match a known key and no default closure is defined");
190             }
191             defaultClosure.execute(input);
192         }
193         else {
194             closure.execute(input);
195         }
196 
197     }
198 
199     /***
200      * Returns the mappings defined by this <code>SwitchMapClosure</code>.
201      *
202      * @return A read-only view of the mappings defined by this
203      *         <code>SwitchMapClosure</code>.
204      *
205      * @see #getDefaultClosure()
206      * @since Collections15 1.0
207      */
208     public Map<E, Closure<? super E>> getMappings()
209     {
210         return Collections.unmodifiableMap(map);
211     }
212 
213     /***
214      * Returns the default <code>Closure</code>.
215      *
216      * @return The default <code>Closure</code> applied to an input object if it
217      *         doesn't satisfy any of the cases defined for this
218      *         <code>SwitchClosure</code>. May be <code>null</code> if no
219      *         default is defined.
220      *
221      * @see #getMappings()
222      * @since Collections15 1.0
223      */
224     public Closure<? super E> getDefaultClosure()
225     {
226         return defaultClosure;
227     }
228 }