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.functor.core.collection;
018
019import java.util.Collections;
020import java.util.Iterator;
021
022import org.apache.commons.functor.UnaryFunction;
023import org.apache.commons.functor.UnaryPredicate;
024import org.apache.commons.functor.core.IsInstance;
025import org.apache.commons.functor.core.composite.UnaryAnd;
026
027/**
028 * Adds a fluent filtering API to any {@link Iterable}.
029 *
030 * @version $Revision: 1344786 $ $Date: 2012-05-31 11:55:31 -0400 (Thu, 31 May 2012) $
031 *
032 * @param <T> the Iterable generic type
033 */
034public class FilteredIterable<T> implements Iterable<T> {
035    /**
036     * A default {@code FilteredIterable} static instance that iterates over an empty collection.
037     */
038    @SuppressWarnings({ "rawtypes", "unchecked" })
039    // type irrelevant for empty instance
040    private static final FilteredIterable EMPTY = new FilteredIterable(Collections.EMPTY_LIST) {
041        /**
042         * {@inheritDoc}
043         */
044        @Override
045        public FilteredIterable retain(Class type) {
046            return this;
047        }
048
049        /**
050         * {@inheritDoc}
051         */
052        @Override
053        public FilteredIterable retain(UnaryPredicate predicate) {
054            return this;
055        }
056
057        /**
058         * {@inheritDoc}
059         */
060        @Override
061        public FilteredIterable retain(Class... ofType) {
062            return this;
063        }
064    };
065
066    /**
067     * The {@link Iterable} has to be filtered.
068     */
069    private final Iterable<? extends T> iterable;
070    /**
071     * The predicate used to test input {@link Iterable} elements.
072     */
073    private UnaryAnd<T> predicate;
074
075    /**
076     * Create a new FilteredIterable.
077     * @param iterable wrapped
078     */
079    protected FilteredIterable(Iterable<? extends T> iterable) {
080        super();
081        this.iterable = iterable;
082    }
083
084    /**
085     * {@inheritDoc}
086     */
087    public Iterator<T> iterator() {
088        UnaryPredicate<T> predicateReference;
089        synchronized (this) {
090            predicateReference = predicate;
091        }
092        return FilteredIterator.filter(iterable.iterator(), predicateReference);
093    }
094
095    /**
096     * {@inheritDoc}
097     */
098    @Override
099    public String toString() {
100        return "FilteredIterable<" + iterable + ">";
101    }
102
103    /**
104     * Retain only elements matching <code>predicate</code>.
105     * @param filter filter predicate, non-<code>null</code>
106     * @return <code>this</code>, fluently
107     */
108    public FilteredIterable<T> retain(UnaryPredicate<? super T> filter) {
109        if (filter == null) {
110            throw new NullPointerException("filtering predicate was null");
111        }
112        synchronized (this) {
113            if (this.predicate == null) {
114                this.predicate = new UnaryAnd<T>();
115            }
116            this.predicate.and(filter);
117        }
118        return this;
119    }
120
121    /**
122     * Retain elements of a given type with type-safety.
123     *
124     * @param <U> the input Class generic type.
125     * @param type filter, non-<code>null</code>
126     * @return new FilteredIterable instance that delegates to <code>this</code>
127     */
128    public <U> FilteredIterable<U> retain(final Class<U> type) {
129        if (type == null) {
130            throw new NullPointerException("filtered type was null");
131        }
132        return new FilteredIterable<U>(new Iterable<U>() {
133
134            public Iterator<U> iterator() {
135                return TransformedIterator.transform(
136                        FilteredIterator.filter(FilteredIterable.this.iterator(), IsInstance.of(type)),
137                        new UnaryFunction<T, U>() {
138
139                            @SuppressWarnings("unchecked")
140                            // this is okay because of the isinstance check
141                            public U evaluate(T obj) {
142                                return (U) obj;
143                            }
144                        });
145            }
146
147        });
148    }
149
150    /**
151     * Retain elements of any of specified types.
152     * @param ofType filter, non-<code>null</code>
153     * @return <code>this</code>, fluently
154     */
155    public FilteredIterable<T> retain(final Class<?>... ofType) {
156        if (ofType == null) {
157            throw new NullPointerException("array of filtered types was null");
158        }
159        return retain(new UnaryPredicate<T>() {
160
161            public boolean test(T obj) {
162                for (Class<?> type : ofType) {
163                    if (type.isInstance(obj)) {
164                        return true;
165                    }
166                }
167                return false;
168            }
169        });
170    }
171
172    /**
173     * Get a {@link FilteredIterable} of <code>iterable</code>.  If <code>wrapped</code> is <code>null</code>,
174     * result will also be <code>null</code>.  A {@link FilteredIterable} argument will be passed back
175     * directly; any other argument will be wrapped in a new {@link FilteredIterable} object.
176     * @param <T> the input iterable generic type
177     * @param iterable wrapped
178     * @return FilteredIterable
179     */
180    public static <T> FilteredIterable<T> of(Iterable<T> iterable) {
181        if (iterable == null) {
182            return null;
183        }
184        if (iterable instanceof FilteredIterable<?>) {
185            return (FilteredIterable<T>) iterable;
186        }
187        return new FilteredIterable<T>(iterable);
188    }
189
190    /**
191     * Get an empty FilteredIterable.
192     * @param <T> the expected {@link Iterable} generic type.
193     * @return FilteredIterable<T>
194     */
195    @SuppressWarnings("unchecked")
196    public static <T> FilteredIterable<T> empty() {
197        return EMPTY;
198    }
199}