001 package edu.nrao.sss.model.project.scan; 002 003 import java.math.BigDecimal; 004 import java.math.RoundingMode; 005 import java.util.ArrayList; 006 import java.util.Collections; 007 import java.util.List; 008 009 import javax.xml.bind.annotation.XmlElement; 010 import javax.xml.bind.annotation.XmlElementWrapper; 011 import javax.xml.bind.annotation.XmlRootElement; 012 013 import edu.nrao.sss.measure.Distance; 014 import edu.nrao.sss.measure.DistanceUnits; 015 import edu.nrao.sss.measure.TimeDuration; 016 017 /** 018 * A scan that holds a list of {@link FocusOffset focus offsets}. 019 * <p> 020 * <b>Version Info:</b> 021 * <table style="margin-left:2em"> 022 * <tr><td>$Revision: 1314 $</td></tr> 023 * <tr><td>$Date: 2008-05-30 11:31:14 -0600 (Fri, 30 May 2008) $</td></tr> 024 * <tr><td>$Author: dharland $</td></tr> 025 * </table></p> 026 * 027 * @author David M. Harland 028 * @since 2006-07-10 029 */ 030 @XmlRootElement 031 public class FocusScan 032 extends Scan 033 { 034 private List<FocusOffset> offsets; 035 036 /** Creates a new instance. */ 037 FocusScan() 038 { 039 super(); 040 041 offsets = new ArrayList<FocusOffset>(); 042 } 043 044 /** 045 * Sets the list of focus offsets held by this scan. 046 * A <i>null</i> {@code replacementList} will be interpreted 047 * as a new empty list. 048 * <p> 049 * This scan will hold a reference to {@code replacementList} 050 * (unless it is <i>null</i>), so any changes made to the list 051 * after calling this method will be reflected in this object.</p> 052 * 053 * @param replacementList a list of focus offsets to be held by this 054 * scan. 055 */ 056 public void setOffsets(List<FocusOffset> replacementList) 057 { 058 offsets = (replacementList == null) ? new ArrayList<FocusOffset>() 059 : replacementList; 060 } 061 062 /** 063 * Returns the list of focus offsets held by this scan. 064 * The returned list is guaranteed to be non-null, but may 065 * be empty. 066 * <p> 067 * The returned list is the actual list held by this scan, 068 * so changes made to the list will be reflected in this 069 * object.</p> 070 * 071 * @return the list of focus offsets held by this scan. 072 */ 073 @XmlElementWrapper 074 @XmlElement(name="focusOffset") 075 public List<FocusOffset> getOffsets() 076 { 077 return offsets; 078 } 079 080 /** 081 * Sorts the internal list of offsets and returns it. 082 * <p> 083 * The returned list is the actual list held by this scan, 084 * so changes made to the list will be reflected in this 085 * object.</p> 086 * 087 * @return the list of focus offsets held by this scan after, they have 088 * been sorted. 089 */ 090 public List<FocusOffset> sortOffsets() 091 { 092 Collections.sort(offsets); 093 return offsets; 094 } 095 096 /** 097 * Adds a series of offsets to this scan's list. 098 * The number of new offsets added is equal to 099 * <tt>1 + 2 * numberOfPositiveOffsets</tt>. 100 * If we call the <tt>numberOfPositiveOffsets</tt> "<tt>n</tt>" 101 * and the <tt>incrementalOffset</tt> "<tt>d</tt>", offsets 102 * of the following lengths will be added to this scan: 103 * <p> 104 * <tt> -nd,...,-2d,-d,0,+d,+2d,...,+nd</tt>.</p> 105 * 106 * @param numberOfPositiveOffsets 107 * the number of positive offsets to be added. The total number of offsets 108 * added is equal to one plus two times this amount. An {@code 109 * IllegalArgumentException} will be thrown if this value is negative. 110 * 111 * @param incrementalOffset 112 * the spacing between the offsets added by this method. This value is 113 * typically in millimeters. An {@code IllegalArgumentException} will be 114 * thrown if this value less than or equal to zero. 115 * 116 * @param timeAtOffset 117 * the amount of time the focus should be placed at this offset. 118 * 119 * @throws IllegalArgumentException 120 * if any of the parameters violate the rules listed above. 121 */ 122 public void addOffsets(int numberOfPositiveOffsets, 123 Distance incrementalOffset, 124 TimeDuration timeAtOffset) 125 { 126 validateParameters(numberOfPositiveOffsets,incrementalOffset,timeAtOffset); 127 128 DistanceUnits units = incrementalOffset.getUnits(); 129 BigDecimal increment = incrementalOffset.getValue(); 130 BigDecimal length = increment.multiply(new BigDecimal(-numberOfPositiveOffsets)); 131 132 int offsetCount = 1 + 2 * numberOfPositiveOffsets; 133 134 for (int i=1; i <= offsetCount; i++) 135 { 136 FocusOffset offset = new FocusOffset(); 137 138 offset.setOffsetLength(new Distance(length, units)); 139 offset.getTimeAtOffset().set(timeAtOffset); 140 141 offsets.add(offset); 142 143 length = length.add(increment); 144 } 145 } 146 147 /** 148 * Adds a series of offsets to this scan's list. 149 * The number of new offsets added is {@code numberOfOffsets}. 150 * The smallest new offset is {@code minimumOffset}, the largest is 151 * {@code maximumOffset}. The other offsets are spread evenly between 152 * these two endpoints. 153 * 154 * @param minimumOffset 155 * the smallest focus offset. This value is often negative. 156 * 157 * @param maximumOffset 158 * the largest focus offset. An {@code IllegalArgumentException} will be 159 * thrown if this value is less than or equal to the {@code minimumOffset}. 160 * 161 * @param numberOfOffsets 162 * the number of new offsets to be added. 163 * Special cases:<ul> 164 * <li>numberOfOffsets < 1: No new offsets will be added.</li> 165 * <li>numberOfOffsets == 1: Only the {@code minimumOffset} 166 * will be added.</li> 167 * <li>numberOfOffsets == 2: Only the {@code minimumOffset} 168 * and {@code maximumOffset} 169 * will be added.</li> 170 * </ul> 171 * 172 * @param timeAtOffset 173 * the amount of time the focus should be placed at this offset. 174 * 175 * @throws IllegalArgumentException 176 * if any of the parameters violate the rules listed above. 177 */ 178 public void addOffsets(Distance minimumOffset, Distance maximumOffset, 179 int numberOfOffsets, TimeDuration timeAtOffset) 180 { 181 validateParameters(minimumOffset, maximumOffset, 182 numberOfOffsets, timeAtOffset); 183 184 //Special cases 185 if (numberOfOffsets < 1) //No action 186 { 187 return; 188 } 189 else if (numberOfOffsets <= 2) //Add only min (& possibly max) offset 190 { 191 FocusOffset lowestOffset = new FocusOffset(); 192 lowestOffset.setOffsetLength(minimumOffset); 193 lowestOffset.setTimeAtOffset(timeAtOffset); 194 offsets.add(lowestOffset); 195 196 if (numberOfOffsets == 2) //Add only min & max offsets 197 { 198 FocusOffset highestOffset = new FocusOffset(); 199 highestOffset.setOffsetLength(maximumOffset); 200 highestOffset.setTimeAtOffset(timeAtOffset); 201 offsets.add(highestOffset); 202 } 203 } 204 //Normal logic 205 else 206 { 207 DistanceUnits units = maximumOffset.getUnits(); 208 209 BigDecimal length = minimumOffset.toUnits(units); 210 211 BigDecimal increment = 212 maximumOffset.getValue().subtract(length) 213 .divide(new BigDecimal(numberOfOffsets - 1), 214 RoundingMode.HALF_UP); 215 for (int i=1; i <= numberOfOffsets; i++) 216 { 217 FocusOffset offset = new FocusOffset(); 218 219 offset.getTimeAtOffset().set(timeAtOffset); 220 offset.setOffsetLength(new Distance(length, units)); 221 222 offsets.add(offset); 223 224 length = length.add(increment); 225 } 226 } 227 } 228 229 /** Helps addOffsets(...). */ 230 private void validateParameters(int numberOfPositiveOffsets, 231 Distance incrementalOffset, 232 TimeDuration timeAtOffset) 233 { 234 final String errMsgStart = "Illegal value for "; 235 236 if (numberOfPositiveOffsets < 0) 237 { 238 StringBuilder errMsg = new StringBuilder(errMsgStart); 239 errMsg.append("numberOfPositiveOffsets (") 240 .append(numberOfPositiveOffsets) 241 .append("): value may not be negative."); 242 throw new IllegalArgumentException(errMsg.toString()); 243 } 244 245 if (incrementalOffset.getValue().signum() <= 0) 246 { 247 StringBuilder errMsg = new StringBuilder(errMsgStart); 248 errMsg.append("incrementalOffset (") 249 .append(incrementalOffset) 250 .append("): value must be positive."); 251 throw new IllegalArgumentException(errMsg.toString()); 252 } 253 } 254 255 /** Helps addOffsets(...). */ 256 private void validateParameters(Distance minimumOffset, 257 Distance maximumOffset, 258 int numberOfOffsets, 259 TimeDuration timeAtOffset) 260 { 261 if (minimumOffset.compareTo(maximumOffset) >= 0) 262 { 263 StringBuilder errMsg = new StringBuilder("The minimum offset ("); 264 errMsg.append(minimumOffset.toString()) 265 .append(") must be less than the maximum offset (") 266 .append(maximumOffset.toString()).append(")."); 267 throw new IllegalArgumentException(errMsg.toString()); 268 } 269 } 270 271 //============================================================================ 272 // TEXT 273 //============================================================================ 274 275 /* (non-Javadoc) 276 * @see edu.nrao.sss.model.project.scan.ScanLoopElement#toString() 277 */ 278 public String toSummaryString() 279 { 280 StringBuilder buff = new StringBuilder(super.toSummaryString()); 281 282 buff.append(", offsetCnt=").append(offsets.size()); 283 284 return buff.toString(); 285 } 286 287 //============================================================================ 288 // 289 //============================================================================ 290 291 /** 292 * Returns a focus scan that is a copy of this one. 293 * <p> 294 * The returned scan is, for the most part, a deep copy of this one. 295 * However, there are a few exceptions noted in the 296 * {@link ScanLoop#clone() clone method} of this class's parent.</p> 297 * <p> 298 * If anything goes wrong during the cloning procedure, 299 * a {@code RuntimeException} will be thrown.</p> 300 */ 301 public FocusScan clone() 302 { 303 FocusScan clone = null; 304 305 try 306 { 307 clone = (FocusScan)super.clone(); 308 309 //Need to clone set AND contained elements. 310 clone.offsets = new ArrayList<FocusOffset>(); 311 for (FocusOffset fo : this.offsets) 312 clone.offsets.add(fo.clone()); 313 } 314 catch (Exception ex) 315 { 316 throw new RuntimeException(ex); 317 } 318 319 return clone; 320 } 321 322 /** 323 * Returns <i>true</i> if {@code o} is equal to this focus scan. 324 * <p> 325 * In order for {@code o} to be equal to this scan, it must have 326 * equal focus offsets in the same order as those of this scan. 327 * It must also follow the rules set forth in the 328 * {@link ScanLoop#equals(Object) equals method} of this class's parent.</p> 329 */ 330 public boolean equals(Object o) 331 { 332 //Quick exit if o is this object 333 if (o == this) 334 return true; 335 336 //Not equal if super class says not equal 337 if (!super.equals(o)) 338 return false; 339 340 //Super class tested for Class equality, so cast is safe 341 FocusScan other = (FocusScan)o; 342 343 //Compare attributes 344 return other.offsets.equals(this.offsets); 345 } 346 347 /* (non-Javadoc) 348 * @see edu.nrao.sss.model.project.scan.Scan#hashCode() 349 */ 350 public int hashCode() 351 { 352 //Taken from the Effective Java book by Joshua Bloch. 353 //The constant 37 is arbitrary & carries no meaning. 354 int result = 37 * super.hashCode(); 355 356 result = 37 * result + offsets.hashCode(); 357 358 return result; 359 } 360 361 //============================================================================ 362 // 363 //============================================================================ 364 /* 365 public static void main(String[] args) throws Exception 366 { 367 FocusScan scan1 = new FocusScan(); 368 FocusScan scan2 = new FocusScan(); 369 370 scan1.addOffsets(5, new Distance(1.0, DistanceUnits.MILLIMETER), 371 new TimeDuration(5.0, edu.nrao.sss.measure.TimeUnits.MINUTE)); 372 373 scan2.addOffsets(new Distance(-5.0, DistanceUnits.MILLIMETER), 374 new Distance(+5.0, DistanceUnits.MILLIMETER), 375 11, 376 new TimeDuration(5.0, edu.nrao.sss.measure.TimeUnits.MINUTE)); 377 378 System.out.println("SCAN 1:"); 379 for (FocusOffset offset : scan1.getOffsets()) 380 System.out.println(" " + offset); 381 382 System.out.println(); 383 384 System.out.println("SCAN 2:"); 385 for (FocusOffset offset : scan2.getOffsets()) 386 System.out.println(" " + offset); 387 } 388 */ 389 }