001    package edu.nrao.sss.model.source;
002    
003    import java.net.MalformedURLException;
004    import java.net.URL;
005    
006    import edu.nrao.sss.astronomy.StokesParameter;
007    import edu.nrao.sss.measure.Frequency;
008    
009    /**
010     * A descriptive link to an image of an astronomical source.
011     * <p>
012     * <b>CVS Info:</b>
013     * <table style="margin-left:2em">
014     *   <tr><td>$Revision: 1911 $</td></tr>
015     *   <tr><td>$Date: 2009-01-21 10:27:48 -0700 (Wed, 21 Jan 2009) $</td></tr>
016     *   <tr><td>$Author: dharland $</td></tr>
017     * </table></p>
018     * 
019     * @author David M. Harland
020     * @since 2006-11-29
021     */
022    public class SourceImageLink
023      implements Cloneable, Comparable<SourceImageLink>
024    {
025      private static final String NO_COMMENTS = "";
026      private static final String NO_NAME     = "[image]";
027      
028      private static URL DEFAULT_IMAGE_URL;
029      static
030      {
031        try { DEFAULT_IMAGE_URL = new URL("http://www.nrao.edu/brokenLink.html"); }
032        catch (MalformedURLException ex) { DEFAULT_IMAGE_URL = null; }
033      }
034      
035      private Frequency       frequency;
036      private StokesParameter polarization;
037      private String          comments;
038      private URL             imageLocation;
039      private String          displayName;
040      
041      /**
042       * Creates a new instance with a phony URL, an unknown polarization,
043       * and a frequency of zero GHz.
044       */
045      public SourceImageLink()
046      {
047        this(null, null, null);
048      }
049      
050      /**
051       * @param frequency the frequency at which the linked image was observed.
052       *                  If this parameter is <i>null</i>,
053       *                  a new frequency of zero GHz will be used.
054       *                  
055       * @param polarization the polarization for which the linked image was
056       *                     observed.  If this parameter is <i>null</i>,
057       *                     a default polarization will be used.
058       *                     
059       * @param imageLocation the URL for the image.
060       *                      If this parameter is <i>null</i>,
061       *                      a phony non-null URL will be used.
062       */
063      public SourceImageLink(Frequency       frequency,
064                             StokesParameter polarization,
065                             URL             imageLocation)
066      {
067        //In general, it's not a good idea to call non-final methods
068        //in constructors (due to the fact that these methods could
069        //be overridden in subclasses, and that the overridden methods
070        //might interfere with the valid construction of this object).
071        //However, for this simple class we make this choice consciously.
072        setFrequency(frequency);
073        setPolarization(polarization);
074        setImageLocation(imageLocation);
075        
076        comments    = NO_COMMENTS;
077        displayName = NO_NAME;
078      }
079    
080      /**
081       * Sets the frequency at which the linked image was observed.
082       *
083       * @param newFrequency the frequency at which the linked image was observed.
084       *                     If this parameter is <i>null</i>,
085       *                     a new frequency of zero GHz will be used.
086       */
087      public void setFrequency(Frequency newFrequency)
088      {
089        frequency = (newFrequency == null) ? new Frequency() : newFrequency;
090      }
091    
092      /**
093       * Returns the frequency at which the linked image was observed.
094       * The value returned is guaranteed to be non-null.
095       *
096       * @return the frequency at which the linked image was observed.
097       */
098      public Frequency getFrequency()
099      {
100        return frequency;
101      }
102    
103      /**
104       * Sets the polarization for which the linked image was observed.
105       *
106       * @param newPolarization the polarization for which the linked image was
107       *                        observed.  If this parameter is <i>null</i>,
108       *                        a default polarization will be used.
109       */
110      public void setPolarization(StokesParameter newPolarization)
111      {
112        polarization = (newPolarization == null) ? StokesParameter.getDefault()
113                                                 : newPolarization;
114      }
115    
116      /**
117       * Returns the polarization of this SourceImageLink.java.
118       * The value returned is guaranteed to be non-null.
119       *
120       * @return the polarization of this SourceImageLink.java.
121       */
122      public StokesParameter getPolarization()
123      {
124        return polarization;
125      }
126    
127      /**
128       * Stores free-form text related to this link.
129       * The comments usually relate to the manner in which the
130       * image was obtained.  This might include the telescope used,
131       * the date of the acquisition, the person performing the
132       * observation, and anything else that might be of interest
133       * to other astronomers.</p>
134       * 
135       * @param replacementComments
136       *   free-form text related to this link.
137       *   These comments replace all previously set comments.
138       *   A <i>null</i> value will be replaced by the empty string (<tt>""</tt>}).
139       * 
140       * @see #appendComments(String)
141       */
142      public void setComments(String replacementComments)
143      {
144        comments = (replacementComments == null) ? NO_COMMENTS : replacementComments;
145      }
146      
147      /**
148       * Adds additional comments to those already associated with this link.
149       * 
150       * @param additionalComments
151       *   new, additional, comments for this link.
152       *   
153       * @see #setComments(String)
154       */
155      public void appendComments(String additionalComments)
156      {
157        if ((additionalComments != null) && (additionalComments.length() > 0))
158        {
159          if (!comments.equals(NO_COMMENTS))
160            comments = comments + System.getProperty("line.separator");
161          
162          comments = comments + additionalComments;
163        }
164      }
165      
166      /**
167       * Returns free-form text related to this link.
168       * The value returned is guaranteed to be non-null.
169       * <p>
170       * See {@link #setComments(String)} for the typical use
171       * of this property.</p>
172       * 
173       * @return free-form text related to this link.
174       */
175      public String getComments()
176      {
177        return comments;
178      }
179      
180      /**
181       * Sets the URL for this image.
182       *
183       * @param imageLocation the URL for the image.
184       *                      If this parameter is <i>null</i>,
185       *                      a phony non-null URL will be used.
186       */
187      public void setImageLocation(URL imageLocation)
188      {
189        this.imageLocation = (imageLocation == null) ? DEFAULT_IMAGE_URL
190                                                     : imageLocation;
191      }
192    
193      /**
194       * Returns the URL for this image.
195       * The value returned is guaranteed to be non-null, but it may be
196       * a phony URL.
197       *
198       * @return the URL for this image.
199       */
200      public URL getImageLocation()
201      {
202        return imageLocation;
203      }
204      
205      /**
206       * Sets the display name for this link.
207       * 
208       * @param newName the display name for this link.  If this value is
209       *                <i>null</i>, a non-null default name is used in its
210       *                place.
211       */
212      public void setDisplayName(String newName)
213      {
214        displayName = (newName == null) ? NO_NAME : newName;
215      }
216      
217      /**
218       * Returns text that may be used for displaying this link.
219       * @return text that may be used for displaying this link.
220       */
221      public String getDisplayName()
222      {
223        return displayName;
224      }
225      
226      /**
227       * Returns a string in the form
228       * <tt>
229       * &lt;a href="<i>getImageLocation().toString()</i>"&gt;<i>getDisplayName()</i>&lt;/a&gt;
230       * </tt>
231       * @return an HTML anchor representing this link.
232       */
233      public String toHtmlAnchor()
234      {
235        StringBuilder buff = new StringBuilder("<a href=\"");
236        
237        buff.append(getImageLocation().toString()).append("\">");
238        buff.append(getDisplayName()).append("</a>");
239        
240        return buff.toString();
241      }
242      
243      /**
244       *  Returns an image link that is a copy of this one.
245       *  <p>
246       *  If anything goes wrong during the cloning procedure,
247       *  a {@code RuntimeException} will be thrown.</p>
248       */
249      @Override
250      public SourceImageLink clone()
251      {
252        SourceImageLink clone = null;
253    
254        try
255        {
256          //This line takes care of the primitive & immutable fields properly
257          clone = (SourceImageLink)super.clone();
258          
259          clone.frequency = this.frequency.clone();
260          
261          clone.imageLocation = new URL(this.imageLocation.toString());
262        }
263        catch (Exception ex)
264        {
265          throw new RuntimeException(ex);
266        }
267        
268        return clone;
269      }
270      
271      /** Returns <i>true</i> if {@code o} is equal to this image link. */
272      @Override
273      public boolean equals(Object o)
274      {
275        //Quick exit if o is this
276        if (o == this)
277          return true;
278        
279        //Quick exit if o is null
280        if (o == null)
281          return false;
282        
283        //Quick exit if classes are different
284        if (!o.getClass().equals(this.getClass()))
285          return false;
286    
287        SourceImageLink other = (SourceImageLink)o;
288        
289        //The comments & displayName are intentionally ignored
290        return other.imageLocation.toString()
291                                  .equals(this.imageLocation.toString()) &&
292               other.polarization.equals (this.polarization)  &&
293               other.frequency.equals    (this.frequency);
294      }
295    
296      /** Returns a hash code value for this image link. */
297      @Override
298      public int hashCode()
299      {
300        //Taken from the Effective Java book by Joshua Bloch.
301        //The constants 17 & 37 are arbitrary & carry no meaning.
302        int result = 17;
303        
304        //You MUST keep this method in sync w/ the equals method
305    
306        result = 37 * result + imageLocation.toString().hashCode();
307        result = 37 * result + polarization.hashCode();
308        result = 37 * result + frequency.hashCode();
309        
310        return result;
311      }
312    
313      /**
314       * Compares this image link to {@code other} for order.
315       * <p>
316       * One link is deemed to be "less than" the other if its frequency
317       * is lower than that of the other.
318       * In the case that two links are for the same frequency, the
319       * polarization is used as a tie-breaker.  If these are the same,
320       * the text of the image location URL is the final tie-breaker.
321       * The comments are not compared.</p>
322       * 
323       * @param other the image link to which this one is compared.
324       * 
325       * @return a negative integer, zero, or a positive integer as this link
326       *         is less than, equal to, or greater than the other link.
327       */ 
328      public int compareTo(SourceImageLink other)
329      {
330        //First compare the frequency
331        int answer = this.frequency.compareTo(other.frequency);
332        if (answer != 0)
333          return answer;
334    
335        //Polarization
336        answer = this.polarization.compareTo(other.polarization);
337        if (answer != 0)
338          return answer;
339        
340        //Ignore the comments property
341        
342        //The link itself
343        return
344          this.imageLocation.toString().compareTo(other.imageLocation.toString());
345      }
346      
347      /*
348      public static void main(String[] args)
349      {
350        SourceBuilder builder = new SourceBuilder();
351        for (int trial = 1; trial <= 10; trial++)
352        {
353          SourceImageLink l1 = builder.makeImageLink();
354          SourceImageLink l2 = builder.makeImageLink();
355        
356          System.out.println(l1.equals(l2));
357          System.out.println(l1.toHtmlAnchor());
358        }
359      }
360      */
361    }