|
0
|
1 |
package hirondelle.web4j.model;
|
|
|
2 |
|
|
|
3 |
import hirondelle.web4j.model.Id;
|
|
|
4 |
import hirondelle.web4j.model.ModelCtorException;
|
|
|
5 |
import hirondelle.web4j.model.ModelUtil;
|
|
|
6 |
import hirondelle.web4j.model.Check;
|
|
|
7 |
import hirondelle.web4j.security.SafeText;
|
|
|
8 |
|
|
|
9 |
import java.io.IOException;
|
|
|
10 |
import java.io.ObjectInputStream;
|
|
|
11 |
import java.io.ObjectOutputStream;
|
|
|
12 |
import java.io.Serializable;
|
|
|
13 |
|
|
|
14 |
/**
|
|
|
15 |
An item in a code table.
|
|
|
16 |
|
|
|
17 |
<P>Here, a value in a code table is modeled with one required item (text), and four optional items
|
|
|
18 |
(id, short form, long form, and order index). The id is optional, since for 'add' operations it is not yet
|
|
|
19 |
specified. This class is offered as a convenience for implementing Code Tables.
|
|
|
20 |
Applications are not required to use it.
|
|
|
21 |
|
|
|
22 |
<P>Please see the example application for an example of one way of implementing code tables.
|
|
|
23 |
|
|
|
24 |
<h3>Code Tables</h3>
|
|
|
25 |
<P>A code table is an informal term describing a set of related values. It resembles
|
|
|
26 |
an enumeration. Simple examples :
|
|
|
27 |
<ul>
|
|
|
28 |
<li>geographical divisions - countries, states or provinces
|
|
|
29 |
<li>list of accepted credit cards - Mastercard, Visa, and so on
|
|
|
30 |
<li>the account types offered by a bank - chequing, savings, and so on
|
|
|
31 |
</ul>
|
|
|
32 |
|
|
|
33 |
Other important aspects of code tables :
|
|
|
34 |
<ul>
|
|
|
35 |
<li>most applications use them.
|
|
|
36 |
<li>they often appear in the user interface as drop-down <tt>SELECT</tt> controls.
|
|
|
37 |
<li>they usually don't change very often.
|
|
|
38 |
<li>they may have a specific sort order, unrelated to alphabetical ordering.
|
|
|
39 |
<li>it is almost always desirable that the user-visible text attached to a code be unique (in a given code table),
|
|
|
40 |
since the user will usually have no means to distinguish the identical items.
|
|
|
41 |
<li>it is often desirable to present the same code in different ways, according to context.
|
|
|
42 |
For example, report listings may present codes in an abbreviated style, while a tool-tips may present a
|
|
|
43 |
codes in a more verbose style, to explain their meaning.
|
|
|
44 |
<li>they are usually closely related to various foreign keys in the database.
|
|
|
45 |
</ul>
|
|
|
46 |
|
|
|
47 |
<h3>Underlying Data</h3>
|
|
|
48 |
Code tables may be implemented in various ways, including
|
|
|
49 |
<ul>
|
|
|
50 |
<li>database tables constructed explicitly for that purpose
|
|
|
51 |
<li>database tables that have already been constructed to hold problem domain items. That is,
|
|
|
52 |
data from an existing table may be extracted in a shortened form, to be used as a code table.
|
|
|
53 |
<li>simple in-memory data. For example, a <tt>1..10</tt> rating system might use simple <tt>Integer</tt>
|
|
|
54 |
objects created upon startup.
|
|
|
55 |
</ul>
|
|
|
56 |
|
|
|
57 |
<h3>Avoiding Double-Escaping</h3>
|
|
|
58 |
This class uses {@link SafeText}, which escapes special characters.
|
|
|
59 |
When rendering a <tt>Code</tt> object in a JSP, some care must be taken to ensure that
|
|
|
60 |
special characters are not mistakenly escaped <em>twice</em>.
|
|
|
61 |
|
|
|
62 |
<P>In a single language app, it's usually safe to render a <tt>Code</tt> by simply using <tt>${code}</tt>. This
|
|
|
63 |
calls {@link #toString()}, which returns escaped text, safe for direct rendering in a JSP.
|
|
|
64 |
|
|
|
65 |
<P>In a multilingual app, however, the various translation tags
|
|
|
66 |
(<tt><w:txt>, <w:txtFlow>, <w:tooltip></tt>) <em>already escape special characters</em>.
|
|
|
67 |
So, if a translation tag encounters a <tt>Code</tt> somewhere its body, the <tt>Code</tt> must be in
|
|
|
68 |
an <em>unescaped</em> form, otherwise it wil be escaped <em>twice</em>, which undesirable.
|
|
|
69 |
<span class='highlight'>In a multilingual app, you should usually render a <tt>Code</tt> using <tt>${code.text.rawString}</tt>.</span>
|
|
|
70 |
|
|
|
71 |
<P>This asymmetry between single-language and many-language apps is somewhat displeasing, and
|
|
|
72 |
constitutes a pitfall of using this class. If desired, you could define an alternate <tt>Code</tt> class
|
|
|
73 |
whose <tt>toString</tt> returns a <tt>String</tt> instead of {@link SafeText}.
|
|
|
74 |
*/
|
|
|
75 |
public final class Code implements Serializable {
|
|
|
76 |
|
|
|
77 |
/**
|
|
|
78 |
Full constructor.
|
|
|
79 |
|
|
|
80 |
@param aId underlying database identifier for the code value. Optional, 1..50 characters.
|
|
|
81 |
@param aText default text for presentation to the end user. Required, 1..50 characters.
|
|
|
82 |
@param aShortText short form for text presented to the end user. This item is useful for terse
|
|
|
83 |
presentation in reports, often as an abbreviation of one or two letters. Optional, 1..10 characters.
|
|
|
84 |
@param aLongText long form for text presented to the user. For example, this may be a description
|
|
|
85 |
or definition of the meaning of the code. Optional, 1..200 characters.
|
|
|
86 |
@param aOrderIdx defines the order of appearance of this item in a listing. Optional, range 1..1000.
|
|
|
87 |
This item is used to provide explicit control over the order of appearance of items as presented to
|
|
|
88 |
the user, and will often be related to an <tt>ORDER BY</tt> clause in an SQL statement.
|
|
|
89 |
*/
|
|
|
90 |
public Code(Id aId, SafeText aText, SafeText aShortText, SafeText aLongText, Integer aOrderIdx) throws ModelCtorException {
|
|
|
91 |
fId = aId;
|
|
|
92 |
fText = aText;
|
|
|
93 |
fShortText = aShortText;
|
|
|
94 |
fLongText = aLongText;
|
|
|
95 |
fOrderIdx = aOrderIdx;
|
|
|
96 |
validateState();
|
|
|
97 |
}
|
|
|
98 |
|
|
|
99 |
/** 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 |
}
|