001 package hirondelle.web4j.model; 002 003 import java.io.Serializable; 004 import java.io.ObjectInputStream; 005 import java.io.ObjectOutputStream; 006 import java.io.IOException; 007 import hirondelle.web4j.model.ModelUtil; 008 import hirondelle.web4j.util.Consts; 009 import hirondelle.web4j.util.Util; 010 import hirondelle.web4j.security.SafeText; 011 012 /** 013 Building block class for identifiers. 014 015 <P>Identifiers are both common and important. Unfortunately, there is no class in the 016 JDK specifically for identifiers. 017 018 <P>An <tt>Id</tt> class is useful for these reasons : 019 <ul> 020 <li>it allows model classes to read at a higher level of abstraction. Identifiers are 021 labeled as such, and stand out very clearly from other items 022 <li>it avoids a common <a href="http://www.javapractices.com/Topic192.cjp">problem</a> 023 in modeling identifiers as numbers. 024 </ul> 025 026 <P>The underlying database column may be modeled as either text or as a number. 027 If the underlying column is of a numeric type, however, then a Data Access Object 028 will need to pass <tt>Id</tt> parameters to {@link hirondelle.web4j.database.Db} 029 using {@link #asInteger} or {@link #asLong}. 030 031 <P><em>Design Note :</em><br> 032 This class is <tt>final</tt>, immutable, {@link Serializable}, 033 and {@link Comparable}, in imitation of the other building block classes 034 such as {@link String}, {@link Integer}, and so on. 035 */ 036 public final class Id implements Serializable, Comparable<Id> { 037 038 /** 039 Construct an identifier using an arbitrary {@link String}. 040 041 This class uses a {@link SafeText} object internally. 042 @param aText is non-null, and contains characters that are allowed 043 by {@link hirondelle.web4j.security.PermittedCharacters}. 044 */ 045 public Id(String aText) { 046 fId = new SafeText(aText); 047 validateState(); 048 } 049 050 /** 051 Factory method. 052 053 Simply a slightly more compact way of building an object, as opposed to 'new'. 054 */ 055 public static Id from(String aText){ 056 return new Id(aText); 057 } 058 059 /** 060 Return this id as an {@link Integer}, if possible. 061 062 <P>See class comment. 063 064 <P>If this <tt>Id</tt> is not convertible to an {@link Integer}, then a {@link RuntimeException} is 065 thrown. 066 */ 067 public Integer asInteger(){ 068 return new Integer(fId.getRawString()); 069 } 070 071 /** 072 Return this id as a {@link Long}, if possible. 073 074 <P>See class comment. 075 076 <P>If this <tt>Id</tt> is not convertible to a {@link Long}, 077 then a {@link RuntimeException} is thrown. 078 */ 079 public Long asLong(){ 080 return new Long(fId.getRawString()); 081 } 082 083 /** 084 Return the id, with special characters escaped. 085 086 <P>The return value either has content (with no leading or trailing spaces), 087 or is empty. 088 See {@link hirondelle.web4j.util.EscapeChars#forHTML(String)} for a list of escaped 089 characters. 090 */ 091 @Override public String toString(){ 092 return fId.toString(); 093 } 094 095 /** Return the text passed to the constructor. */ 096 public String getRawString(){ 097 return fId.getRawString(); 098 } 099 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 }