classes/hirondelle/web4j/model/Id.java
changeset 0 3060119b1292
equal deleted inserted replaced
-1:000000000000 0:3060119b1292
       
     1 package hirondelle.web4j.model;
       
     2 
       
     3 import java.io.Serializable;
       
     4 import java.io.ObjectInputStream;
       
     5 import java.io.ObjectOutputStream;
       
     6 import java.io.IOException;
       
     7 import hirondelle.web4j.model.ModelUtil;
       
     8 import hirondelle.web4j.util.Consts;
       
     9 import hirondelle.web4j.util.Util;
       
    10 import hirondelle.web4j.security.SafeText;
       
    11 
       
    12 /**
       
    13  Building block class for identifiers. 
       
    14  
       
    15  <P>Identifiers are both common and important. Unfortunately, there is no class in the 
       
    16  JDK specifically for identifiers. 
       
    17  
       
    18  <P>An <tt>Id</tt> class is useful for these reasons :
       
    19 <ul>
       
    20  <li>it allows model classes to read at a higher level of abstraction. Identifiers are 
       
    21  labeled as such, and stand out very clearly from other items
       
    22  <li>it avoids a common <a href="http://www.javapractices.com/Topic192.cjp">problem</a> 
       
    23  in modeling identifiers as numbers.
       
    24 </ul>
       
    25 
       
    26  <P>The underlying database column may be modeled as either text or as a number.
       
    27  If the underlying column is of a numeric type, however, then a Data Access Object
       
    28  will need to pass <tt>Id</tt> parameters to {@link hirondelle.web4j.database.Db} 
       
    29  using {@link #asInteger} or {@link #asLong}.
       
    30 
       
    31  <P><em>Design Note :</em><br>
       
    32  This class is <tt>final</tt>, immutable, {@link Serializable}, 
       
    33  and {@link Comparable}, in imitation of the other building block classes 
       
    34  such as {@link String}, {@link Integer}, and so on.
       
    35 */
       
    36 public final class Id implements Serializable, Comparable<Id> {
       
    37 
       
    38   /**
       
    39    Construct an identifier using an arbitrary {@link String}.
       
    40     
       
    41    This class uses a {@link SafeText} object internally.
       
    42    @param aText is non-null, and contains characters that are allowed 
       
    43    by {@link hirondelle.web4j.security.PermittedCharacters}.
       
    44   */
       
    45   public Id(String aText) {
       
    46     fId = new SafeText(aText);
       
    47     validateState();
       
    48   }
       
    49   
       
    50   /**
       
    51    Factory method.
       
    52    
       
    53    Simply a slightly more compact way of building an object, as opposed to 'new'.
       
    54   */
       
    55   public static Id from(String aText){
       
    56     return new Id(aText);
       
    57   }
       
    58 
       
    59   /**
       
    60    Return this id as an {@link Integer}, if possible.
       
    61    
       
    62    <P>See class comment.
       
    63    
       
    64    <P>If this <tt>Id</tt> is not convertible to an {@link Integer}, then a {@link RuntimeException} is 
       
    65    thrown.
       
    66   */
       
    67   public Integer asInteger(){
       
    68     return new Integer(fId.getRawString());
       
    69   }
       
    70 
       
    71   /**
       
    72    Return this id as a {@link Long}, if possible.
       
    73    
       
    74    <P>See class comment.
       
    75    
       
    76    <P>If this <tt>Id</tt> is not convertible to a {@link Long}, 
       
    77    then a {@link RuntimeException} is thrown.
       
    78   */
       
    79   public Long asLong(){
       
    80     return new Long(fId.getRawString());
       
    81   }
       
    82   
       
    83   /**
       
    84    Return the id, with special characters escaped. 
       
    85    
       
    86    <P>The return value either has content (with no leading or trailing spaces), 
       
    87    or is empty.
       
    88    See {@link hirondelle.web4j.util.EscapeChars#forHTML(String)} for a list of escaped
       
    89    characters.
       
    90   */
       
    91   @Override public String toString(){
       
    92     return fId.toString();
       
    93   }
       
    94   
       
    95   /** Return the text passed to the constructor. */
       
    96   public String getRawString(){
       
    97     return fId.getRawString();
       
    98   }
       
    99   
       
   100   /** Return the text with special  XML characters esacped. See {@link SafeText#getXmlSafe()}.  */
       
   101   public String getXmlSafe() {
       
   102     return fId.getXmlSafe();
       
   103   }
       
   104   
       
   105   @Override public boolean equals(Object aThat){
       
   106     Boolean result = ModelUtil.quickEquals(this, aThat);
       
   107     if ( result == null ){
       
   108       Id that = (Id) aThat;
       
   109       result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
       
   110     }
       
   111     return result;    
       
   112   }
       
   113 
       
   114   @Override public int hashCode(){
       
   115     return ModelUtil.hashCodeFor(getSignificantFields());
       
   116   }
       
   117   
       
   118   public int compareTo(Id aThat) {
       
   119     final int EQUAL = 0;
       
   120     if ( this == aThat ) return EQUAL;
       
   121     int comparison = this.fId.compareTo(aThat.fId);
       
   122     if ( comparison != EQUAL ) return comparison;
       
   123     return EQUAL;
       
   124   }
       
   125   
       
   126   // PRIVATE 
       
   127   
       
   128   /** @serial  */
       
   129   private SafeText fId;
       
   130   
       
   131   /**
       
   132    For evolution of this class, see Sun guidelines : 
       
   133    http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/version.html#6678 
       
   134   */
       
   135   private static final long serialVersionUID = 7526472295633676147L;
       
   136   
       
   137   /**
       
   138    Always treat de-serialization as a full-blown constructor, by
       
   139    validating the final state of the de-serialized object.
       
   140   */
       
   141   private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
       
   142      //always perform the default de-serialization first
       
   143      aInputStream.defaultReadObject();
       
   144      //make defensive copy of mutable fields (none here)
       
   145      //ensure that object state has not been corrupted or tampered with maliciously
       
   146      validateState();
       
   147   }
       
   148 
       
   149   /**
       
   150    This is the default implementation of writeObject.
       
   151    Customise if necessary.
       
   152   */
       
   153   private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
       
   154     //perform the default serialization for all non-transient, non-static fields
       
   155     aOutputStream.defaultWriteObject();
       
   156   }
       
   157   
       
   158   private void validateState() {
       
   159     if( ! Util.textHasContent(fId) ) {
       
   160       if ( ! Consts.EMPTY_STRING.equals(fId.getRawString()) ) {
       
   161         throw new IllegalArgumentException(
       
   162           "Id must have content, or be the empty String. Erroneous Value : " + Util.quote(fId)
       
   163         );
       
   164       }
       
   165     }
       
   166   }
       
   167   
       
   168   private Object[] getSignificantFields(){
       
   169     return new Object[] {fId};
       
   170   }
       
   171 }