001 package edu.nrao.sss.model.resource; 002 003 import java.util.HashSet; 004 import java.util.Set; 005 import javax.xml.bind.annotation.XmlType; 006 import javax.xml.bind.annotation.XmlElement; 007 import javax.xml.bind.annotation.XmlTransient; 008 009 /** 010 * A selection of antennas. 011 * <p> 012 * A selection of antennas can be more than just a collection of 013 * antennas. It can be used to make requests such as: 014 * "give me at least 5 antennas, preferrably those that result 015 * in the longest possible baselines".</p> 016 * <p> 017 * <b>Version Info:</b> 018 * <table style="margin-left:2em"> 019 * <tr><td>$Revision: 1710 $</td></tr> 020 * <tr><td>$Date: 2008-11-14 11:54:07 -0700 (Fri, 14 Nov 2008) $</td></tr> 021 * <tr><td>$Author: dharland $</td></tr> 022 * </table></p> 023 * 024 * @author David M. Harland 025 * @since 2007-03-07 026 */ 027 @XmlType( 028 propOrder={ 029 "idType", "method", "minimumNumber", 030 "persistentUserPicks", "requiredReceiver" 031 } 032 ) 033 public class AntennaSelection implements Cloneable 034 { 035 //private static int nextId = 1; 036 037 //private final int debugId = nextId++; 038 039 //private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(AntennaSelection.class); 040 041 private static final AntennaSelectionMethod DEFAULT_METHOD = 042 AntennaSelectionMethod.ALL; 043 044 private static final AntennaSpecifier DEFAULT_ID_TYPE = 045 AntennaSpecifier.ANTENNA_ID; 046 047 //User input 048 private AntennaSelectionMethod method; 049 private AntennaSpecifier idType; 050 private AntennaProvider provider; 051 private int minimumNumber; 052 private Set<String> userPicks; 053 private ReceiverBand receiverBand; 054 055 //Not user modifiable & not persisted 056 private boolean antennaSetIsStale; 057 private Set<String> antennas; 058 059 060 /** Creates a new instance using antennaProvider as the source of available 061 * antennas to select from. */ 062 public AntennaSelection(AntennaProvider antennaProvider) 063 { 064 this.provider = (antennaProvider != null)? antennaProvider : new DefaultAntennaProvider(); 065 066 this.userPicks = new HashSet<String>(); 067 068 this.antennas = new HashSet<String>(); 069 070 init(); 071 } 072 073 /** Creates a new instance using a default AntennaProvider that returns an 074 * empty set of Antennas. */ 075 public AntennaSelection() 076 { 077 this(null); 078 } 079 080 private static class DefaultAntennaProvider implements AntennaProvider 081 { 082 public Set<Antenna> getAvailableAntennas() { return new HashSet<Antenna>(); } 083 } 084 085 /** Used by constructor and reset(). */ 086 private void init() 087 { 088 method = DEFAULT_METHOD; 089 idType = DEFAULT_ID_TYPE; 090 minimumNumber = 0; 091 antennaSetIsStale = true; 092 receiverBand = null; 093 } 094 095 /** 096 * Puts this selection back in its initial state. 097 */ 098 public void reset() 099 { 100 init(); 101 102 userPicks.clear(); 103 antennas.clear(); 104 } 105 106 //============================================================================ 107 // 108 //============================================================================ 109 110 /** 111 * Sets the manner in which antennas will be selected. 112 * <p> 113 * Depending on the value of {@code newMethod}, previously set properties 114 * may be cleared.</p> 115 * 116 * @param newMethod the manner in which antennas will be selected. 117 * If this value is <i>null</i>, the default method 118 * (see {@link #getMethod()}) will be used. 119 */ 120 public void setMethod(AntennaSelectionMethod newMethod) 121 { 122 if (!method.equals(newMethod)) 123 { 124 method = (newMethod == null) ? DEFAULT_METHOD : newMethod; 125 126 if (!method.requiresMinimumNumberSpecification()) 127 minimumNumber = 0; 128 129 if (!method.requiresManualIdentification()) 130 { 131 userPicks.clear(); 132 } 133 134 antennaSetIsStale = true; 135 } 136 } 137 138 /** 139 * Returns the manner in which antennas will be selected. 140 * The default value for this property is 141 * {@code AntennaSelectionMethod.ALL}. 142 * 143 * @return the manner in which antennas will be selected. 144 */ 145 @XmlElement public AntennaSelectionMethod getMethod() 146 { 147 return method; 148 } 149 150 /** 151 * Set the type of identifiers returned by {@link #getMatchingAntennas()}. 152 * 153 * @param newType the type of identifiers returned by {@link #getMatchingAntennas()}. 154 * If this value is <i>null</i>, the default type 155 * (see {@link #getIdType()}) will be used. 156 */ 157 public void setIdType(AntennaSpecifier newType) 158 { 159 if (!idType.equals(newType)) 160 { 161 idType = (newType == null) ? DEFAULT_ID_TYPE : newType; 162 163 antennaSetIsStale = true; 164 } 165 } 166 167 /** 168 * Returns the type of identifiers returned by {@link #getMatchingAntennas()}. 169 * The default value for this property is 170 * {@code AntennaSpecifier.ANTENNA_ID}. 171 * <p> 172 * <b>Caveat:</b> If this selection holds a collection of user-specified 173 * antennas, there is no guarantee that the user-specified IDs are of the 174 * type returned here.</p> 175 * 176 * @return the type of identifiers returned by {@link #getMatchingAntennas()}. 177 */ 178 @XmlElement public AntennaSpecifier getIdType() 179 { 180 return idType; 181 } 182 183 /** 184 * Sets the minimum number of antennas to select and the selection method to 185 * use. 186 * 187 * @param newMin the minimum number of antennas to select. 188 * @param selectionMethod the manner in which antennas will be selected. 189 * 190 * @throws IllegalArgumentException if {@code newMin} is less than one or if 191 * {@code selectionMethod} does not require specification of a 192 * minimum number of antennas. 193 */ 194 public void setMinimumNumber(int newMin, 195 AntennaSelectionMethod selectionMethod) 196 { 197 if (newMin < 1) 198 throw new IllegalArgumentException( 199 "The lowest allowed value for the minimum number of antennas is one."); 200 201 if (!selectionMethod.requiresMinimumNumberSpecification()) 202 throw new IllegalArgumentException("Selection method " + selectionMethod + 203 " does not require specification of a minimum # of antennas."); 204 205 setMethod(selectionMethod); 206 207 minimumNumber = newMin; 208 209 antennaSetIsStale = true; 210 } 211 212 /** 213 * Returns the minimum number of antennas to select. 214 * 215 * @return the minimum number of antennas to select. 216 */ 217 @XmlElement public int getMinimumNumber() 218 { 219 //TODO: Count actual selections if user-list? 220 // Return # of antennas if ALL? 221 222 return minimumNumber; 223 } 224 225 /** 226 * Adds the given antenna ID to this selection. 227 * The antenna selection method is also set to 228 * {@link AntennaSelectionMethod#LIST}. 229 * 230 * @param antennaId the ID of a manually selected antenna. 231 */ 232 public void addUserPick(String antennaId) 233 { 234 setMethod(AntennaSelectionMethod.LIST); 235 236 userPicks.add(antennaId); 237 238 antennaSetIsStale = true; 239 } 240 241 /** 242 * Removes the given antenna ID from this selection. 243 * If the current selection method is not 244 * {@link AntennaSelectionMethod#LIST}, this method does nothing. 245 * 246 * @param antennaId the ID of antenna to remove from the set of 247 * manually selected antennas. 248 */ 249 public void removeUserPick(String antennaId) 250 { 251 if (getMethod().equals(AntennaSelectionMethod.LIST)) 252 { 253 userPicks.remove(antennaId); 254 255 antennaSetIsStale = true; 256 } 257 } 258 259 /** 260 * Sets the AntennaProvider instance used to find the complete set of 261 * available Antennas. If {@code p} is null, then the provider is set to an 262 * instance of DefaultAntennaProvider. In either case the selection returned 263 * by getMatchingAntennas() is recalculated. 264 */ 265 public void setAntennaProvider(AntennaProvider p) 266 { 267 this.provider = (p != null)? p : new DefaultAntennaProvider(); 268 this.antennaSetIsStale = true; 269 } 270 271 /** 272 * @return the AntennaProvider instance used to find the complete set of 273 * available Antennas. 274 */ 275 @XmlTransient public AntennaProvider getAntennaProvider() 276 { 277 return this.provider; 278 } 279 280 /** 281 * Sets a receiver band that must be supported on all returned Antennas. 282 * Setting this to null implies that there is no restriction on supported 283 * receiver bands. 284 */ 285 public void setRequiredReceiver(ReceiverBand b) 286 { 287 this.receiverBand = b; 288 } 289 290 /** 291 * Returns the receiver band that must be supported on all returned Antennas. 292 * If this returns null, that implies that there is no restriction on supported 293 * receiver bands. 294 */ 295 @XmlElement public ReceiverBand getRequiredReceiver() 296 { 297 return this.receiverBand; 298 } 299 300 //============================================================================ 301 // 302 //============================================================================ 303 304 /** 305 * Returns a collection of antenna identifiers represented by this selection. 306 * Note that this method will only recalculate the matching antennas if 1 or 307 * more of the properties of this object have changed! 308 * <p> 309 * The returned set is <i>not</i> held internally by this selection, so any 310 * changes made to it will not be reflected in this object. 311 * The returned set is guaranteed to be non-null, but it may be empty.</p> 312 * 313 * @return a collection of antenna identifiers represented by this selection. 314 * @see #recalculateMatchingAntennas 315 */ 316 @XmlTransient public Set<String> getMatchingAntennas() 317 { 318 if (antennaSetIsStale) 319 recalculateMatchingAntennas(); 320 321 return new HashSet<String>(antennas); 322 } 323 324 /** 325 * Selects antennas based on the other properties of this object. Calling 326 * this method will recalculate the matching antennas even if none of the 327 * properties of this object have changed. In contrast, 328 * getMatchingAntennas() will always return the same set, with no 329 * recalculation, if none of the properties have changed. 330 * 331 * <p>TODO: What do we do if the AntennaProvider can not meet the 332 * requirements laid out in this object?</p> 333 */ 334 public void recalculateMatchingAntennas() 335 { 336 this.antennas.clear(); 337 338 switch(this.method) 339 { 340 case LIST: 341 this.antennas.addAll(this.userPicks); 342 break; 343 344 case MINIMUM_NUMBER_SHORTEST_BASELINES: 345 break; 346 347 case MINIMUM_NUMBER_LONGEST_BASELINES: 348 break; 349 350 case MINIMUM_NUMBER_EVEN_DISTRIBUTION: 351 case MINIMUM_NUMBER: 352 break; 353 354 case ALL: 355 default: 356 for (Antenna a : this.provider.getAvailableAntennas()) 357 { 358 this.antennas.add((getIdType().equals(AntennaSpecifier.ANTENNA_ID))? a.getId() : a.getPadId()); 359 } 360 } 361 362 this.antennaSetIsStale = false; 363 } 364 365 //============================================================================ 366 // PERSISTENCE HELPERS 367 //============================================================================ 368 369 @SuppressWarnings("unused") 370 @XmlElement private String getPersistentUserPicks() 371 { 372 StringBuilder buff = new StringBuilder(); 373 374 for (String id : userPicks) 375 buff.append(id).append(';'); 376 377 return buff.toString(); 378 } 379 380 381 @SuppressWarnings("unused") 382 private void setPersistentUserPicks(String picks) 383 { 384 boolean picksAdded = false; 385 if (picks != null) 386 { 387 for (String id : picks.split(";")) 388 { 389 if (id.length() > 0) 390 { 391 userPicks.add(id); 392 this.antennaSetIsStale = true; 393 picksAdded = true; 394 } 395 } 396 397 if (picksAdded) 398 setMethod(AntennaSelectionMethod.LIST); 399 } 400 } 401 402 //============================================================================ 403 // 404 //============================================================================ 405 406 /** 407 * Returns a selection that is a copy of this one. 408 * <p> 409 * If anything goes wrong during the cloning procedure, 410 * a {@code RuntimeException} will be thrown.</p> 411 */ 412 @Override 413 public AntennaSelection clone() 414 { 415 AntennaSelection clone = null; 416 417 try 418 { 419 //This line takes care of the primitive & immutable fields properly 420 clone = (AntennaSelection)super.clone(); 421 422 //Clone the sets, but not the immutable elements 423 clone.userPicks = new HashSet<String>(this.userPicks); 424 clone.antennas = new HashSet<String>(this.antennas); 425 } 426 catch (Exception ex) 427 { 428 throw new RuntimeException(ex); 429 } 430 431 return clone; 432 } 433 434 /** Returns <i>true</i> if {@code o} is equal to this antenna selection. */ 435 @Override 436 public boolean equals(Object o) 437 { 438 //Quick exit if o is null 439 if (o == null) 440 return false; 441 442 //Quick exit if o is this 443 if (o == this) 444 return true; 445 446 //Quick exit if classes are different 447 if (!o.getClass().equals(this.getClass())) 448 return false; 449 450 AntennaSelection other = (AntennaSelection)o; 451 452 //TODO Right now we're comparing only the user input. 453 // Once we know how we're going to programmatically select 454 // antennas, we might want to look at either 455 // A) the new input fields 456 // B) the derived antenna selection 457 458 459 return other.method.equals(this.method) && 460 other.idType.equals(this.idType) && 461 other.minimumNumber == this.minimumNumber && 462 other.userPicks.equals(this.userPicks); 463 464 //Note the comparison of the userPicks Set is OK because 465 //the elements are immutable. 466 } 467 468 /** Returns a hash code value for this antenna selection. */ 469 @Override 470 public int hashCode() 471 { 472 //You MUST keep this method in sync w/ the equals method 473 474 //Taken from the Effective Java book by Joshua Bloch. 475 //The constants 17 & 37 are arbitrary & carry no meaning. 476 int result = 17; 477 478 result = 37 * result + method.hashCode(); 479 result = 37 * result + idType.hashCode(); 480 result = 37 * result + minimumNumber; 481 result = 37 * result + userPicks.hashCode(); 482 483 return result; 484 } 485 }