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 }