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 }