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>&lt;w:txt&gt;, &lt;w:txtFlow&gt;, &lt;w:tooltip&gt;</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    }