001    package edu.nrao.sss.util;
002    
003    import java.util.ArrayList;
004    import java.util.Collection;
005    import java.util.List;
006    
007    /**
008     * A filter that is composed of other filters.
009     * <p>
010     * The primary purpose of this class is to allow for the logical
011     * (AND / OR) joining of other filters -- including other compound
012     * filters.  An individual filter uses only AND logic for its
013     * criteria.  In those situations where you need to allow passage
014     * of particles through a filter under condition A OR condition B,
015     * create a filter for each condition and add them to a
016     * <tt>CompoundFilter</tt> using the logical OR operator.</p>
017     * <p>
018     * <b>Version Info:</b>
019     * <table style="margin-left:2em">
020     *   <tr><td>$Revision: 819 $</td></tr>
021     *   <tr><td>$Date: 2007-08-13 16:25:44 -0600 (Mon, 13 Aug 2007) $</td></tr>
022     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
023     * </table></p>
024     * 
025     * @author David M. Harland
026     * @since 2007-08-13
027     */
028    public class CompoundFilter<T>
029      implements Filter<T>
030    {
031      private List<Filter<T>> filters;
032      private LogicalOperator operator;
033      
034      /**
035       * Creates a new wide open filter with the logical
036       * {@link LogicalOperator#AND AND} operator. 
037       */
038      public CompoundFilter()
039      {
040        this(null, LogicalOperator.AND);
041      }
042      
043      /**
044       * Creates a new compound filter with the given features.
045       * 
046       * @param filters the component filters for this compound filter.
047       *          If this value is <i>null</i>, it will be treated as
048       *          an empty collection.
049       *          
050       * @param operator see {@link #setOperator(LogicalOperator)}.
051       * 
052       * @throws IllegalArgumentException if {@code operator} is neither
053       *           <tt>AND</tt> nor <tt>OR</tt>.
054       */
055      public CompoundFilter(Collection<? extends Filter<T>> filters,
056                            LogicalOperator                 operator)
057      {
058        validate(operator);
059        this.operator = operator;
060        
061        this.filters = new ArrayList<Filter<T>>();
062    
063        if (filters != null)
064          this.filters.addAll(filters);
065      }
066      
067      /**
068       * Adds {@code newFilter} as a component of this compound filter.
069       * This filter will hold a reference to {@code newFilter}, so
070       * changes made to it after this call be reflected herein.
071       * 
072       * @param newFilter a new component filter.
073       * 
074       * @return this compound filter.
075       */
076      public CompoundFilter<T> add(Filter<T> newFilter)
077      {
078        if (newFilter != null && !filters.contains(newFilter))
079          filters.add(newFilter);
080        
081        return this;
082      }
083      
084      /**
085       * Adds each of the filters in the collection as components of this
086       * compound filter.
087       * 
088       * @param newFilters a collection of new component filters.
089       * 
090       * @return this compound filter.
091       * 
092       * @see #add(Filter)
093       */
094      public CompoundFilter<T> addAll(Collection<? extends Filter<T>> newFilters)
095      {
096        if (newFilters != null)
097          filters.addAll(newFilters);
098        
099        return this;
100      }
101      
102      /**
103       * The component filter to be removed from this compound filter.
104       * @param unwantedFilter the component filter to be removed.
105       * @return this compound filter.
106       */
107      public CompoundFilter<T> remove(Filter<T> unwantedFilter)
108      {
109        if (unwantedFilter != null)
110        {
111          while (filters.contains(unwantedFilter))
112            filters.remove(unwantedFilter);
113        }
114        
115        return this;
116      }
117      
118      /**
119       * The component filters to be removed from this compound filter.
120       * @param unwantedFilters the component filters to be removed.
121       * @return this compound filter.
122       */
123      public CompoundFilter<T>
124        removeAll(Collection<? extends Filter<T>> unwantedFilters)
125      {
126        for (Filter<T> unwantedFilter : unwantedFilters)
127          remove(unwantedFilter);
128        
129        return this;
130      }
131      
132      /**
133       * Removes all component filters from this compound filter.
134       * This action leaves this filter in a wide-open state (i.e.,
135       * one that allows passage of all particles).
136       */
137      public CompoundFilter<T> removeAllFilters()
138      {
139        filters.clear();
140        
141        return this;
142      }
143      
144      /**
145       * Sets the operator to use when joining the component filters of this
146       * compound filter.  The only two legal values are
147       * {@link LogicalOperator#AND} and {@link LogicalOperator#OR}.
148       * <p>
149       * The operator is used when deciding whether or not a given particle
150       * may pass through this compound filter.  In the case of <tt>AND</tt>,
151       * the particle must pass through <i>all</i> the component filters,
152       * while in the case of <tt>OR</tt> it pass through this filter if it
153       * passes through <i>any</i> of the component filters.</p> 
154       * 
155       * @param newOperator the operator for joining the component filters.
156       *          This value must be either {@link LogicalOperator#AND AND}
157       *          or {@link LogicalOperator#OR OR}.
158       * 
159       * @throws IllegalArgumentException if {@code newOperator} is neither
160       *           <tt>AND</tt> nor <tt>OR</tt>.
161       */
162      public void setOperator(LogicalOperator newOperator)
163      {
164        validate(newOperator);
165        operator = newOperator;
166      }
167      
168      /**
169       * Returns <i>true</i> if <tt>newOperator</tt> is a valid value.
170       */
171      private void validate(LogicalOperator newOperator)
172      {
173        boolean isValid =
174          newOperator != null && (newOperator == LogicalOperator.AND ||
175                                  newOperator == LogicalOperator.OR);
176        if (!isValid)
177          throw new IllegalArgumentException
178          (
179            newOperator +
180            " is not a valid operator for CompoundFilter.  Must be AND or OR."
181          );
182      }
183      
184      /**
185       * Returns <i>true</i> if this filter allows {@code particle}
186       * to pass through.
187       * <p>
188       * This compound filter delegates filtering decisions to its component
189       * filters and uses its logical operator to combine those results.
190       * All component filters are subject to the same operator, either
191       * <tt>AND</tt> or <tt>OR</tt>.  Short-circuit logic is used.  That is,
192       * when using the <tt>AND</tt> operator, evaluation stops with the
193       * first component that blocks the particle; when using the <tt>OR</tt>
194       * operator, evaluation stops with the first component filter that
195       * allows passage of the particle.</p>  
196       * 
197       * @param particle an object attempting to pass through this filter.
198       * 
199       * @return <i>true</i> if this filter passes {@code particle}.
200       */
201      public boolean allows(T particle)
202      {
203        //Quick exit if no filters
204        if (filters.size() == 0)
205          return true;
206        
207        boolean passesThrough;
208        
209        switch (operator)
210        {
211          case AND:
212            passesThrough = true;
213            for (Filter<T> filter : filters)
214            {
215              if (!filter.allows(particle))
216              {
217                passesThrough = false; //Must pass ALL filters
218                break;
219              }
220            }
221            break;
222            
223          case OR:
224            passesThrough = false;
225            for (Filter<T> filter : filters)
226            {
227              if (filter.allows(particle))
228              {
229                passesThrough = true; //Passing one filter is good enough
230                break;
231              }
232            }
233            break;
234    
235          default:
236            throw new RuntimeException("Programmer Error.  " +
237              "CompoundFilter.allows does not know about LogicalOperator." +
238              operator.name());
239        }
240        
241        return passesThrough;
242      }
243      
244      /**
245       * Returns <i>true</i> if this filter blocks {@code particle}
246       * from passing through.  This is
247       * a convenience method that is equivalent to {@code !allows(particle)}.
248       * 
249       * @param particle an object attempting to pass through this filter.
250       * @return <i>true</i> if this filter blocks {@code particle}.
251       */
252      public boolean blocks(T particle)
253      {
254        return !allows(particle);
255      }
256    }