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
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;
161 map = new HashMap<E, Closure<? super E>>(size);
162 }
163
164
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 }