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 }