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 }