/* XXL: The eXtensible and fleXible Library for data processing Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department of Mathematics and Computer Science University of Marburg Germany This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; If not, see <http://www.gnu.org/licenses/>. http://code.google.com/p/xxl/ */ package xxl.core.functions; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import xxl.core.cursors.AbstractCursor; import xxl.core.cursors.Cursor; import xxl.core.cursors.mappers.Mapper; /** * This class provides a functional <i>switch</i> statement. Arbitrary * functions can be registered to the switch function using an unique * identifier for each function. When the switch function is invoked, the first * parameter of the invocation method is taken as the identifier of the * internally stored function to be executed. If there is not such a case a * function representing the default case is invoked.<br /> * <b>Note,</b> that the iterations returned by the methods * <code>identifiers</code> and <code>functions</code> deliver their data in * the same order as it is put in the switch function. When a map is specified * to the constructor that already contains some functions the order of them is * specified by the map's {@link Map#keySet() keySet} method. * * @param <I> the type of the identifiers used for identifying the different * cases. * @param <R> the result type of the switch function, i.e., a supertype for all * given functions modeling the cases of the switch function. */ @SuppressWarnings("serial") public class Switch<I, R> extends AbstractFunction<Object, R> { /** * A map that is used to store the functions representing the different * cases of this switch function. */ protected Map<I, Function<Object, ? extends R>> functions; /** * An array-list storing the identifiers of the functions representing the * different cases of the switch function. This list is internally used to * save the order the functions representing the different cases of this * switch function are put in it. */ protected ArrayList<I> identifiers; /** * The default case of this switch function. It is invoked when is does not * contain a function for a given identifier. */ protected Function<Object, ? extends R> defaultFunction; /** * Creates a new switch function using the functions and their identifiers * stored in the given map to represent its different cases and the * specified function for representing the default case. The order of the * stored functions is given by the map's {@link Map#keySet() keySet} * method. * * @param functions a map storing the functions and their identifiers used * to represent its different cases. * @param defaultFunction a function representing the default case of this * switch function. */ public Switch(Map<I, Function<Object, ? extends R>> functions, Function<Object, ? extends R> defaultFunction) { this.functions = functions; this.identifiers = new ArrayList<I>(functions.keySet()); this.defaultFunction = defaultFunction; } /** * Creates a new switch function without a specified case and a function * representing the default case that returns an * <code>IllegalArgumentException</code> if it is invoked. In order to * replace the default case by a meaningful one, call the * {@link #putDefault(Function) putDefault} with the desired function. */ public Switch() { this( new HashMap<I, Function<Object, ? extends R>>(), new AbstractFunction<Object, R>() { @Override public R invoke(List<? extends Object> arguments) throws IllegalArgumentException { throw new IllegalArgumentException("the switch function does not contain a function for the given identifier and no default function is specified"); } } ); } /** * Associates the specified function with the case specified by the given * identifier in this switch function. If the switch function previously * contained a function dealing with this case, the old function is * replaced by the specified function. * * @param identifier an unique identifier associated with the case the * specified function deals with. * @param function the function that deals with the case associated with * the given identifier. * @return the function that previously dealt with the case the specified * identifier is associated with. */ public Function<Object, ? extends R> put(I identifier, Function<Object, ? extends R> function) { if (!functions.containsKey(identifier)) identifiers.add(identifier); return functions.put(identifier, function); } /** * Associates the specified function with the default case of the switch * function and returns the function previously associated with this case. * * @param function the function that deals with the default case. * @return the function that previously dealt with the default case. */ public Function<Object, ? extends R> putDefault(Function<Object, ? extends R> function) { Function<Object, ? extends R> defaultFunction = this.defaultFunction; this.defaultFunction = function; return defaultFunction; } /** * Removes the function associated with the specified identifier from this * switch function. If the switch function contains a function dealing with * this case, the function is removed and returned, otherwise * <code>null</code> is returned. * * @param identifier an unique identifier associated with the function * to be removed. * @return the function associated with the specified identifier or * <code>null</code> if this switch function contains no function * dealing with this case. */ public Function<Object, ? extends R> remove(I identifier) { if (functions.containsKey(identifier)) identifiers.remove(identifier); return functions.remove(identifier); } /** * Returns the function that deals with the case identified by the given * identifier. If the switch function associates no case with this * identifier the function representing the default case is returned. * * @param identifier an unique identifier associated with the case whose * function should be returned. * @return the function dealing with the case identified by the given * identifier or the function representing the default case if * there is no such case. */ public Function<Object, ? extends R> get(I identifier) { if (functions.containsKey(identifier)) return functions.get(identifier); return defaultFunction; } /** * Returns the function representing the default case of this switch * function. * * @return the function representing the default case of this switch * function. */ public Function<Object, ? extends R> getDefault() { return defaultFunction; } /** * Returns <code>true</code> if and only if this switch function contains a * function that deals with the case identified by the given identifier. If * it associates no case with this identifier <code>false</code> is * returned. * * @param identifier an unique identifier associated with the case that * should be tested for being handled by this switch function. * @return <code>true</code> if and only if this switch function contains a * function that deals with the case identified by the given * identifier, otherwise <tt>false</tt>. */ public boolean contains(I identifier) { return identifiers.contains(identifier); } /** * Returns an iteration over the identifiers associated with the cases of * this switch function. This iteration delivers its data in the same order * as it is put in the switch function. When a map is specified to the * constructor that already contains some functions the order of them is * specified by the map's {@link Map#keySet() keySet} method. * * @return an iteration containing the identifiers associated with the * cases of this switch function. */ public Cursor<I> identifiers() { return new AbstractCursor<I>() { protected int index = 0; @Override protected boolean hasNextObject() { return index < identifiers.size(); } @Override protected I nextObject() { return identifiers.get(index++); } @Override public void remove() throws IllegalStateException { super.remove(); functions.remove(identifiers.remove(--index)); } @Override public boolean supportsRemove() { return true; } }; } /** * Returns an iteration over the functions dealing with the cases of this * switch function. This iteration delivers its data in the same order * as it is put in the switch function. When a map is specified to the * constructor that already contains some functions the order of them is * specified by the map's {@link Map#keySet() keySet} method. * * @return an iteration containing the functions dealing with the cases of * this switch function. */ @SuppressWarnings("unchecked") public Cursor<Function<Object, ? extends R>> functions() { return new Mapper<I, Function<Object, ? extends R>>( new AbstractFunction<I, Function<Object, ? extends R>>() { @Override public Function<Object, ? extends R> invoke(I identifier) { return functions.get(identifier); } }, identifiers() ); } /** * Returns the result of the switch function as an object. The first * element of the given <code>Object</code> array is used to identify the * case of the switch function that should be invoked. Thereafter all * arguments are used to invoke the function that represents the desired * case. Because at least one argument is needed by the switch function to * identify the case to be invoked, this method throws an * <code>IllegalArgumentException</code> if it is invoked with zero * arguments or an empty <code>Object</code> array. * * @param arguments the arguments to the switch function whereas the first * element of the <code>Object</code> array identifies the case of * the switch function to be invoked with the remaining elements. * @return the return value of the function representing the case * identified by the first element of the given <code>Object</code> * array. * @throws IllegalArgumentException if the invoke method is called without * any arguments or with an empty <code>Object</code> array or * the switch function associates no case with the given identifier * and the function representing the default case is not set. */ @Override @SuppressWarnings("unchecked") // the first argument must be the identifier public R invoke(List<? extends Object> arguments) throws IllegalArgumentException { if (arguments.size() == 0) throw new IllegalArgumentException("switch functions must be invoked with at least one argument identifying the internally stored function to be invoked."); return get((I)arguments.get(0)).invoke(arguments); } }