diff --git a/common/source/java/ch/systemsx/cisd/common/collections/AbstractCollectionDecorator.java b/common/source/java/ch/systemsx/cisd/common/collections/AbstractCollectionDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..85094d8e0487b1167f03478f04bd8ded4b4f1a8a --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/collections/AbstractCollectionDecorator.java @@ -0,0 +1,153 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.systemsx.cisd.common.collections; + +import java.util.Collection; +import java.util.Iterator; + +/** + * Decorates another <code>Collection</code> to provide additional behaviour. + * <p> + * Each method call made on this <code>Collection</code> is forwarded to the decorated <code>Collection</code>. + * This class is used as a framework on which to build extensions. The main advantage of decoration is that one + * decorator can wrap any implementation of <code>Collection</code>, whereas sub-classing requires a new class to be + * written for each implementation. + * </p> + * + * @author Christian Ribeaud + */ +public abstract class AbstractCollectionDecorator<E> implements Collection<E> +{ + + /** The collection being decorated. */ + private final Collection<E> collection; + + /** + * Constructor that wraps (not copies). + * + * @param coll the collection to decorate, must not be <code>null</code>. + */ + protected AbstractCollectionDecorator(Collection<E> coll) + { + assert coll != null; + this.collection = coll; + } + + /** + * Gets the collection being decorated. + * + * @return the decorated collection + */ + protected Collection<E> getCollection() + { + return collection; + } + + // + // Collection + // + + public boolean add(E object) + { + return collection.add(object); + } + + public boolean addAll(Collection<? extends E> coll) + { + return collection.addAll(coll); + } + + public void clear() + { + collection.clear(); + } + + public boolean contains(Object object) + { + return collection.contains(object); + } + + public boolean isEmpty() + { + return collection.isEmpty(); + } + + public Iterator<E> iterator() + { + return collection.iterator(); + } + + public boolean remove(Object object) + { + return collection.remove(object); + } + + public int size() + { + return collection.size(); + } + + public Object[] toArray() + { + return collection.toArray(); + } + + public <T> T[] toArray(T[] object) + { + return collection.toArray(object); + } + + public boolean containsAll(Collection<?> coll) + { + return collection.containsAll(coll); + } + + public boolean removeAll(Collection<?> coll) + { + return collection.removeAll(coll); + } + + public boolean retainAll(Collection<?> coll) + { + return collection.retainAll(coll); + } + + // + // Object + // + + @Override + public final boolean equals(Object object) + { + if (object == this) + { + return true; + } + return collection.equals(object); + } + + @Override + public final int hashCode() + { + return collection.hashCode(); + } + + @Override + public final String toString() + { + return collection.toString(); + } +} diff --git a/common/source/java/ch/systemsx/cisd/common/collections/AbstractListIteratorDecorator.java b/common/source/java/ch/systemsx/cisd/common/collections/AbstractListIteratorDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..3fb567b7179d65e0e2ba53eeaf229d3cdf535dff --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/collections/AbstractListIteratorDecorator.java @@ -0,0 +1,104 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +import java.util.ListIterator; + +/** + * Provides basic behaviour for decorating a list iterator with extra functionality. + * <p> + * All methods are forwarded to the decorated list iterator. + * </p> + * + * @author Christian Ribeaud + */ +public class AbstractListIteratorDecorator<E> implements ListIterator<E> +{ + + /** The iterator being decorated */ + private final ListIterator<E> iterator; + + /** + * Constructor that decorates the specified iterator. + * + * @param iterator the iterator to decorate, must not be <code>null</code>. + */ + public AbstractListIteratorDecorator(ListIterator<E> iterator) + { + assert iterator != null; + this.iterator = iterator; + } + + /** + * Gets the iterator being decorated. + * + * @return the decorated iterator + */ + protected final ListIterator<E> getListIterator() + { + return iterator; + } + + // + // ListIterator + // + + public boolean hasNext() + { + return iterator.hasNext(); + } + + public E next() + { + return iterator.next(); + } + + public int nextIndex() + { + return iterator.nextIndex(); + } + + public boolean hasPrevious() + { + return iterator.hasPrevious(); + } + + public E previous() + { + return iterator.previous(); + } + + public int previousIndex() + { + return iterator.previousIndex(); + } + + public void remove() + { + iterator.remove(); + } + + public void set(E obj) + { + iterator.set(obj); + } + + public void add(E obj) + { + iterator.add(obj); + } +} diff --git a/common/source/java/ch/systemsx/cisd/common/collections/FilteredCollection.java b/common/source/java/ch/systemsx/cisd/common/collections/FilteredCollection.java new file mode 100644 index 0000000000000000000000000000000000000000..67145aee5ff91a2a63e98759db585fe12f93604f --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/collections/FilteredCollection.java @@ -0,0 +1,101 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +import java.util.Collection; + +/** + * Decorates another <code>Collection</code> to validate that additions match a specified <code>Validator</code>. + * <p> + * This collection exists to provide validation for the decorated collection. + * </p> + * + * @author Christian Ribeaud + */ +public class FilteredCollection<E> extends AbstractCollectionDecorator<E> +{ + + /** The validator to use. */ + protected final Validator<E> validator; + + /** + * Factory method to create a filtered (validating) collection. + * <p> + * If there are any elements already in the collection being decorated, they are not validated. + * </p> + * + * @param coll the collection to decorate, must not be <code>null</code> + * @param validator the predicate to use for validation, must not be <code>null</code> + * @return a new predicated collection + */ + public static <E> Collection<E> decorate(Collection<E> coll, Validator<E> validator) + { + return new FilteredCollection<E>(coll, validator); + } + + /** + * Constructor that wraps (not copies) given <code>Collection</code>. + * + * @param collection the collection to decorate, must not be <code>null</code>. + * @param validator the predicate to use for validation, must not be <code>null</code>. + */ + protected FilteredCollection(Collection<E> collection, Validator<E> validator) + { + super(collection); + assert validator != null; + + this.validator = validator; + } + + /** + * Validates the object being added. + * + * @param object the object being added + */ + protected boolean isValid(E object) + { + return validator.isValid(object); + } + + // + // AbstractCollectionDecorator + // + + @Override + public final boolean add(E object) + { + if (isValid(object)) + { + return getCollection().add(object); + } + return false; + } + + @Override + public final boolean addAll(Collection<? extends E> collection) + { + boolean changed = false; + for (E e : collection) + { + if (add(e) && changed == false) + { + changed = true; + } + } + return changed; + } +} diff --git a/common/source/java/ch/systemsx/cisd/common/collections/FilteredList.java b/common/source/java/ch/systemsx/cisd/common/collections/FilteredList.java new file mode 100644 index 0000000000000000000000000000000000000000..bda0dfed145df98c408a5749a03bff3e56117a51 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/collections/FilteredList.java @@ -0,0 +1,236 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +/** + * Decorates another <code>List</code> to validate that all additions match a specified <code>Validator</code>. + * <p> + * This list exists to provide validation for the decorated list. This class is not <code>Serializable</code>. + * </p> + * + * @author Christian Ribeaud + */ +public final class FilteredList<E> extends FilteredCollection<E> implements List<E> +{ + + /** + * Constructor that wraps (not copies) given <code>List</code>. + * + * @param list the list to decorate, must not be <code>null</code> + * @param validator the predicate to use for validation, must not be <code>null</code> + */ + protected FilteredList(List<E> list, Validator<E> validator) + { + super(list, validator); + } + + /** + * Factory method to create a filtered (validating) list. + * <p> + * If there are any elements already in the list being decorated, they are not validated. + * </p> + * + * @param list the list to decorate, must not be <code>null</code> + * @param validator the predicate to use for validation, must not be <code>null</code> + */ + public static <E> List<E> decorate(List<E> list, Validator<E> validator) + { + return new FilteredList<E>(list, validator); + } + + /** + * Gets the list being decorated. + * + * @return the decorated list + */ + protected List<E> getList() + { + return (List<E>) getCollection(); + } + + // + // List + // + + public E get(int index) + { + return getList().get(index); + } + + public int indexOf(Object object) + { + return getList().indexOf(object); + } + + public int lastIndexOf(Object object) + { + return getList().lastIndexOf(object); + } + + public E remove(int index) + { + return getList().remove(index); + } + + public void add(int index, E object) + { + if (isValid(object)) + { + getList().add(index, object); + } + } + + public boolean addAll(int index, Collection<? extends E> collection) + { + for (Iterator<? extends E> iter = collection.iterator(); iter.hasNext();) + { + E element = iter.next(); + if (isValid(element) == false) + { + iter.remove(); + } + } + return getList().addAll(index, collection); + } + + public ListIterator<E> listIterator() + { + return listIterator(0); + } + + public ListIterator<E> listIterator(int i) + { + return new FilteredListIterator(getList().listIterator(i)); + } + + public E set(int index, E object) + { + if (isValid(object)) + { + return getList().set(index, object); + } + return null; + } + + public List<E> subList(int fromIndex, int toIndex) + { + return new FilteredList<E>(getList().subList(fromIndex, toIndex), validator); + } + + @Override + public Iterator<E> iterator() + { + return listIterator(); + } + + /** + * Inner class <code>Iterator</code> for the <code>FilteredList</code>. + * + * @author Christian Ribeaud + */ + protected class FilteredListIterator extends AbstractListIteratorDecorator<E> + { + /** The next object in the iteration */ + private E nextObject; + + /** Whether the next object has been calculated yet. */ + private boolean nextObjectSet = false; + + /** + * Constructs a new <code>FilterIterator</code>. + * + * @param iterator the iterator to use. + */ + public FilteredListIterator(ListIterator<E> iterator) + { + super(iterator); + } + + /** + * Set <code>nextObject</code> to the next object. If there are no more objects then returns + * <code>false</code>. Otherwise, returns <code>true</code>. + */ + private boolean setNextObject() + { + ListIterator<E> iterator = getListIterator(); + while (iterator.hasNext()) + { + E object = iterator.next(); + if (validator.isValid(object)) + { + nextObject = object; + nextObjectSet = true; + return true; + } + } + return false; + } + + // + // AbstractListIteratorDecorator + // + + /** + * Returns <code>true</code> if the underlying iterator contains an object that matches the + * <code>Validator</code>. + * + * @return <code>true</code> if there is another object that matches the <code>Validator</code>. + */ + @Override + public final boolean hasNext() + { + if (nextObjectSet) + { + return true; + } else + { + return setNextObject(); + } + } + + @Override + public E next() + { + if (nextObjectSet == false) + { + if (setNextObject() == false) + { + throw new NoSuchElementException(); + } + } + nextObjectSet = false; + return nextObject; + } + + @Override + public void remove() + { + if (nextObjectSet) + { + throw new IllegalStateException("remove() cannot be called"); + } + listIterator().remove(); + } + + } +} diff --git a/common/source/java/ch/systemsx/cisd/common/collections/Validator.java b/common/source/java/ch/systemsx/cisd/common/collections/Validator.java new file mode 100644 index 0000000000000000000000000000000000000000..431af03ba15a96ecdb57ff7d801b91f7d6855310 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/collections/Validator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +/** + * Defines a functor interface implemented by classes that perform a predicate test on an object. + * <p> + * A <code>Validator</code> is the object equivalent of an <code>if</code> statement. It uses the input object to + * return a <code>true</code> or <code>false</code> value, and is often used in validation or filtering. + * </p> + * + * @author Christian Ribeaud + */ +public interface Validator<E> +{ + + /** + * Use the specified parameter to perform a test that returns <code>true</code> or <code>false</code>. + * + * @param object the typed object to evaluate + * @return <code>true</code> or <code>false</code> + */ + public boolean isValid(E object); + +} diff --git a/common/source/java/ch/systemsx/cisd/common/collections/ValidatorUtils.java b/common/source/java/ch/systemsx/cisd/common/collections/ValidatorUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..20bfa2956c51261276b39fa3ce85e7fcd8527261 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/collections/ValidatorUtils.java @@ -0,0 +1,77 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.commons.lang.StringUtils; + +/** + * <code>ValidatorUtils</code> provides reference implementations and utilities for the <code>Validator</code> + * interface. + * + * @author Christian Ribeaud + */ +public final class ValidatorUtils +{ + + private ValidatorUtils() + { + // Can not be instantiated. + } + + /** + * Creates a <code>Validator</code> based on the given pattern. + * + * @return <code>null</code> if given <var>pattern</var> is also <code>null</code>. + * @throws PatternSyntaxException if the expression's syntax is invalid. + */ + public final static Validator<String> createPatternValidator(final String pattern) + { + if (pattern == null) + { + return null; + } + final Pattern regEx = Pattern.compile(convertToRegEx(pattern)); + return new Validator<String>() + { + + // + // Validator + // + + public final boolean isValid(String text) + { + return regEx.matcher(text).matches(); + } + }; + } + + /** + * Converts given pattern into a regular expression. This method does the following: + * <ol> + * <li>replaces any <code>?</code> with <code>.</code></li> + * <li>replaces any <code>*</code> with <code>.*</code></li> + * </ol> + */ + final static String convertToRegEx(String pattern) + { + assert pattern != null; + return StringUtils.replace(pattern.replace('?', '.'), "*", ".*"); + } +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/collections/FilteredListTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/collections/FilteredListTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a068c3a42a1857d99d2a742ab2e1d7cf493ffa0b --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/collections/FilteredListTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; +import static org.testng.AssertJUnit.*; + +/** + * Tests for {@link FilteredList}. + * + * @author Christian Ribeaud + */ +public final class FilteredListTest +{ + + @Test + public final void testDecorate() + { + try + { + FilteredList.decorate(new ArrayList<String>(), null); + fail("Neither list nor validator can be null"); + } catch (AssertionError e) + { + // Nothing to do here. + } + try + { + FilteredList.decorate(null, new NullValidator()); + fail("Neither list nor validator can be null"); + } catch (AssertionError e) + { + // Nothing to do here. + } + } + + @Test + public final void testWithEmptyList() + { + List<String> list = FilteredList.decorate(new ArrayList<String>(), new NullValidator()); + list.add(null); + list.add(null); + list.add("0"); + list.add(null); + assert list.size() == 1; + try + { + list.set(1, "1"); + fail("IndexOutOfBoundsException should be thrown."); + } catch (IndexOutOfBoundsException e) + { + // Nothing to do here. + } + String old0 = list.set(0, "newO"); + assert list.size() == 1; + assertEquals("0", old0); + } + + @Test + public final void testWithNonEmptyList() + { + List<String> list = new ArrayList<String>(); + list.add(null); + list.add(null); + list.add("0"); + list.add("1"); + list.add(null); + list.add(null); + list.add("2"); + list.add(null); + assert list.size() == 8; + list = FilteredList.decorate(list, new NullValidator()); + assert list.size() == 8; + int count = 0; + for (final String string : list) + { + assertEquals(count++ + "", string); + } + assert count == 3; + } + + // + // Helper classes + // + + private final static class NullValidator implements Validator<String> + { + + // + // Validator + // + + public boolean isValid(String object) + { + return object != null; + } + } +} \ No newline at end of file diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/collections/ValidatorUtilsTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/collections/ValidatorUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..63bcc37985105bd032ab9806a58dec48fe1d11a4 --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/collections/ValidatorUtilsTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.common.collections; + +import static org.testng.AssertJUnit.*; + +import org.testng.annotations.Test; + +/** + * Tests for {@link ValidatorUtils}. + * + * @author Christian Ribeaud + */ +public final class ValidatorUtilsTest +{ + @Test + public final void testConvertToRegEx() + { + String s = null; + try + { + ValidatorUtils.convertToRegEx(s); + fail("Pattern can not be null."); + } catch (AssertionError e) + { + // Nothing to do here. + } + s = "he?lo"; + assertEquals("he.lo", ValidatorUtils.convertToRegEx(s)); + s = "he?l?"; + assertEquals("he.l.", ValidatorUtils.convertToRegEx(s)); + s = "he*o"; + assertEquals("he.*o", ValidatorUtils.convertToRegEx(s)); + s = "he*o*"; + assertEquals("he.*o.*", ValidatorUtils.convertToRegEx(s)); + s = "?h?l*o*"; + assertEquals(".h.l.*o.*", ValidatorUtils.convertToRegEx(s)); + } + + @Test + public final void testCreatePatternValidator() { + assertNull(ValidatorUtils.createPatternValidator(null)); + Validator<String> validator = ValidatorUtils.createPatternValidator("he*"); + assert validator.isValid("he"); + assert validator.isValid("hello"); + assert validator.isValid("hullo") == false; + validator = ValidatorUtils.createPatternValidator("he?lo"); + assert validator.isValid("helo") == false; + assert validator.isValid("hello"); + assert validator.isValid("he lo"); + assert validator.isValid("he.lo"); + assert validator.isValid("he\nlo") == false; + } +} \ No newline at end of file