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 */ 017package org.apache.commons.collections4.functors; 018 019import java.io.Serializable; 020import java.util.Map; 021import java.util.Objects; 022 023import org.apache.commons.collections4.Closure; 024import org.apache.commons.collections4.Predicate; 025 026/** 027 * Closure implementation calls the closure whose predicate returns true, 028 * like a switch statement. 029 * 030 * @since 3.0 031 */ 032public class SwitchClosure<E> implements Closure<E>, Serializable { 033 034 /** Serial version UID */ 035 private static final long serialVersionUID = 3518477308466486130L; 036 037 /** 038 * Create a new Closure that calls one of the closures depending 039 * on the predicates. 040 * <p> 041 * The Map consists of Predicate keys and Closure values. A closure 042 * is called if its matching predicate returns true. Each predicate is evaluated 043 * until one returns true. If no predicates evaluate to true, the default 044 * closure is called. The default closure is set in the map with a 045 * null key. The ordering is that of the iterator() method on the entryset 046 * collection of the map. 047 * 048 * @param <E> the type that the closure acts on 049 * @param predicatesAndClosures a map of predicates to closures 050 * @return the {@code switch} closure 051 * @throws NullPointerException if the map is null 052 * @throws NullPointerException if any closure in the map is null 053 * @throws ClassCastException if the map elements are of the wrong type 054 */ 055 @SuppressWarnings("unchecked") 056 public static <E> Closure<E> switchClosure(final Map<Predicate<E>, Closure<E>> predicatesAndClosures) { 057 Objects.requireNonNull(predicatesAndClosures, "predicatesAndClosures"); 058 // convert to array like this to guarantee iterator() ordering 059 final Closure<? super E> defaultClosure = predicatesAndClosures.remove(null); 060 final int size = predicatesAndClosures.size(); 061 if (size == 0) { 062 return (Closure<E>) (defaultClosure == null ? NOPClosure.<E>nopClosure() : defaultClosure); 063 } 064 final Closure<E>[] closures = new Closure[size]; 065 final Predicate<E>[] preds = new Predicate[size]; 066 int i = 0; 067 for (final Map.Entry<Predicate<E>, Closure<E>> entry : predicatesAndClosures.entrySet()) { 068 preds[i] = entry.getKey(); 069 closures[i] = entry.getValue(); 070 i++; 071 } 072 return new SwitchClosure<>(false, preds, closures, defaultClosure); 073 } 074 /** 075 * Factory method that performs validation and copies the parameter arrays. 076 * 077 * @param <E> the type that the closure acts on 078 * @param predicates array of predicates, cloned, no nulls 079 * @param closures matching array of closures, cloned, no nulls 080 * @param defaultClosure the closure to use if no match, null means nop 081 * @return the {@code chained} closure 082 * @throws NullPointerException if array is null 083 * @throws NullPointerException if any element in the array is null 084 * @throws IllegalArgumentException if the array lengths of predicates and closures do not match 085 */ 086 @SuppressWarnings("unchecked") 087 public static <E> Closure<E> switchClosure(final Predicate<? super E>[] predicates, 088 final Closure<? super E>[] closures, 089 final Closure<? super E> defaultClosure) { 090 FunctorUtils.validate(predicates); 091 FunctorUtils.validate(closures); 092 if (predicates.length != closures.length) { 093 throw new IllegalArgumentException("The predicate and closure arrays must be the same size"); 094 } 095 if (predicates.length == 0) { 096 return (Closure<E>) (defaultClosure == null ? NOPClosure.<E>nopClosure() : defaultClosure); 097 } 098 return new SwitchClosure<>(predicates, closures, defaultClosure); 099 } 100 /** The tests to consider */ 101 private final Predicate<? super E>[] iPredicates; 102 103 /** The matching closures to call */ 104 private final Closure<? super E>[] iClosures; 105 106 /** The default closure to call if no tests match */ 107 private final Closure<? super E> iDefault; 108 109 /** 110 * Hidden constructor for the use by the static factory methods. 111 * 112 * @param clone if {@code true} the input arguments will be cloned 113 * @param predicates array of predicates, no nulls 114 * @param closures matching array of closures, no nulls 115 * @param defaultClosure the closure to use if no match, null means nop 116 */ 117 private SwitchClosure(final boolean clone, final Predicate<? super E>[] predicates, 118 final Closure<? super E>[] closures, final Closure<? super E> defaultClosure) { 119 iPredicates = clone ? FunctorUtils.copy(predicates) : predicates; 120 iClosures = clone ? FunctorUtils.copy(closures) : closures; 121 iDefault = defaultClosure == null ? NOPClosure.<E>nopClosure() : defaultClosure; 122 } 123 124 /** 125 * Constructor that performs no validation. 126 * Use {@code switchClosure} if you want that. 127 * 128 * @param predicates array of predicates, cloned, no nulls 129 * @param closures matching array of closures, cloned, no nulls 130 * @param defaultClosure the closure to use if no match, null means nop 131 */ 132 public SwitchClosure(final Predicate<? super E>[] predicates, final Closure<? super E>[] closures, 133 final Closure<? super E> defaultClosure) { 134 this(true, predicates, closures, defaultClosure); 135 } 136 137 /** 138 * Executes the closure whose matching predicate returns true 139 * 140 * @param input the input object 141 */ 142 @Override 143 public void execute(final E input) { 144 for (int i = 0; i < iPredicates.length; i++) { 145 if (iPredicates[i].evaluate(input)) { 146 iClosures[i].execute(input); 147 return; 148 } 149 } 150 iDefault.execute(input); 151 } 152 153 /** 154 * Gets the closures. 155 * 156 * @return a copy of the closures 157 * @since 3.1 158 */ 159 public Closure<? super E>[] getClosures() { 160 return FunctorUtils.<E>copy(iClosures); 161 } 162 163 /** 164 * Gets the default closure. 165 * 166 * @return the default closure 167 * @since 3.1 168 */ 169 public Closure<? super E> getDefaultClosure() { 170 return iDefault; 171 } 172 173 /** 174 * Gets the predicates. 175 * 176 * @return a copy of the predicates 177 * @since 3.1 178 */ 179 public Predicate<? super E>[] getPredicates() { 180 return FunctorUtils.<E>copy(iPredicates); 181 } 182 183}