|
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 } |