001    package edu.nrao.sss.model.source;
002    
003    import java.util.ArrayList;
004    import java.util.Collection;
005    import java.util.Date;
006    import java.util.HashMap;
007    import java.util.HashSet;
008    import java.util.Map;
009    import java.util.Set;
010    
011    import java.util.regex.Pattern;
012    import java.util.regex.PatternSyntaxException;
013    
014    import org.apache.log4j.Logger;
015    
016    import edu.nrao.sss.astronomy.CelestialCoordinateSystem;
017    import edu.nrao.sss.astronomy.CoordinateConversionException;
018    import edu.nrao.sss.astronomy.Epoch;
019    import edu.nrao.sss.astronomy.SkyPosition;
020    import edu.nrao.sss.astronomy.SkyPositionFilter;
021    import edu.nrao.sss.geom.EarthPosition;
022    import edu.nrao.sss.measure.LocalSiderealTime;
023    import edu.nrao.sss.model.resource.TelescopeType;
024    import edu.nrao.sss.util.Filter;
025    
026    /**
027     * A filter that operates on sources.
028     * <p>
029     * <b>Revision Info:</b>
030     * <table style="margin-left:2em">
031     *   <tr><td>$Revision: 2314 $</td></tr>
032     *   <tr><td>$Date: 2009-05-21 11:56:06 -0600 (Thu, 21 May 2009) $</td></tr>
033     *   <tr><td>$Author: btruitt $ (last person to modify)</td></tr>
034     * </table></p>
035     *  
036     * @author David M. Harland
037     * @since 2006-06-27
038     */
039    public class SourceFilter
040      implements Filter<Source>
041    {
042      private static final Logger log = Logger.getLogger(SourceFilter.class);
043      
044      private static final Pattern     ANY_NAME = null;
045      private static final Set<String> ANY_UDV = null;
046    
047      /**
048       * A constant that indicates the current time should be used when evaluating
049       * the information held by a source.
050       */
051      public static final Date CURRENT_TIME = null;
052    
053            public static final boolean INCLUDE_ALIASES = true;
054            public static final boolean EXCLUDE_ALIASES = false;
055    
056      private Pattern namePattern;
057            private boolean includeAliases = INCLUDE_ALIASES;
058      
059      private Set<String>              forbiddenUdvKeys;
060      private Map<String, Set<String>> requiredUdvs;
061      
062      private SkyPositionFilter         positionFilter;
063      private boolean                   convertPositions;
064      private CelestialCoordinateSystem coordSys;
065      private Epoch                     epoch;
066      
067      private Set<SourceBrightnessFilter> brightnessFilters;
068    
069      private Date queryTime;
070    
071      /**
072       * Creates a new wide-open filter that allows all sources to pass.
073       * Source position conversion will be <em>on</em> and the default
074       * conversion will be to J2000 right ascension and declination.
075       */
076      public SourceFilter()
077      {
078        forbiddenUdvKeys  = new HashSet<String>();
079        requiredUdvs      = new HashMap<String, Set<String>>();
080        brightnessFilters = new HashSet<SourceBrightnessFilter>();
081        
082        clearAll();
083      }
084    
085      //============================================================================
086      // CLEARING THE FILTERING CRITERIA
087      //============================================================================
088    
089      /**
090       * Sets this filter to a wide-open state.
091       * Also turns on position conversion and sets the system to
092       * J2000 right ascension and declination.
093       * After this call all filtering criteria will be in their wide-open
094       * states and this filter will allow all sources to pass. 
095       */
096      public void clearAll()
097      {
098        clearNamePattern();
099        clearUserDefinedValues();
100        clearPositionFilter();
101        clearCoordSysAndEpoch();
102        turnOnPositionConversion();
103        clearBrightnessFilters();
104        clearQueryTime();
105      }
106      
107      /**
108       * Removes the position filter from this filter.
109       * This has the effect of putting position filtering into
110       * its wide-open state.
111       */
112      public void clearPositionFilter()
113      {
114        positionFilter = null;
115      }
116      
117      /**
118       * Removes all brightness filters from this filter.
119       * This has the effect of putting brightness filtering into
120       * its wide-open state.
121       */
122      public void clearBrightnessFilters()
123      {
124        brightnessFilters.clear();
125      }
126      
127      /**
128       * Sets the name criterion to its wide-open state.
129       * 
130       * @see #setNamePattern(Pattern, boolean)
131       * @see #setNamePattern(String, boolean)
132       */
133      public void clearNamePattern()
134      {
135        namePattern = ANY_NAME;
136      }
137      
138      /**
139       * Sets the UDV criterion to its wide-open state.
140       * Both required and forbidden UDVs are erased and UDVs
141       * do not enter into the filtering operations once this
142       * method has been called.
143       * 
144       * @since 2008-07-28
145       */
146      public void clearUserDefinedValues()
147      {
148        forbiddenUdvKeys.clear();
149        requiredUdvs.clear();
150      }
151      
152      /**
153       * Sets the coordinate system to
154       * {@link CelestialCoordinateSystem#EQUATORIAL equatorial}
155       * and the epoch to {@link Epoch#J2000}.
156       * This method does not effect the decision to turn
157       * conversions on or off.
158       * 
159       * @see #turnOffPositionConversion()
160       * @see #turnOnPositionConversion()
161       */
162      public void clearCoordSysAndEpoch()
163      {
164        coordSys = CelestialCoordinateSystem.EQUATORIAL;
165        epoch    = Epoch.J2000;
166      }
167      
168      /**
169       * Prevents this filter from performing conversions of source positions to a
170       * common coordinate system and epoch.  Turning off the conversion will
171       * speed up the filtering process and is useful when a client knows that
172       * the positions are all already in the same system.
173       * <p>
174       * A newly created or cleared filter will have conversion turned <em>on</em>.
175       * </p> 
176       */
177      public void turnOffPositionConversion()
178      {
179        convertPositions = false;
180      }
181    
182      /**
183       * Tells this filter to perform conversions of source positions to a common
184       * coordinate system and epoch before filtering on position.
185       * 
186       * @see #turnOffPositionConversion()
187       */
188      public void turnOnPositionConversion()
189      {
190        convertPositions = true;
191      }
192    
193      /**
194       * Sets this filter so that it will use the current time when querying
195       * sources sent to the {@link #allows(Source)} or
196       * {@link #blocks(Source)} methods.
197       */
198      public void clearQueryTime()
199      {
200        queryTime = CURRENT_TIME;
201      }
202    
203      //============================================================================
204      // SETTING THE FILTER CRITERIA
205      //============================================================================
206    
207      /**
208       * Sets a regular expression for matching the names of sources.
209       * Only sources whose names are matched by {@code regex} are allowed
210       * to pass through this filter.  If sources should not be filtered
211       * based on their names, call {@link #clearNamePattern()}.
212       * 
213       * @param regex a regular expression for matching the names of sources.
214             * @param includeAliases if true, sources with an alias matching {@code
215             * regex} will pass this criterion.
216       */
217      public void setNamePattern(String regex, boolean includeAliases)
218                    throws PatternSyntaxException
219      {
220                    this.includeAliases = includeAliases;
221        namePattern = (regex == null) ? ANY_NAME : Pattern.compile(regex);
222      }
223      
224            /**
225       * Sets a regular expression for matching the names of sources.
226       * Only sources whose names are matched by {@code regex} are allowed
227       * to pass through this filter.  If sources should not be filtered
228       * based on their names, call {@link #clearNamePattern()}.
229       * 
230       * @param regex a regular expression for matching the names of sources.
231             * @param includeAliases if true, sources with an alias matching {@code
232             * regex} will pass this criterion.
233       */
234      public void setNamePattern(Pattern regex, boolean includeAliases)
235      {
236                    this.includeAliases = includeAliases;
237        namePattern = (regex == null) ? ANY_NAME : regex;
238      }
239    
240      /**
241       * Sets this filter's position filter.
242       * The position filter is used by the {@code blocks} and {@code allows}
243       * methods.
244       * If sources should not be filtered based on their position,
245       * call {@link #clearPositionFilter()} or send a <i>null</i> to
246       * this method.
247       * 
248       * @param posFilter a filter to be used on the position of a source.
249       */
250      public void setPositionFilter(SkyPositionFilter posFilter)
251      {
252        positionFilter = posFilter;
253      }
254      
255      /**
256       * Sets the coordinate system and epoch to use if source positions are to
257       * be converted to a common system prior to filtering.
258       * 
259       * @param newSys
260       *          the new coordinate system.  If this value is <i>null</i>, an
261       *          <tt>IllegalArgumentException</tt> will be thrown.
262       * @param newEpoch
263       *          the new epoch.  If this value is <i>null</i>, it will be
264       *          treated as <tt>Epoch.UNKNOWN</tt>.
265       *          
266       * @see #turnOffPositionConversion()
267       */
268      public void setCoordSysAndEpoch(CelestialCoordinateSystem newSys,
269                                      Epoch                     newEpoch)
270      {
271        if (newSys == null)
272          throw new IllegalArgumentException("Cannot set NULL coord system.");
273        
274        coordSys = newSys;
275        epoch    = (newEpoch == null) ? Epoch.UNKNOWN : newEpoch;
276      }
277      
278      /**
279       * Adds the given filter to this filter.  Brightness filters are
280       * used by the {@code blocks} and {@code allows} methods.
281       * If sources should not be filtered based on their brightnesses,
282       * call {@link #clearBrightnessFilters()}.
283       * 
284       * @param sbFilter a filter to be used on the brightnesses of
285       *                 a source.
286       */
287      public void addBrightnessFilter(SourceBrightnessFilter sbFilter)
288      {
289        if (sbFilter != null)
290          brightnessFilters.add(sbFilter);
291      }
292      
293      /**
294       * Sets the date and time at which the filtered source is
295       * queried for its information.
296       * 
297       * @param time the time used by the {@link #allows(Source)}
298       *             and {@link #blocks(Source)} methods when
299       *             querying a source.
300       */
301      public void setQueryTime(Date time)
302      {
303        queryTime = (time == null) ? CURRENT_TIME : time;
304        //We don't set the query times of component filters here because they
305        //might be added after this call.  Instead we do just-in-time setting.
306      }
307    
308      //----------------------------------------------------------------------------
309      // User-Defined Values
310      //----------------------------------------------------------------------------
311      // We could consider making a MapFilter<String, String> class.
312      
313      /**
314       * Adds a user-defined key to this filter's set of required keys.
315       * In order for a source to pass through this filter, it must have a
316       * {@link Source#getUserDefinedValues() UDV} for {@code udKey}.
317       * The particular value it holds for that key, though, is immaterial.
318       * <p>
319       * This is a convenience method that is equivalent to both
320       * {@code addRequiredUserDefinedValues(udKey, null)} and
321       * {@code replaceRequiredUserDefinedValues(udKey, null)}.
322       * 
323       * @param udKey
324       *   the key for a user defined value that a source must have in order to
325       *   pass through this filter.  A value of <i>null</i> will be ignored.
326       * 
327       * @since 2008-07-28
328       */
329      public void addRequiredUserDefinedKey(String udKey)
330      {
331        addRequiredUserDefinedValues(udKey, ANY_UDV);
332      }
333      
334      /**
335       * Adds additional values to the set held by this filter for the given
336       * user defined key.
337       * In order for a source to pass through this filter, it must have a
338       * {@link Source#getUserDefinedValues() UDV} for {@code udKey}, and the
339       * value it has for that key must be in the set of values this filter
340       * holds for that key.
341       * <p>
342       * The effects of subsequent calls with the same key are cumulative.
343       * For example, imagine we have a block of (psuedo-)code like this:<pre>
344       * 
345       *   filter.addRequiredUserDefinedValues("stooge", ["Larry", "Moe", "Curly"]);
346       *   ...
347       *   filter.addRequiredUserDefinedValues("stooge", ["Shemp", "Curly Joe"]);
348       * </pre>
349       * After such a setup, <tt>filter</tt> will continue to allow sources with a
350       * UDV key of "stooge" and a value of "Curly" to pass, demonstrating that 
351       * "Shemp" and "Curly Joe" were added to the original values and did not
352       * replace them.</p>
353       * <p>
354       * If this filter has no entry for {@code udKey}, this method creates one
355       * and sets its values to {@code additionalUdvs}.</p>
356       * <p>
357       * This method will remove {@code udKey} from its set of
358       * {@link #addForbiddenUserDefinedKey(String) forbidden} keys, if present.</p>
359       * 
360       * @param udKey
361       *   the key for a user defined value that a source must have in order to
362       *   pass through this filter.  A value of <i>null</i> will be ignored.
363       *   
364       * @param additionalUdvs
365       *   a set of values for {@code udKey}.  In order for a source to pass through
366       *   this filter its UDV for {@code udKey} must be contained in this set
367       *   (see note above regarding consecutive calls to this method).
368       *   A special value of <i>null</i> may be used to indicate that the presence
369       *   of the key is required, but the value associated with that key is
370       *   immaterial.
371       *   
372       * @see #addForbiddenUserDefinedKey(String)
373       * @see #addRequiredUserDefinedKey(String)
374       * @see #replaceRequiredUserDefinedValues(String, Set)
375       * 
376       * @since 2008-07-28
377       */
378      public void addRequiredUserDefinedValues(String udKey, Set<String> additionalUdvs)
379      {
380        //Quick exit if key is null
381        if (udKey == null)
382          return;
383        
384        //Rem: null set of values is signal to blocksUdv that any value is OK
385        if (additionalUdvs == null)
386        {
387          requiredUdvs.put(udKey, additionalUdvs);
388        }
389        else //add new values to current
390        {
391          //New key, non-null set of values
392          if (!requiredUdvs.containsKey(udKey))
393          {
394            requiredUdvs.put(udKey, new HashSet<String>(additionalUdvs));
395          }
396          else //existing key, non-null set of values
397          {
398            Set<String> currValues = requiredUdvs.get(udKey);
399            
400            //If key is assoc w/ null, then it already represents all values.
401            //Add new values only to non-null set.
402            if (currValues != null)
403              currValues.addAll(additionalUdvs);
404          }
405        }
406        
407        //If we're requiring a UDV key, we cannot also forbid it
408        forbiddenUdvKeys.remove(udKey);
409      }
410    
411      /**
412       * Replaces the values held by this filter for the given user defined key.
413       * In order for a source to pass through this filter, it must have a
414       * {@link Source#getUserDefinedValues() UDV} for {@code udKey}, and the
415       * value it has for that key must be in the {@code replacementUdvs} set.
416       * <p>
417       * Contrast the behavior of this method with
418       * {@link #addRequiredUserDefinedValues(String, Set)}:<pre>
419       * 
420       *   filter.addRequiredUserDefinedValues("stooge", ["Larry", "Moe", "Curly"]);
421       *   ...
422       *   filter.addRequiredUserDefinedValues("stooge", ["Larry", "Moe", "Shemp"]);
423       * </pre>
424       * After the second call, sources whose "stooge" is "Curly" will no longer
425       * pass through this filter.
426       * <p>
427       * If this filter has no entry for {@code udKey}, this method creates one
428       * and sets its values to {@code replacementUdvs}.</p>
429       * <p>
430       * This method will remove {@code udKey} from its set of
431       * {@link #addForbiddenUserDefinedKey(String) forbidden} keys, if present.</p>
432       * 
433       * @param udKey
434       *   the key for a user defined value that a source must have in order to
435       *   pass through this filter.  A value of <i>null</i> will be ignored.
436       *   
437       * @param replacementUdvs
438       *   a set of values for {@code udKey}.  In order for a source to pass through
439       *   this filter its UDV for {@code udKey} must be contained in this set.
440       *   A special value of <i>null</i> may be used to indicate that the presence
441       *   of the key is required, but the value associated with that key is
442       *   immaterial.
443       *   
444       * @see #addForbiddenUserDefinedKey(String)
445       * @see #addRequiredUserDefinedKey(String)
446       * @see #addRequiredUserDefinedValues(String, Set)
447       * 
448       * @since 2008-07-28
449       */
450      public void replaceRequiredUserDefinedValues(String udKey, Set<String> replacementUdvs)
451      {
452        if (udKey != null)
453        {
454          requiredUdvs.remove(udKey);
455          addRequiredUserDefinedValues(udKey, replacementUdvs);
456        }
457      }
458      
459      /**
460       * Removes {@code udKey} from this filter's collection of 
461       * {@link Source#getUserDefinedValues() UDV} keys that passing sources
462       * must possess.
463       * 
464       * @param udKey
465       *   a key for a {@link Source#getUserDefinedValues() user-defined value}
466       *   (UDV) of a source.
467       * 
468       * @see #addRequiredUserDefinedKey(String)
469       * @see #addRequiredUserDefinedValues(String, Set)
470       * @see #clearUserDefinedValues()
471       * @see #replaceRequiredUserDefinedValues(String, Set)
472       * 
473       * @since 2008-07-28
474       */
475      public void removeRequiredUserDefinedValue(String udKey)
476      {
477        if (udKey != null)
478        {
479          requiredUdvs.remove(udKey);
480        }
481      }
482      
483      /**
484       * Adds a user-defined key to this filter's set of forbidden keys.
485       * In order for a source to pass through this filter, it must <i>not</i>
486       * have a {@link Source#getUserDefinedValues() UDV} for {@code udKey}.
487       * Any source holding such a key, no matter the value associated with
488       * that key, will be blocked by this filter.
489       * <p>
490       * This method will remove {@code udKey} from its set of
491       * {@link #addRequiredUserDefinedKey(String) required} keys, if present.</p>
492       * 
493       * @param udKey
494       *   a key for a {@link Source#getUserDefinedValues() user-defined value}
495       *   (UDV) of a source.  A value of <i>null</i> will be ignored.
496       * 
497       * @see #addRequiredUserDefinedKey(String)
498       * @see #clearUserDefinedValues()
499       * @see #removeForbiddenUserDefinedKey(String)
500       * 
501       * @since 2008-07-28
502       */
503      public void addForbiddenUserDefinedKey(String udKey)
504      {
505        if (udKey != null)
506        {
507          forbiddenUdvKeys.add(udKey);
508        
509          //If we're forbidding a UDV key, we cannot also require it
510          requiredUdvs.remove(udKey);
511        }
512      }
513      
514      /**
515       * Removes {@code udKey} from this filter's collection of 
516       * {@link Source#getUserDefinedValues() UDV} keys that passing sources
517       * must <i>not</i> possess.
518       * 
519       * @param udKey
520       *   a key for a {@link Source#getUserDefinedValues() user-defined value}
521       *   (UDV) of a source.  A value of <i>null</i> will be ignored.
522       * 
523       * @see #clearUserDefinedValues()
524       * @see #addForbiddenUserDefinedKey(String)
525       * 
526       * @since 2008-07-28
527       */
528      public void removeForbiddenUserDefinedKey(String udKey)
529      {
530        if (udKey != null)
531        {
532          forbiddenUdvKeys.remove(udKey);
533        }
534      }
535      
536      //============================================================================
537      // APPLYING THIS FILTER
538      //============================================================================
539    
540      /**
541       * Returns <i>true</i> if this filter blocks the given source.
542       * <i>Null</i> sources are always blocked.
543       * <p>
544       * The logic for allowing or blocking a source is somewhat complex.
545       * Some of the details about which clients should be aware are:
546       * <ol>
547       *   <li>Even though an {@code SphericalPositionFilter} and a
548       *       {@code SourceBrightnessFilter} each carries its own
549       *       query time, this filter will ensure that its component
550       *       filters are using the same time as that specified directly
551       *       to this filter.</li>
552       *       <br/>
553       *   <li>Position and brightness information is held at the subsource
554       *       level, and a source may have multiple subsources.  The only
555       *       subsource examined during filtering is the
556       *       {@link Source#getCentralSubsource() central subsource}.</li>
557       *       <br/>
558       *   <li>A subsource may have multiple brightnesses.  This filter may
559       *       have several brightness filters.  The source will be blocked
560       *       only if at least one filter blocks all brightnesses.</li>
561       *       <br/>
562       *   <li>If this filter has a position filter and the central subsource
563       *       has no position information, the source will be blocked.</li>
564       *       <br/>
565       *   <li>If this filter has one or more brightness filters and the central
566       *       subsource has no brightness information, the source will be
567       *       blocked.</li>
568       *       <br/>
569       *   <li>If position filtering occurs and position conversion is requested,
570       *       the filtering will not stop on account of a conversion exception.
571       *       Instead, the filter will work with the unconverted position and
572       *       log the conversion failure.</li>
573       * </ol></p>
574       * 
575       * @param src the source to be filtered.
576       * 
577       * @return <i>true</i> if this filter blocks {@code src}.
578       */
579      public boolean blocks(Source src)
580      {
581        //Filter blocks all null sources
582        if (src == null)
583          return true;
584    
585        //Filter blocks all sources that lack a central subsource
586        Subsource sub = src.getCentralSubsource();
587        if (sub == null)
588          return true;
589    
590        //Source name & alias
591        if (blocksName(src))
592          return true;
593        
594        //User-defined values
595        if (blocksUdv(src))
596          return true;
597    
598        //Position of central subsource
599        if (blocksPosition(src, sub))
600          return true;
601    
602        //Brightnesses of central subsource
603        if (blocksBrightness(sub))
604          return true;
605    
606        //Source was not blocked
607        return false;
608      }
609    
610      /**
611       * Helps main blocks(Source) method.
612       */
613      private boolean blocksName(Source src)
614      {
615        boolean filterBlocksName = false;
616        
617        //Block src if its name doesn't match the pattern (unless we're not
618        //filtering on names)
619        if (namePattern != ANY_NAME)
620        {
621          boolean matchesOne = namePattern.matcher(src.getName()).matches();
622    
623          //If the name didn't match, check the aliases if appropriate
624          if (this.includeAliases && !matchesOne)
625          {
626            for (String name : src.getAliases())
627            {
628              if (namePattern.matcher(name).matches())
629              {
630                matchesOne = true;
631                break;
632              }
633            }
634    
635            //If none of the aliases matched either, then block the src. 
636            if (!matchesOne)
637              filterBlocksName = true;
638          }
639    
640          //If the name didn't match and we're not checking aliases, then we block
641          //this src.
642          else if (!matchesOne)
643            filterBlocksName = true;
644        }
645        
646        return filterBlocksName;
647      }
648    
649      /**
650       * Helps main blocks(Source) method.
651       */
652      private boolean blocksUdv(Source src)
653      {
654        Map<String, String> udvs = src.getUserDefinedValues();
655        return hasForbiddenUdvs(udvs) || isMissingRequiredUdvs(udvs);
656      }
657      
658      private boolean hasForbiddenUdvs(Map<String, String> udvs)
659      {
660        for (String udKey : udvs.keySet())
661          if (forbiddenUdvKeys.contains(udKey))
662            return true;
663        
664        return false;  //source has no forbidden value
665      }
666      
667      private boolean isMissingRequiredUdvs(Map<String, String> udvs)
668      {
669        Set<String> udKeys = udvs.keySet();
670        
671        //Returns true if source is missing any required key or does not
672        //have a req'd value for any req'd key.
673        for (String reqdKey : requiredUdvs.keySet())
674        {
675          if (!udKeys.contains(reqdKey))
676          {
677            return true; //source is missing a required key
678          }
679          else //source has the key, does it have the req'd value?
680          {
681            Set<String> reqdValues = requiredUdvs.get(reqdKey);
682            
683            //Rem: null is signal that ALL values are OK
684            if (reqdValues != null)
685            {
686              String udv = udvs.get(reqdKey);
687              
688              if (!reqdValues.contains(udv))
689                return true; //source has key, but not one of the req'd values
690            }
691          }
692        }
693        
694        return false;  //source is not missing req'd key/value
695      }
696    
697      /**
698       * Helps main blocks(Source) method.
699       */
700      private boolean blocksPosition(Source src, Subsource sub)
701      {
702        boolean filterBlocksPosition = false;
703        
704        //Block src if the position filter blocks the central subsource's position
705        if (positionFilter != null)
706        {
707          SkyPosition srcPos = sub.getPosition();
708          
709          //Set up the query time.  For positions a time is required because
710          //position is a function of time.  The time is used for calculation,
711          //as opposed to filtering.
712          Date qt = (queryTime == CURRENT_TIME) ? new Date() : queryTime;
713    
714          if (convertPositions)
715          {
716            try
717            {
718              //TODO need to allow user to specify location
719              LocalSiderealTime lst = new LocalSiderealTime(qt);
720              EarthPosition observer = TelescopeType.EVLA.getLocation();
721              srcPos = srcPos.toPosition(coordSys, epoch, observer, lst);
722            }
723            catch (CoordinateConversionException ex)
724            {
725              //Run w/ uncoverted position, but log failure
726              log.warn("Failed to convert position of source " + src.getName() +
727                       " to " + coordSys + ", " + epoch, ex);
728            }
729          }
730          
731          positionFilter.setQueryTime(qt);
732          if (positionFilter.blocks(sub.getPosition()))
733            filterBlocksPosition = true;
734        }
735        
736        return filterBlocksPosition;
737      }
738    
739      /**
740       * Helps main blocks(Source) method.
741       */
742      private boolean blocksBrightness(Subsource sub)
743      {
744        //This logic is a little different than what is normally found in the
745        //'blocks' method of a filter.  This filter may have multiple brightness
746        //filters, and the central subsource may have multiple brightnesses.
747        //In order for a brightness filter to block the central subsource,
748        //it must block ALL of the brightnesses.  The viewpoint to adopt is
749        //that of the client who asks: "Let this source pass if it has at least
750        //one brightness whose flux density for polarization P is in the range
751        //FD1-FD2."
752        //Note, though, if any single brightness filter blocks all brightnesses,
753        //then the source is blocked.
754        //If this filter has brightness filters, and if the central subsource has
755        //no brightnesses, this filter will block the source.
756        
757        boolean filterBlocksAll = false;  //If no b-filters, no blocking
758        
759        //Set up the query time.  For brightness the queryTime is truly used for
760        //filtering (unlike the case with positions).
761        boolean setTheTime = (queryTime != CURRENT_TIME);
762    
763        for (SourceBrightnessFilter sbf : brightnessFilters)
764        {
765          filterBlocksAll = true;
766    
767          if (setTheTime)
768            sbf.setTime(queryTime);
769          else
770            sbf.clearTime();
771          
772          for (SourceBrightness brightness : sub.getBrightnesses())
773          {
774            if (sbf.allows(brightness))
775            {
776              filterBlocksAll = false;
777              break; //Don't need to check w/ this filter any longer
778            }
779          }
780          
781          //Either there were no brightnesses or the current filter blocked
782          //all brightnesses.
783          if (filterBlocksAll)
784            break;
785        }
786        
787        return filterBlocksAll;
788      }
789      
790      /**
791       * Returns <i>true</i> if this filter allows the given source
792       * to pass through it.
793       * <i>Null</i> sources are always blocked.
794       * <p>
795       * The logic for allowing or blocking a source is somewhat complex.
796       * See {@link #blocks(Source)} for details.</p>
797       * 
798       * @param src the source to be filtered.
799       * 
800       * @return <i>true</i> if this filter allows {@code src} to pass through it.
801       */
802      public boolean allows(Source src)
803      {
804        return !blocks(src);
805      }
806      
807      /**
808       * Selects those objects in {@code bag} that are sources and that can pass
809       * through this filter.  The selections are added to a new collection and
810       * returned.  If the bag holds no such objects, the returned collection
811       * will be empty.
812       * <p>
813       * The original collection ({@code bag}) is not altered.</p>
814       * 
815       * @param bag a collection of objects.
816       * 
817       * @return a collection of sources from {@code bag} that were able to
818       *         pass through this filter.
819       */
820      public Collection<Source> selectFrom(Collection<?> bag)
821      {
822        Collection<Source> selection = new ArrayList<Source>();
823        
824        for (Object candidate : bag)
825        {
826          if (candidate instanceof Source)
827          {
828            Source source = (Source)candidate;
829            
830            if (this.allows(source))
831              selection.add(source);
832          }
833        }
834        
835        return selection;
836      }
837    }