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 }