001 package hirondelle.web4j.model;
002
003 import hirondelle.web4j.model.Id;
004 import hirondelle.web4j.model.ModelCtorException;
005 import hirondelle.web4j.model.ModelUtil;
006 import hirondelle.web4j.model.Check;
007 import hirondelle.web4j.security.SafeText;
008
009 import java.io.IOException;
010 import java.io.ObjectInputStream;
011 import java.io.ObjectOutputStream;
012 import java.io.Serializable;
013
014 /**
015 An item in a code table.
016
017 <P>Here, a value in a code table is modeled with one required item (text), and four optional items
018 (id, short form, long form, and order index). The id is optional, since for 'add' operations it is not yet
019 specified. This class is offered as a convenience for implementing Code Tables.
020 Applications are not required to use it.
021
022 <P>Please see the example application for an example of one way of implementing code tables.
023
024 <h3>Code Tables</h3>
025 <P>A code table is an informal term describing a set of related values. It resembles
026 an enumeration. Simple examples :
027 <ul>
028 <li>geographical divisions - countries, states or provinces
029 <li>list of accepted credit cards - Mastercard, Visa, and so on
030 <li>the account types offered by a bank - chequing, savings, and so on
031 </ul>
032
033 Other important aspects of code tables :
034 <ul>
035 <li>most applications use them.
036 <li>they often appear in the user interface as drop-down <tt>SELECT</tt> controls.
037 <li>they usually don't change very often.
038 <li>they may have a specific sort order, unrelated to alphabetical ordering.
039 <li>it is almost always desirable that the user-visible text attached to a code be unique (in a given code table),
040 since the user will usually have no means to distinguish the identical items.
041 <li>it is often desirable to present the same code in different ways, according to context.
042 For example, report listings may present codes in an abbreviated style, while a tool-tips may present a
043 codes in a more verbose style, to explain their meaning.
044 <li>they are usually closely related to various foreign keys in the database.
045 </ul>
046
047 <h3>Underlying Data</h3>
048 Code tables may be implemented in various ways, including
049 <ul>
050 <li>database tables constructed explicitly for that purpose
051 <li>database tables that have already been constructed to hold problem domain items. That is,
052 data from an existing table may be extracted in a shortened form, to be used as a code table.
053 <li>simple in-memory data. For example, a <tt>1..10</tt> rating system might use simple <tt>Integer</tt>
054 objects created upon startup.
055 </ul>
056
057 <h3>Avoiding Double-Escaping</h3>
058 This class uses {@link SafeText}, which escapes special characters.
059 When rendering a <tt>Code</tt> object in a JSP, some care must be taken to ensure that
060 special characters are not mistakenly escaped <em>twice</em>.
061
062 <P>In a single language app, it's usually safe to render a <tt>Code</tt> by simply using <tt>${code}</tt>. This
063 calls {@link #toString()}, which returns escaped text, safe for direct rendering in a JSP.
064
065 <P>In a multilingual app, however, the various translation tags
066 (<tt><w:txt>, <w:txtFlow>, <w:tooltip></tt>) <em>already escape special characters</em>.
067 So, if a translation tag encounters a <tt>Code</tt> somewhere its body, the <tt>Code</tt> must be in
068 an <em>unescaped</em> form, otherwise it wil be escaped <em>twice</em>, which undesirable.
069 <span class='highlight'>In a multilingual app, you should usually render a <tt>Code</tt> using <tt>${code.text.rawString}</tt>.</span>
070
071 <P>This asymmetry between single-language and many-language apps is somewhat displeasing, and
072 constitutes a pitfall of using this class. If desired, you could define an alternate <tt>Code</tt> class
073 whose <tt>toString</tt> returns a <tt>String</tt> instead of {@link SafeText}.
074 */
075 public final class Code implements Serializable {
076
077 /**
078 Full constructor.
079
080 @param aId underlying database identifier for the code value. Optional, 1..50 characters.
081 @param aText default text for presentation to the end user. Required, 1..50 characters.
082 @param aShortText short form for text presented to the end user. This item is useful for terse
083 presentation in reports, often as an abbreviation of one or two letters. Optional, 1..10 characters.
084 @param aLongText long form for text presented to the user. For example, this may be a description
085 or definition of the meaning of the code. Optional, 1..200 characters.
086 @param aOrderIdx defines the order of appearance of this item in a listing. Optional, range 1..1000.
087 This item is used to provide explicit control over the order of appearance of items as presented to
088 the user, and will often be related to an <tt>ORDER BY</tt> clause in an SQL statement.
089 */
090 public Code(Id aId, SafeText aText, SafeText aShortText, SafeText aLongText, Integer aOrderIdx) throws ModelCtorException {
091 fId = aId;
092 fText = aText;
093 fShortText = aShortText;
094 fLongText = aLongText;
095 fOrderIdx = aOrderIdx;
096 validateState();
097 }
098
099 /** As in the full constructor, but without a short description, long description, or an order index. */
100 public Code(Id aId, SafeText aText) throws ModelCtorException {
101 fId = aId;
102 fText = aText;
103 fShortText = null;
104 fLongText = null;
105 fOrderIdx = null;
106 validateState();
107 }
108
109 /** As in the full constructor, but without a long description or order index. */
110 public Code(Id aId, SafeText aText, SafeText aShortText) throws ModelCtorException {
111 fId = aId;
112 fText = aText;
113 fShortText = aShortText;
114 fLongText = null;
115 fOrderIdx = null;
116 validateState();
117 }
118
119 /** As in the full constructor, but without an order index. */
120 public Code(Id aId, SafeText aText, SafeText aShortText, SafeText aLongText) throws ModelCtorException {
121 fId = aId;
122 fText = aText;
123 fShortText = aShortText;
124 fLongText = aLongText;
125 fOrderIdx = null;
126 validateState();
127 }
128
129 /** Return the Id passed to the constructor. */
130 public Id getId() { return fId; }
131 /** Return the Text passed to the constructor. */
132 public SafeText getText() { return fText; }
133 /** Return the Short Text passed to the constructor. */
134 public SafeText getShortText() { return fShortText; }
135 /** Return the Long Text passed to the constructor. */
136 public SafeText getLongText() { return fLongText; }
137 /** Return the Order Index passed to the constructor. */
138 public Integer getOrderIdx() { return fOrderIdx; }
139
140 /**
141 Returns {@link #getText()}.toString().
142
143 <P>This is the most user-friendly form of a code, and is useful for rendering in JSPs.
144 */
145 @Override public String toString(){
146 return fText.toString();
147 }
148
149 @Override public boolean equals(Object aThat){
150 Boolean result = ModelUtil.quickEquals(this, aThat);
151 if ( result == null ){
152 Code that = (Code) aThat;
153 result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
154 }
155 return result;
156 }
157
158 @Override public int hashCode() {
159 if ( fHashCode == 0 ) {
160 fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
161 }
162 return fHashCode;
163 }
164
165 // PRIVATE
166
167 /** @serial */
168 private final Id fId;
169 /** @serial */
170 private final SafeText fText;
171 /** @serial */
172 private final SafeText fShortText;
173 /** @serial */
174 private final SafeText fLongText;
175 /** @serial */
176 private final Integer fOrderIdx;
177 /** @serial */
178 private int fHashCode;
179
180 /**
181 For evolution of this class, see Sun guidelines :
182 http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/version.html#6678
183 */
184 private static final long serialVersionUID = 8856876119383545215L;
185
186 private Object[] getSignificantFields(){
187 return new Object[] {fId, fText, fShortText, fLongText, fOrderIdx};
188 }
189
190 private void validateState() throws ModelCtorException {
191 ModelCtorException ex = new ModelCtorException();
192 if ( ! Check.optional(fId, Check.min(1), Check.max(50))) {
193 ex.add("Code Id is optional, 1..50 chars.");
194 }
195 if ( ! Check.required(fText, Check.range(1,50))) {
196 ex.add("Text is required, 1..50 chars.");
197 }
198 if ( ! Check.optional(fShortText, Check.range(1,10))) {
199 ex.add("Short Text is optional, 1..10 chars.");
200 }
201 if ( ! Check.optional(fLongText, Check.range(1,200))) {
202 ex.add("Long Text is optional, 1..200 chars.");
203 }
204 if ( ! Check.optional(fOrderIdx, Check.range(1,1000))) {
205 ex.add("Order Idx is optional, 1..1000.");
206 }
207 if ( ! ex.isEmpty() ) throw ex;
208 }
209
210 /**
211 Always treat de-serialization as a full-blown constructor, by
212 validating the final state of the de-serialized object.
213 */
214 private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException, ModelCtorException {
215 //always perform the default de-serialization first
216 aInputStream.defaultReadObject();
217 //make defensive copy of mutable fields (none here)
218 //ensure that object state has not been corrupted or tampered with maliciously
219 validateState();
220 }
221
222 /**
223 This is the default implementation of writeObject.
224 Customise if necessary.
225 */
226 private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
227 //perform the default serialization for all non-transient, non-static fields
228 aOutputStream.defaultWriteObject();
229 }
230 }