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