diff --git a/src/java/com/samskivert/util/Predicate.java b/src/java/com/samskivert/util/Predicate.java new file mode 100644 index 00000000..4409aa5c --- /dev/null +++ b/src/java/com/samskivert/util/Predicate.java @@ -0,0 +1,300 @@ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-2004 Michael Bayne +// +// 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 2.1 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.util; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Arbitrates membership. + * + * Example usage: + * Find the best kind of peanut butter: + * public Iterator findBestKinds (Collection pbs) + * { + * return new Predicate() { + * public boolean isMatch (PeanutButter pb) + * { + * return pb.isCreamy() && !pb.containsPartiallyHydrogenatedOils(); + * } + * }.filter(pbs.iterator()); + * } + * + * An interface like this may someday be in the Java library, but until then... + */ +public abstract class Predicate +{ + //-------------------------------------------------------------------- + // But first, some handy utility classes: + + /** + * A simple predicate that includes an object if it is an instance + * of the specified class. + */ + public static class InstanceOf extends Predicate + { + public InstanceOf (Class clazz) + { + _class = clazz; + } + + // from Predicate + public boolean isMatch (T obj) + { + return _class.isInstance(obj); + } + + /** The class that must be implemented by applicants. */ + protected Class _class; + } + + /** + * Includes an object if and only if all the predicates with which it + * was constructed also include the object. The predicates + * are tested in the order specified and testing is halted as soon + * as one does not include the object. If the Predicate is constructed + * with no arguments, it will behave like TRUE. + */ + public static class And extends Predicate + { + public And (Predicate ... preds) + { + _preds = preds; + } + + // from Predicate + public boolean isMatch (T obj) + { + for (Predicate pred : _preds) { + if (!pred.isMatch(obj)) { + return false; + } + } + return true; + } + + /** The predicates that all must be satisfied. */ + protected Predicate[] _preds; + } + + /** + * Includes an object if at least one of the predicates specified in the + * constructor include the object. The predicates are tested in the + * order specified and testing is halted as soon as one includes + * the object. If the predicate is constructed with no arguments, + * it will behave like FALSE. + */ + public static class Or extends Predicate + { + public Or (Predicate ... preds) + { + _preds = preds; + } + + // from Predicate + public boolean isMatch (T obj) + { + for (Predicate pred : _preds) { + if (pred.isMatch(obj)) { + return true; + } + } + return false; + } + + protected Predicate[] _preds; + } + + /** + * Negates any other predicate. + */ + public static class Not extends Predicate + { + public Not (Predicate negated) + { + _pred = negated; + } + + // from Predicate + public boolean isMatch (T obj) + { + return !_pred.isMatch(obj); + } + + protected Predicate _pred; + } + + //-------------------------------------------------------------------- + // Here's the sole abstract method in the Predicate class: + + /** + * Does the specified object belong to the special set that we test for? + */ + public abstract boolean isMatch (T obj); + + //-------------------------------------------------------------------- + // And finally, some handy utility methods: + + /** + * Return a new iterator that contains only matching elements from + * the input iterator. + */ + public Iterator filter (final Iterator input) + { + return new Iterator() { + // from Iterator + public boolean hasNext () + { + return _found || findNext(); + } + + // from Iterator + public E next () + { + if (_found || findNext()) { + _found = false; + return _last; + + } else { + throw new NoSuchElementException(); + } + } + + // from Iterator + public void remove () + { + if (_removeable) { + _removeable = false; + input.remove(); + + } else { + throw new IllegalStateException(); + } + } + + private boolean findNext () + { + boolean result = false; + while (input.hasNext()) { + E candidate = input.next(); + if (isMatch(candidate)) { + _last = candidate; + result = true; + break; + } + } + + _found = result; + _removeable = result; + return result; + } + + private E _last; + private boolean _found; // because _last == null is a valid element + private boolean _removeable; + }; + } + + /** + * Remove non-matching elements from the specified collection. + */ + public void filter (Collection coll) + { + for (Iterator itr = coll.iterator(); itr.hasNext(); ) { + if (!isMatch(itr.next())) { + itr.remove(); + } + } + } + + /** + * Create an Iterable view of the specified Iterable that only contains + * elements that match the predicate. + * This Iterable can be iterated over at any time in the future to + * view the current predicate-matching elements of the input Iterable. + */ + public Iterable createView (final Iterable input) + { + return new Iterable() { + public Iterator iterator() { + return filter(input.iterator()); + } + }; + } + + /** + * Create a view of the specified collection that only contains elements + * that match the predicate. + * This collection can be examined at any time in the future to view + * the current predicate-matching elements of the input collection. + * + * Note that the view is not modifiable and currently has poor + * implementations of some methods. + */ + public Collection createView (final Collection input) + { + // TODO: create a collection of the same type? + return new AbstractCollection() { + // from abstract AbstractCollection + public int size () + { + // oh god, oh god: we iterate and count + int size = 0; + for (E elem : this) { + size++; + } + return size; + } + + @Override + public boolean add (E element) + { + return input.add(element); + } + + @Override + public boolean remove (Object element) + { + return input.remove(element); + } + + @Override + public boolean contains (Object element) + { + try { + @SuppressWarnings("unchecked") + E elem = (E) element; + return isMatch(elem) && input.contains(elem); + + } catch (ClassCastException cce) { + // since it's not an E, it can't be a member of our view + return false; + } + } + + // from abstract AbstractCollection + public Iterator iterator () + { + return filter(input.iterator()); + } + }; + } +}