001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.commons.collections;
018    
019    import java.util.Collection;
020    import java.util.Iterator;
021    import java.util.Map;
022    
023    import org.apache.commons.collections.functors.ChainedClosure;
024    import org.apache.commons.collections.functors.EqualPredicate;
025    import org.apache.commons.collections.functors.ExceptionClosure;
026    import org.apache.commons.collections.functors.ForClosure;
027    import org.apache.commons.collections.functors.IfClosure;
028    import org.apache.commons.collections.functors.InvokerTransformer;
029    import org.apache.commons.collections.functors.NOPClosure;
030    import org.apache.commons.collections.functors.SwitchClosure;
031    import org.apache.commons.collections.functors.TransformerClosure;
032    import org.apache.commons.collections.functors.WhileClosure;
033    
034    /**
035     * <code>ClosureUtils</code> provides reference implementations and utilities
036     * for the Closure functor interface. The supplied closures are:
037     * <ul>
038     * <li>Invoker - invokes a method on the input object
039     * <li>For - repeatedly calls a closure for a fixed number of times
040     * <li>While - repeatedly calls a closure while a predicate is true
041     * <li>DoWhile - repeatedly calls a closure while a predicate is true
042     * <li>Chained - chains two or more closures together
043     * <li>Switch - calls one closure based on one or more predicates
044     * <li>SwitchMap - calls one closure looked up from a Map
045     * <li>Transformer - wraps a Transformer as a Closure
046     * <li>NOP - does nothing
047     * <li>Exception - always throws an exception
048     * </ul>
049     * All the supplied closures are Serializable.
050     * 
051     * @since Commons Collections 3.0
052     * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
053     *
054     * @author Stephen Colebourne
055     * @author Matt Benson
056     */
057    public class ClosureUtils {
058    
059        /**
060         * This class is not normally instantiated.
061         */
062        public ClosureUtils() {
063            super();
064        }
065    
066        /**
067         * Gets a Closure that always throws an exception.
068         * This could be useful during testing as a placeholder.
069         *
070         * @see org.apache.commons.collections.functors.ExceptionClosure
071         * 
072         * @return the closure
073         */
074        public static Closure exceptionClosure() {
075            return ExceptionClosure.INSTANCE;
076        }
077    
078        /**
079         * Gets a Closure that will do nothing.
080         * This could be useful during testing as a placeholder.
081         *
082         * @see org.apache.commons.collections.functors.NOPClosure
083         * 
084         * @return the closure
085         */
086        public static Closure nopClosure() {
087            return NOPClosure.INSTANCE;
088        }
089    
090        /**
091         * Creates a Closure that calls a Transformer each time it is called.
092         * The transformer will be called using the closure's input object.
093         * The transformer's result will be ignored.
094         *
095         * @see org.apache.commons.collections.functors.TransformerClosure
096         * 
097         * @param transformer  the transformer to run each time in the closure, null means nop
098         * @return the closure
099         */
100        public static Closure asClosure(Transformer transformer) {
101            return TransformerClosure.getInstance(transformer);
102        }
103    
104        /**
105         * Creates a Closure that will call the closure <code>count</code> times.
106         * <p>
107         * A null closure or zero count returns the <code>NOPClosure</code>.
108         *
109         * @see org.apache.commons.collections.functors.ForClosure
110         * 
111         * @param count  the number of times to loop
112         * @param closure  the closure to call repeatedly
113         * @return the <code>for</code> closure
114         */
115        public static Closure forClosure(int count, Closure closure) {
116            return ForClosure.getInstance(count, closure);
117        }
118    
119        /**
120         * Creates a Closure that will call the closure repeatedly until the 
121         * predicate returns false.
122         *
123         * @see org.apache.commons.collections.functors.WhileClosure
124         * 
125         * @param predicate  the predicate to use as an end of loop test, not null
126         * @param closure  the closure to call repeatedly, not null
127         * @return the <code>while</code> closure
128         * @throws IllegalArgumentException if either argument is null
129         */
130        public static Closure whileClosure(Predicate predicate, Closure closure) {
131            return WhileClosure.getInstance(predicate, closure, false);
132        }
133    
134        /**
135         * Creates a Closure that will call the closure once and then repeatedly
136         * until the predicate returns false.
137         *
138         * @see org.apache.commons.collections.functors.WhileClosure
139         * 
140         * @param closure  the closure to call repeatedly, not null
141         * @param predicate  the predicate to use as an end of loop test, not null
142         * @return the <code>do-while</code> closure
143         * @throws IllegalArgumentException if either argument is null
144         */
145        public static Closure doWhileClosure(Closure closure, Predicate predicate) {
146            return WhileClosure.getInstance(predicate, closure, true);
147        }
148    
149        /**
150         * Creates a Closure that will invoke a specific method on the closure's
151         * input object by reflection.
152         *
153         * @see org.apache.commons.collections.functors.InvokerTransformer
154         * @see org.apache.commons.collections.functors.TransformerClosure
155         * 
156         * @param methodName  the name of the method
157         * @return the <code>invoker</code> closure
158         * @throws IllegalArgumentException if the method name is null
159         */
160        public static Closure invokerClosure(String methodName) {
161            // reuse transformer as it has caching - this is lazy really, should have inner class here
162            return asClosure(InvokerTransformer.getInstance(methodName));
163        }
164    
165        /**
166         * Creates a Closure that will invoke a specific method on the closure's
167         * input object by reflection.
168         *
169         * @see org.apache.commons.collections.functors.InvokerTransformer
170         * @see org.apache.commons.collections.functors.TransformerClosure
171         * 
172         * @param methodName  the name of the method
173         * @param paramTypes  the parameter types
174         * @param args  the arguments
175         * @return the <code>invoker</code> closure
176         * @throws IllegalArgumentException if the method name is null
177         * @throws IllegalArgumentException if the paramTypes and args don't match
178         */
179        public static Closure invokerClosure(String methodName, Class[] paramTypes, Object[] args) {
180            // reuse transformer as it has caching - this is lazy really, should have inner class here
181            return asClosure(InvokerTransformer.getInstance(methodName, paramTypes, args));
182        }
183    
184        /**
185         * Create a new Closure that calls two Closures, passing the result of
186         * the first into the second.
187         * 
188         * @see org.apache.commons.collections.functors.ChainedClosure
189         * 
190         * @param closure1  the first closure
191         * @param closure2  the second closure
192         * @return the <code>chained</code> closure
193         * @throws IllegalArgumentException if either closure is null
194         */
195        public static Closure chainedClosure(Closure closure1, Closure closure2) {
196            return ChainedClosure.getInstance(closure1, closure2);
197        }
198    
199        /**
200         * Create a new Closure that calls each closure in turn, passing the 
201         * result into the next closure.
202         * 
203         * @see org.apache.commons.collections.functors.ChainedClosure
204         * 
205         * @param closures  an array of closures to chain
206         * @return the <code>chained</code> closure
207         * @throws IllegalArgumentException if the closures array is null
208         * @throws IllegalArgumentException if any closure in the array is null
209         */
210        public static Closure chainedClosure(Closure[] closures) {
211            return ChainedClosure.getInstance(closures);
212        }
213    
214        /**
215         * Create a new Closure that calls each closure in turn, passing the 
216         * result into the next closure. The ordering is that of the iterator()
217         * method on the collection.
218         * 
219         * @see org.apache.commons.collections.functors.ChainedClosure
220         * 
221         * @param closures  a collection of closures to chain
222         * @return the <code>chained</code> closure
223         * @throws IllegalArgumentException if the closures collection is null
224         * @throws IllegalArgumentException if the closures collection is empty
225         * @throws IllegalArgumentException if any closure in the collection is null
226         */
227        public static Closure chainedClosure(Collection closures) {
228            return ChainedClosure.getInstance(closures);
229        }
230    
231        /**
232         * Create a new Closure that calls another closure based on the
233         * result of the specified predicate.
234         * 
235         * @see org.apache.commons.collections.functors.IfClosure
236         * 
237         * @param predicate  the validating predicate
238         * @param trueClosure  the closure called if the predicate is true
239         * @return the <code>if</code> closure
240         * @throws IllegalArgumentException if the predicate is null
241         * @throws IllegalArgumentException if the closure is null
242         * @since Commons Collections 3.2
243         */
244        public static Closure ifClosure(Predicate predicate, Closure trueClosure) {
245            return IfClosure.getInstance(predicate, trueClosure);
246        }
247    
248        /**
249         * Create a new Closure that calls one of two closures depending 
250         * on the specified predicate.
251         * 
252         * @see org.apache.commons.collections.functors.IfClosure
253         * 
254         * @param predicate  the predicate to switch on
255         * @param trueClosure  the closure called if the predicate is true
256         * @param falseClosure  the closure called if the predicate is false
257         * @return the <code>switch</code> closure
258         * @throws IllegalArgumentException if the predicate is null
259         * @throws IllegalArgumentException if either closure is null
260         */
261        public static Closure ifClosure(Predicate predicate, Closure trueClosure, Closure falseClosure) {
262            return IfClosure.getInstance(predicate, trueClosure, falseClosure);
263        }
264    
265        /**
266         * Create a new Closure that calls one of the closures depending 
267         * on the predicates.
268         * <p>
269         * The closure at array location 0 is called if the predicate at array 
270         * location 0 returned true. Each predicate is evaluated
271         * until one returns true.
272         * 
273         * @see org.apache.commons.collections.functors.SwitchClosure
274         * 
275         * @param predicates  an array of predicates to check, not null
276         * @param closures  an array of closures to call, not null
277         * @return the <code>switch</code> closure
278         * @throws IllegalArgumentException if the either array is null
279         * @throws IllegalArgumentException if any element in the arrays is null
280         * @throws IllegalArgumentException if the arrays are different sizes
281         */
282        public static Closure switchClosure(Predicate[] predicates, Closure[] closures) {
283            return SwitchClosure.getInstance(predicates, closures, null);
284        }
285    
286        /**
287         * Create a new Closure that calls one of the closures depending 
288         * on the predicates.
289         * <p>
290         * The closure at array location 0 is called if the predicate at array
291         * location 0 returned true. Each predicate is evaluated
292         * until one returns true. If no predicates evaluate to true, the default
293         * closure is called.
294         * 
295         * @see org.apache.commons.collections.functors.SwitchClosure
296         * 
297         * @param predicates  an array of predicates to check, not null
298         * @param closures  an array of closures to call, not null
299         * @param defaultClosure  the default to call if no predicate matches
300         * @return the <code>switch</code> closure
301         * @throws IllegalArgumentException if the either array is null
302         * @throws IllegalArgumentException if any element in the arrays is null
303         * @throws IllegalArgumentException if the arrays are different sizes
304         */
305        public static Closure switchClosure(Predicate[] predicates, Closure[] closures, Closure defaultClosure) {
306            return SwitchClosure.getInstance(predicates, closures, defaultClosure);
307        }
308        
309        /**
310         * Create a new Closure that calls one of the closures depending 
311         * on the predicates. 
312         * <p>
313         * The Map consists of Predicate keys and Closure values. A closure 
314         * is called if its matching predicate returns true. Each predicate is evaluated
315         * until one returns true. If no predicates evaluate to true, the default
316         * closure is called. The default closure is set in the map with a 
317         * null key. The ordering is that of the iterator() method on the entryset 
318         * collection of the map.
319         * 
320         * @see org.apache.commons.collections.functors.SwitchClosure
321         * 
322         * @param predicatesAndClosures  a map of predicates to closures
323         * @return the <code>switch</code> closure
324         * @throws IllegalArgumentException if the map is null
325         * @throws IllegalArgumentException if the map is empty
326         * @throws IllegalArgumentException if any closure in the map is null
327         * @throws ClassCastException  if the map elements are of the wrong type
328         */
329        public static Closure switchClosure(Map predicatesAndClosures) {
330            return SwitchClosure.getInstance(predicatesAndClosures);
331        }
332    
333        /**
334         * Create a new Closure that uses the input object as a key to find the
335         * closure to call. 
336         * <p>
337         * The Map consists of object keys and Closure values. A closure 
338         * is called if the input object equals the key. If there is no match, the
339         * default closure is called. The default closure is set in the map
340         * using a null key.
341         * 
342         * @see org.apache.commons.collections.functors.SwitchClosure
343         * 
344         * @param objectsAndClosures  a map of objects to closures
345         * @return the closure
346         * @throws IllegalArgumentException if the map is null
347         * @throws IllegalArgumentException if the map is empty
348         * @throws IllegalArgumentException if any closure in the map is null
349         */
350        public static Closure switchMapClosure(Map objectsAndClosures) {
351            Closure[] trs = null;
352            Predicate[] preds = null;
353            if (objectsAndClosures == null) {
354                throw new IllegalArgumentException("The object and closure map must not be null");
355            }
356            Closure def = (Closure) objectsAndClosures.remove(null);
357            int size = objectsAndClosures.size();
358            trs = new Closure[size];
359            preds = new Predicate[size];
360            int i = 0;
361            for (Iterator it = objectsAndClosures.entrySet().iterator(); it.hasNext();) {
362                Map.Entry entry = (Map.Entry) it.next();
363                preds[i] = EqualPredicate.getInstance(entry.getKey());
364                trs[i] = (Closure) entry.getValue();
365                i++;
366            }
367            return switchClosure(preds, trs, def);
368        }
369    
370    }