View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.functors;
18  
19  import java.io.Serializable;
20  import java.util.Map;
21  import java.util.Objects;
22  
23  import org.apache.commons.collections4.Closure;
24  import org.apache.commons.collections4.Predicate;
25  
26  /**
27   * Closure implementation calls the closure whose predicate returns true,
28   * like a switch statement.
29   *
30   * @param <T> the type of the input to the operation.
31   * @since 3.0
32   */
33  public class SwitchClosure<T> implements Closure<T>, Serializable {
34  
35      /** Serial version UID */
36      private static final long serialVersionUID = 3518477308466486130L;
37  
38      /**
39       * Create a new Closure that calls one of the closures depending
40       * on the predicates.
41       * <p>
42       * The Map consists of Predicate keys and Closure values. A closure
43       * is called if its matching predicate returns true. Each predicate is evaluated
44       * until one returns true. If no predicates evaluate to true, the default
45       * closure is called. The default closure is set in the map with a
46       * null key. The ordering is that of the iterator() method on the entryset
47       * collection of the map.
48       *
49       * @param <E> the type that the closure acts on
50       * @param predicatesAndClosures  a map of predicates to closures
51       * @return the {@code switch} closure
52       * @throws NullPointerException if the map is null
53       * @throws NullPointerException if any closure in the map is null
54       * @throws ClassCastException  if the map elements are of the wrong type
55       */
56      @SuppressWarnings("unchecked")
57      public static <E> Closure<E> switchClosure(final Map<Predicate<E>, Closure<E>> predicatesAndClosures) {
58          Objects.requireNonNull(predicatesAndClosures, "predicatesAndClosures");
59          // convert to array like this to guarantee iterator() ordering
60          final Closure<? super E> defaultClosure = predicatesAndClosures.remove(null);
61          final int size = predicatesAndClosures.size();
62          if (size == 0) {
63              return (Closure<E>) (defaultClosure == null ? NOPClosure.<E>nopClosure() : defaultClosure);
64          }
65          final Closure<E>[] closures = new Closure[size];
66          final Predicate<E>[] preds = new Predicate[size];
67          int i = 0;
68          for (final Map.Entry<Predicate<E>, Closure<E>> entry : predicatesAndClosures.entrySet()) {
69              preds[i] = entry.getKey();
70              closures[i] = entry.getValue();
71              i++;
72          }
73          return new SwitchClosure<>(false, preds, closures, defaultClosure);
74      }
75      /**
76       * Factory method that performs validation and copies the parameter arrays.
77       *
78       * @param <E> the type that the closure acts on
79       * @param predicates  array of predicates, cloned, no nulls
80       * @param closures  matching array of closures, cloned, no nulls
81       * @param defaultClosure  the closure to use if no match, null means nop
82       * @return the {@code chained} closure
83       * @throws NullPointerException if array is null
84       * @throws NullPointerException if any element in the array is null
85       * @throws IllegalArgumentException if the array lengths of predicates and closures do not match
86       */
87      @SuppressWarnings("unchecked")
88      public static <E> Closure<E> switchClosure(final Predicate<? super E>[] predicates,
89                                                 final Closure<? super E>[] closures,
90                                                 final Closure<? super E> defaultClosure) {
91          FunctorUtils.validate(predicates);
92          FunctorUtils.validate(closures);
93          if (predicates.length != closures.length) {
94              throw new IllegalArgumentException("The predicate and closure arrays must be the same size");
95          }
96          if (predicates.length == 0) {
97              return (Closure<E>) (defaultClosure == null ? NOPClosure.<E>nopClosure() : defaultClosure);
98          }
99          return new SwitchClosure<>(predicates, closures, defaultClosure);
100     }
101     /** The tests to consider */
102     private final Predicate<? super T>[] iPredicates;
103 
104     /** The matching closures to call */
105     private final Closure<? super T>[] iClosures;
106 
107     /** The default closure to call if no tests match */
108     private final Closure<? super T> iDefault;
109 
110     /**
111      * Hidden constructor for the use by the static factory methods.
112      *
113      * @param clone  if {@code true} the input arguments will be cloned
114      * @param predicates  array of predicates, no nulls
115      * @param closures  matching array of closures, no nulls
116      * @param defaultClosure  the closure to use if no match, null means nop
117      */
118     private SwitchClosure(final boolean clone, final Predicate<? super T>[] predicates,
119                           final Closure<? super T>[] closures, final Closure<? super T> defaultClosure) {
120         iPredicates = clone ? FunctorUtils.copy(predicates) : predicates;
121         iClosures = clone ? FunctorUtils.copy(closures) : closures;
122         iDefault = defaultClosure == null ? NOPClosure.<T>nopClosure() : defaultClosure;
123     }
124 
125     /**
126      * Constructor that performs no validation.
127      * Use {@code switchClosure} if you want that.
128      *
129      * @param predicates  array of predicates, cloned, no nulls
130      * @param closures  matching array of closures, cloned, no nulls
131      * @param defaultClosure  the closure to use if no match, null means nop
132      */
133     public SwitchClosure(final Predicate<? super T>[] predicates, final Closure<? super T>[] closures,
134                          final Closure<? super T> defaultClosure) {
135         this(true, predicates, closures, defaultClosure);
136     }
137 
138     /**
139      * Executes the closure whose matching predicate returns true
140      *
141      * @param input  the input object
142      */
143     @Override
144     public void execute(final T input) {
145         for (int i = 0; i < iPredicates.length; i++) {
146             if (iPredicates[i].test(input)) {
147                 iClosures[i].accept(input);
148                 return;
149             }
150         }
151         iDefault.accept(input);
152     }
153 
154     /**
155      * Gets the closures.
156      *
157      * @return a copy of the closures
158      * @since 3.1
159      */
160     public Closure<? super T>[] getClosures() {
161         return FunctorUtils.copy(iClosures);
162     }
163 
164     /**
165      * Gets the default closure.
166      *
167      * @return the default closure
168      * @since 3.1
169      */
170     public Closure<? super T> getDefaultClosure() {
171         return iDefault;
172     }
173 
174     /**
175      * Gets the predicates.
176      *
177      * @return a copy of the predicates
178      * @since 3.1
179      */
180     public Predicate<? super T>[] getPredicates() {
181         return FunctorUtils.copy(iPredicates);
182     }
183 
184 }