View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2000 William Chesters
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     William Chesters <williamc At paneris.org>
42   *     http://paneris.org/~williamc
43   *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
44   */
45  
46  package org.melati.poem;
47  
48  import java.sql.PreparedStatement;
49  import java.sql.ResultSet;
50  import java.sql.SQLException;
51  import java.util.Enumeration;
52  
53  import org.melati.poem.dbms.Dbms;
54  import org.melati.poem.util.ConsEnumeration;
55  
56  /**
57   * Base class of all fundamental types.
58   */
59  public abstract class BasePoemType<T> implements SQLPoemType<T>, Cloneable {
60    private int sqlTypeCode;
61    protected boolean nullable;
62  
63    private Comparable<T> low = null, limit = null;
64  
65    BasePoemType(int sqlTypeCode, boolean nullable) {
66      this.sqlTypeCode = sqlTypeCode;
67      this.nullable = nullable;
68    }
69  
70    /**
71     * Set the limits, if applicable.
72     * @param low included lower limit
73     * @param limit excluded upper limit
74     */
75    public void setRawRange(Comparable<T> low, Comparable<T> limit) {
76      this.low = low;
77      this.limit = limit;
78    }
79  
80    protected Comparable<T> getLowRaw() {
81      return low;
82    }
83  
84    protected Comparable<T> getLimitRaw() {
85      return limit;
86    }
87  
88    protected abstract void _assertValidRaw(Object raw)
89        throws ValidationPoemException;
90  
91    @SuppressWarnings("unchecked")
92    private void assertRawInRange(Object raw) {
93      // Range check.  Since we can't do this with multiple inheritance, we
94      // provide it as a facility even in types for which it is meaningless.
95  
96      T asComparable;
97      try {
98        // Note that in java5 this will not throw until 
99        // the cast object is accessed
100       asComparable = (T)raw;
101 
102       if ((low != null && low.compareTo(asComparable) > 0) ||
103           (limit != null && limit.compareTo(asComparable) <= 0))
104       throw new ValidationPoemException(
105             this, raw, new OutsideRangePoemException(low, limit, raw));
106     } catch (ClassCastException e) {
107       throw new NotComparablePoemException(raw, this);
108     }
109   }
110 
111   /**
112    * {@inheritDoc}
113    * @see org.melati.poem.PoemType#assertValidRaw(java.lang.Object)
114    */
115   public final void assertValidRaw(Object raw)
116       throws ValidationPoemException {
117     if (raw == null) {
118       if (!nullable)
119         throw new NullTypeMismatchPoemException(this);
120     }
121     else {
122       if (low != null || limit != null)
123         assertRawInRange(raw);
124       _assertValidRaw(raw);
125     }
126   }
127 
128   /**
129    * Check if the raw value is valid, as expected.
130    * @param raw an Object which should be of correct type
131    */
132   private void doubleCheckValidRaw(Object raw) {
133     try {
134       assertValidRaw(raw);
135     }
136     catch (ValidationPoemException e) {
137       throw new UnexpectedValidationPoemException(e);
138     }
139   }
140 
141   protected abstract T _getRaw(ResultSet rs, int col)
142       throws SQLException;
143 
144   /**
145    * {@inheritDoc}
146    * @see org.melati.poem.SQLType#getRaw(java.sql.ResultSet, int)
147    */
148   public final T getRaw(ResultSet rs, int col)
149       throws ValidationPoemException {
150     T o;
151     try {
152       o = (T) _getRaw(rs, col);
153     }
154     catch (SQLException e) {
155       throw new SQLSeriousPoemException(e);
156     }
157 
158     assertValidRaw(o);
159     return o;
160   }
161 
162   protected abstract void _setRaw(PreparedStatement ps, int col,
163                                   Object raw)
164       throws SQLException;
165 
166   /**
167    * {@inheritDoc}
168    * @see org.melati.poem.SQLType#setRaw(java.sql.PreparedStatement, int, java.lang.Object)
169    */
170   public final void setRaw(PreparedStatement ps, int col, Object raw) {
171     doubleCheckValidRaw(raw);
172     try {
173       if (raw == null)
174         ps.setNull(col, sqlTypeCode());
175       else
176         _setRaw(ps, col, raw);
177     }
178     catch (SQLException e) {
179       throw new SQLSeriousPoemException(e);
180     }
181   }
182 
183   protected Enumeration<T> _possibleRaws() {
184     return null;
185   }
186   
187   /**
188    * {@inheritDoc}
189    * @see org.melati.poem.PoemType#possibleRaws()
190    */
191   public Enumeration<T> possibleRaws() {
192     Enumeration<T> them = _possibleRaws();
193     return them == null ? null :
194                    getNullable() ? new ConsEnumeration<T>(null, them) :
195                    them;
196   }
197 
198   protected abstract String _stringOfRaw(Object raw);
199 
200   /**
201    * This <B>doesn't</B> do an explicit <TT>assertValidRaw</TT>.
202       * {@inheritDoc}
203    * @see org.melati.poem.PoemType#stringOfRaw(java.lang.Object)
204    */
205   public final String stringOfRaw(Object raw)
206       throws ValidationPoemException {
207     return raw == null ? null : _stringOfRaw(raw);
208   }
209 
210   /**
211    * Converts a non-null string to an appropriate value 
212    * for insertion into the underlying DBMS.
213    * @param string the String to parse
214    * @return a converted type
215    */
216   protected abstract T _rawOfString(String string)
217       throws ParsingPoemException;
218 
219   /**
220    * Converts a possibly null <code>String</code> to a low level
221    * representation of a valid database column value.
222    * <p>
223    * Null values are not changed.
224    * <p>
225    * This result is validated with {@link #assertValidRaw(Object)}
226    * whereas {@link #stringOfRaw(Object)} assumes this is not
227    * required.
228    * {@inheritDoc}
229    * @see org.melati.poem.PoemType#rawOfString(java.lang.String)
230    */
231   public final T rawOfString(String string)
232       throws ParsingPoemException, ValidationPoemException {
233     T raw = string == null ? null : _rawOfString(string);
234     assertValidRaw(raw);
235     return raw;
236   }
237 
238   protected abstract void _assertValidCooked(Object cooked)
239       throws ValidationPoemException;
240 
241   /**
242    * {@inheritDoc}
243    * @see org.melati.poem.PoemType#assertValidCooked(java.lang.Object)
244    */
245   public final void assertValidCooked(Object cooked)
246       throws ValidationPoemException {
247     if (cooked == null) {
248       if (!nullable)
249         throw new NullTypeMismatchPoemException(this);
250     }
251     else {
252       _assertValidCooked(cooked);
253       if (low != null || limit != null)
254         assertRawInRange(_rawOfCooked(cooked));
255     }
256   }
257 
258   /**
259    * Check that object is valid, as expected.
260    * NOTE If it isn't valid then it isn't cooked.
261    * @param cooked the cooked object
262    */
263   final void doubleCheckValidCooked(Object cooked) {
264     try {
265       assertValidCooked(cooked);
266     }
267     catch (ValidationPoemException e) {
268       throw new UnexpectedValidationPoemException(e);
269     }
270   }
271 
272   /**
273    * Converts a non-null low-level representation of a database
274    * column value to the appropriate object.
275    * <p>
276    * For the base object types, (String, Integer etc) this involves no change. 
277    * <p>
278    * For types with an integer id, such as Poem internal types and user defined types, 
279    * then the appropriate instantiated type is returned from its Integer id.
280    * @param raw the base object or Integer object id
281    * @return the unchanged base object or an instantiated type
282    */
283   protected abstract Object _cookedOfRaw(Object raw) throws PoemException;
284 
285   /**
286    * Converts a possibly null low-level representation of a database
287    * column value to its canonical form. 
288    * Types represented as integers in the database are converted to 
289    * corresponding objects .
290    * <p>
291    * The raw value is checked to ensure it is valid.
292    * {@inheritDoc}
293    * @see org.melati.poem.PoemType#cookedOfRaw(java.lang.Object)
294    */
295   public final Object cookedOfRaw(Object raw) throws PoemException {
296     doubleCheckValidRaw(raw);
297     return raw == null ? null : _cookedOfRaw(raw);
298   }
299 
300   protected abstract T _rawOfCooked(Object raw) throws PoemException;
301 
302   /**
303    * {@inheritDoc}
304    * @see org.melati.poem.PoemType#rawOfCooked(java.lang.Object)
305    */
306   public final T rawOfCooked(Object cooked) {
307     doubleCheckValidCooked(cooked);
308     return cooked == null ? null : _rawOfCooked(cooked);
309   }
310 
311   protected abstract String _stringOfCooked(Object cooked,
312                                            PoemLocale locale, int style)
313       throws PoemException;
314 
315   /**
316    * {@inheritDoc}
317    * @see org.melati.poem.PoemType#stringOfCooked(java.lang.Object, 
318    *          org.melati.poem.PoemLocale, int)
319    */
320   public final String stringOfCooked(Object cooked,
321                                     PoemLocale locale, int style)
322       throws PoemException {
323     doubleCheckValidCooked(cooked);
324     return cooked == null ? "" : _stringOfCooked(cooked, locale, style);
325   }
326 
327   /**
328    * {@inheritDoc}
329    * @see org.melati.poem.PoemType#getNullable()
330    */
331   public final boolean getNullable() {
332     return nullable;
333   }
334 
335   /**
336    * {@inheritDoc}
337    * @see org.melati.poem.SQLType#sqlTypeCode()
338    */
339   public final int sqlTypeCode() {
340     return sqlTypeCode;
341   }
342 
343   protected abstract String _sqlDefinition(Dbms dbms);
344 
345   /**
346    * See http://dev.mysql.com/doc/refman/5.0/en/timestamp.html
347    * The MySQL default for nullability of timestamps is not null, so need to 
348    * make all fields explicitly nullable.
349    * 
350    * {@inheritDoc}
351    * @see org.melati.poem.SQLType#sqlDefinition(org.melati.poem.dbms.Dbms)
352    */
353   public String sqlDefinition(Dbms dbms) {
354     return sqlTypeDefinition(dbms) + (nullable ? " NULL" : " NOT NULL");
355   }
356   /**
357    * {@inheritDoc}
358    * @see org.melati.poem.SQLType#sqlTypeDefinition(org.melati.poem.dbms.Dbms)
359    */
360   public String sqlTypeDefinition(Dbms dbms) {
361     return _sqlDefinition(dbms);
362   }
363   protected abstract boolean _canRepresent(SQLPoemType<?> other);
364 
365   /**
366    * {@inheritDoc}
367    * @see org.melati.poem.PoemType#canRepresent(org.melati.poem.PoemType)
368    */
369   public <O>PoemType<O> canRepresent(PoemType<O> other) {
370     // FIXME takes no account of range---need to decide on semantics for this,
371     // is it subset (inclusion) or some other notion of storability?
372     if (!(other instanceof SQLPoemType)) 
373       // NOTE Never happens as currently all PoemTypes are SQLPoemTypes
374       return null;
375     else {
376       SQLPoemType<O> q = (SQLPoemType<O>)other;
377       return
378           !(!nullable && q.getNullable()) && // Nullable may represent not nullable
379           _canRepresent(q) ?
380               q : null;
381     }
382   }
383 
384   /**
385    * {@inheritDoc}
386    * @see org.melati.poem.PoemType#withNullable(boolean)
387    */
388   @SuppressWarnings("unchecked")
389   public final PoemType<T> withNullable(boolean nullableP) {
390     if (this.nullable == nullableP)
391       return this;
392     else {
393       BasePoemType<T> it = (BasePoemType<T>)clone();
394       it.nullable = nullableP;
395       return it;
396     }
397   }
398 
399   protected abstract void _saveColumnInfo(ColumnInfo info)
400       throws AccessPoemException;
401 
402   /**
403    * {@inheritDoc}
404    * @see org.melati.poem.PoemType#saveColumnInfo(org.melati.poem.ColumnInfo)
405    */
406   public void saveColumnInfo(ColumnInfo info) throws AccessPoemException {
407     info.setNullable(nullable);
408     info.setSize(0);
409     info.setRangelow_string(
410         getLowRaw() == null ? null : stringOfRaw(getLowRaw()));
411     // this _won't_ throw an OutsideRangePoemException since it doesn't check
412     info.setRangelimit_string(
413         getLimitRaw() == null ? null : stringOfRaw(getLimitRaw()));
414     _saveColumnInfo(info);
415   }
416 
417   protected abstract String _quotedRaw(Object raw);
418 
419   /**
420    * {@inheritDoc}
421    * @see org.melati.poem.SQLType#quotedRaw(java.lang.Object)
422    */
423   public String quotedRaw(Object raw) throws ValidationPoemException {
424     assertValidRaw(raw);
425     return raw == null ? "NULL" : _quotedRaw(raw);
426   }
427 
428   protected abstract String _toString();
429 
430   // 
431   // --------
432   //  Object
433   // --------
434   // 
435 
436   /**
437    * {@inheritDoc}
438    * @see java.lang.Object#toString()
439    */
440   public String toString() {
441     return (nullable ? "nullable " : "") + _toString() + 
442     " (" + this.getClass().getName() + ")";
443   }
444 
445   /**
446    * {@inheritDoc}
447    * @see java.lang.Object#clone()
448    */
449   protected Object clone() {
450     try {
451       return super.clone();
452     }
453     catch (CloneNotSupportedException e) {
454       throw new PoemBugPoemException();
455     }
456   }
457 }