KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > triactive > jdo > store > Column


1 /*
2  * Copyright 2004 (C) TJDO.
3  * All rights reserved.
4  *
5  * This software is distributed under the terms of the TJDO License version 1.0.
6  * See the terms of the TJDO License in the documentation provided with this software.
7  *
8  * $Id: Column.java,v 1.6 2004/03/22 04:58:13 jackknifebarber Exp $
9  */

10
11 package com.triactive.jdo.store;
12
13 import com.triactive.jdo.model.ColumnOptions;
14 import java.sql.Types JavaDoc;
15 import java.util.StringTokenizer JavaDoc;
16
17
18 /**
19  * A column in a database table.
20  * Column objects are used primarily for generating the DDL for a CREATE TABLE
21  * statement or for validating against an existing column in the database.
22  * <p>
23  * All columns are deemed to be serving as backing for a particular Java type.
24  * The Java type and other column attributes are used, usually by a
25  * ColumnMapping, to help determine a suitable corresponding SQL type.
26  * The {@link #setTypeInfo} method must be called at some point to establish the
27  * column's SQL type.
28  *
29  * @author <a HREF="mailto:mmartin5@austin.rr.com">Mike Martin</a>
30  * @version $Revision: 1.6 $
31  */

32
33 public class Column
34 {
35     public static final int LENGTH_NOT_SET = 0;
36     public static final int FIXED_LENGTH = 1;
37     public static final int MAXIMUM_LENGTH = 2;
38     public static final int UNLIMITED_LENGTH = 3;
39
40     protected static final int LENGTH_TYPE_MASK = 3;
41     protected static final int PRIMARY_KEY_PART = 4;
42     protected static final int EXACT_PRECISION = 8;
43     protected static final int NULLABLE = 16;
44     protected static final int UNIQUE = 32;
45
46     protected final Table table;
47     protected final Class JavaDoc type;
48     protected final SQLIdentifier name;
49
50     protected int precision = 0;
51     protected int scale = 0;
52     protected int flags = 0;
53
54     protected TypeInfo typeInfo = null;
55     protected String JavaDoc constraints = null;
56
57     public Column(Table table, Class JavaDoc type, SQLIdentifier name)
58     {
59         this.table = table;
60         this.type = type;
61         this.name = name;
62     }
63
64     public Table getTable()
65     {
66         return table;
67     }
68
69     public StoreManager getStoreManager()
70     {
71         return table.getStoreManager();
72     }
73
74     public SQLIdentifier getName()
75     {
76         return name;
77     }
78
79     public Class JavaDoc getType()
80     {
81         return type;
82     }
83
84     private void assertSQLTypeEstablished()
85     {
86         if (typeInfo == null)
87             throw new IllegalStateException JavaDoc("SQL type not yet established");
88     }
89
90     private int getSQLPrecision()
91     {
92         DatabaseAdapter dba = getStoreManager().getDatabaseAdapter();
93         int sqlPrecision = precision;
94
95         if (getLengthType() == UNLIMITED_LENGTH)
96         {
97             int ulpv = dba.getUnlimitedLengthPrecisionValue(typeInfo);
98
99             if (ulpv > 0)
100                 sqlPrecision = ulpv;
101         }
102
103         /*
104          * Databases like Cloudscape that use BIT types for binary need to
105          * have the length expressed in bits, not bytes.
106          */

107         if (typeInfo.typeName.toLowerCase().startsWith("bit"))
108             return sqlPrecision * 8;
109         else
110             return sqlPrecision;
111     }
112
113
114     /**
115      * Returns a SQL DDL string defining this column as would be included in a
116      * CREATE TABLE statement.
117      *
118      * @return
119      * The SQL DDL defining this column.
120      */

121
122     public String JavaDoc getSQLDefinition()
123     {
124         assertSQLTypeEstablished();
125
126         StringBuffer JavaDoc def = new StringBuffer JavaDoc(name.toString());
127         StringBuffer JavaDoc typeSpec = new StringBuffer JavaDoc(typeInfo.typeName);
128
129         /*
130          * Parse and append createParams to the typeName if it looks like it's
131          * supposed to be appended, i.e. if it contain parentheses and the type
132          * name itself does not.
133          *
134          * createParams is mighty ill-defined by the JDBC spec, but we try to
135          * give our best shot at interpreting it.
136          */

137         if (typeInfo.createParams != null &&
138             typeInfo.createParams.indexOf('(') >= 0 &&
139             typeInfo.typeName.indexOf('(') < 0)
140         {
141             StringTokenizer JavaDoc toks = new StringTokenizer JavaDoc(typeInfo.createParams);
142
143             while (toks.hasMoreTokens())
144             {
145                 String JavaDoc tok = toks.nextToken();
146
147                 if (tok.startsWith("[") && tok.endsWith("]"))
148                 {
149                     /*
150                      * The brackets look like they indicate an optional
151                      * parameter, skip it.
152                      */

153                     continue;
154                 }
155
156                 typeSpec.append(' ').append(tok);
157             }
158         }
159
160         StringBuffer JavaDoc precSpec = new StringBuffer JavaDoc();
161         int sqlPrecision = getSQLPrecision();
162
163         if (sqlPrecision > 0)
164         {
165             precSpec.append(sqlPrecision);
166
167             if (scale > 0)
168                 precSpec.append(",").append(scale);
169         }
170
171         /*
172          * Some databases (like DB2) give you typeNames with ()'s already
173          * present ready for you to insert the values instead of appending them.
174          */

175         int lParenIdx = typeSpec.toString().indexOf('(');
176         int rParenIdx = typeSpec.toString().indexOf(')', lParenIdx);
177
178         if (lParenIdx > 0 && rParenIdx > 0)
179         {
180             if (precSpec.length() > 0)
181                 typeSpec.replace(lParenIdx + 1, rParenIdx, precSpec.toString());
182             else if (rParenIdx == lParenIdx + 1)
183                 throw new ColumnDefinitionException("Invalid precision, column = " + this);
184         }
185         else if (precSpec.length() > 0)
186             typeSpec.append('(').append(precSpec).append(')');
187
188         def.append(" ").append(typeSpec);
189
190         if (!isNullable())
191             def.append(" NOT NULL");
192
193         if (isUnique())
194             def.append(" UNIQUE");
195
196         if (constraints != null)
197             def.append(' ').append(constraints);
198
199         return def.toString();
200     }
201
202
203     /**
204      * Asserts that an existing database column matches, or is effectively
205      * compatible with, the column described by this Column object.
206      *
207      * @param ci
208      * Metadata on the existing column obtained from JDBC.
209      *
210      * @exception SchemaValidationException
211      * If the existing column is not compatible with this Column object.
212      */

213
214     public void validate(ColumnInfo ci)
215     {
216         assertSQLTypeEstablished();
217
218         if (!typeInfo.isCompatibleWith(ci))
219             throw new IncompatibleDataTypeException(this, typeInfo.dataType, ci.dataType);
220
221         if (table instanceof BaseTable)
222         {
223             /*
224              * Only validate precision, scale, and nullability for base tables,
225              * not for views.
226              */

227             int actualPrecision = ci.columnSize;
228             int actualScale = ci.decimalDigits;
229             String JavaDoc actualIsNullable = ci.isNullable;
230
231             int sqlPrecision = getSQLPrecision();
232
233             if (sqlPrecision > 0 && actualPrecision > 0)
234             {
235                 if (sqlPrecision != actualPrecision)
236                     throw new WrongPrecisionException(this, sqlPrecision, actualPrecision);
237             }
238
239             if (scale >= 0 && actualScale >= 0)
240             {
241                 if (scale != actualScale)
242                     throw new WrongScaleException(this, scale, actualScale);
243             }
244
245             if (actualIsNullable.length() > 0)
246             {
247                 switch (Character.toUpperCase(actualIsNullable.charAt(0)))
248                 {
249                     case 'Y':
250                         if (!isNullable())
251                             throw new IsNullableException(this);
252                         break;
253
254                     case 'N':
255                         if (isNullable())
256                             throw new IsNotNullableException(this);
257                         break;
258
259                     default:
260                         break;
261                 }
262             }
263         }
264     }
265
266
267     public void setOptions(ColumnOptions co)
268     {
269         String JavaDoc option = co.getLength();
270
271         if (option != null)
272         {
273             try
274             {
275                 if (option.toLowerCase().equals("unlimited"))
276                     setUnlimitedLength();
277                 else if (option.startsWith("max"))
278                     setMaximumLength(Integer.parseInt(option.substring(3).trim()));
279                 else
280                     setFixedLength(Integer.parseInt(option));
281             }
282             catch (NumberFormatException JavaDoc e)
283             {
284                 throw new ColumnDefinitionException("Invalid length option for field " + toString() + ": " + option);
285             }
286         }
287
288         option = co.getPrecision();
289
290         if (option != null)
291         {
292             try
293             {
294                 if (option.startsWith("min"))
295                     setMinimumPrecision(Integer.parseInt(option.substring(3).trim()));
296                 else
297                     setExactPrecision(Integer.parseInt(option));
298             }
299             catch (NumberFormatException JavaDoc e)
300             {
301                 throw new ColumnDefinitionException("Invalid precision option for field " + toString() + ": " + option);
302             }
303         }
304
305         option = co.getScale();
306
307         if (option != null)
308         {
309             try
310             {
311                 setScale(Integer.parseInt(option));
312             }
313             catch (NumberFormatException JavaDoc e)
314             {
315                 throw new ColumnDefinitionException("Invalid scale option for field " + toString() + ": " + option);
316             }
317         }
318     }
319
320     public final Column setTypeInfo(TypeInfo typeInfo)
321     {
322         this.typeInfo = typeInfo;
323         return this;
324     }
325
326     public final Column setConstraints(String JavaDoc constraints)
327     {
328         this.constraints = constraints;
329         return this;
330     }
331
332     private void setLengthType(int type)
333     {
334         flags &= ~LENGTH_TYPE_MASK;
335         flags |= type;
336     }
337
338     public final Column setFixedLength(int length)
339     {
340         precision = length;
341         setLengthType(FIXED_LENGTH);
342         return this;
343     }
344
345     public final Column setMaximumLength(int length)
346     {
347         precision = length;
348         setLengthType(MAXIMUM_LENGTH);
349         return this;
350     }
351
352     public final Column setUnlimitedLength()
353     {
354         precision = 0;
355         setLengthType(UNLIMITED_LENGTH);
356         return this;
357     }
358
359     public final Column setPrimaryKeyPart()
360     {
361         flags |= PRIMARY_KEY_PART;
362         return this;
363     }
364
365     public final Column setExactPrecision(int precision)
366     {
367         this.precision = precision;
368         flags |= EXACT_PRECISION;
369         return this;
370     }
371
372     public final Column setMinimumPrecision(int precision)
373     {
374         this.precision = precision;
375         flags &= ~EXACT_PRECISION;
376         return this;
377     }
378
379     public final Column setScale(int scale)
380     {
381         this.scale = scale;
382         return this;
383     }
384
385     public final Column setNullable()
386     {
387         flags |= NULLABLE;
388         return this;
389     }
390
391     public final Column setUnique()
392     {
393         flags |= UNIQUE;
394         return this;
395     }
396
397     public final int getPrecision()
398     {
399         return precision;
400     }
401
402     public final int getScale()
403     {
404         return scale;
405     }
406
407     public int getLengthType()
408     {
409         return (flags & LENGTH_TYPE_MASK);
410     }
411
412     public final boolean isPrimaryKeyPart()
413     {
414         return ((flags & PRIMARY_KEY_PART) != 0);
415     }
416
417     public final boolean isExactPrecision()
418     {
419         return ((flags & EXACT_PRECISION) != 0);
420     }
421
422     public final boolean isNullable()
423     {
424         return ((flags & NULLABLE) != 0);
425     }
426
427     public final boolean isUnique()
428     {
429         return ((flags & UNIQUE) != 0);
430     }
431
432     public final void checkPrimitive() throws ColumnDefinitionException
433     {
434         if (getLengthType() != LENGTH_NOT_SET)
435             throw new ColumnDefinitionException("You cannot set a length on this data type, column = " + this);
436
437         if (scale != 0)
438             throw new ColumnDefinitionException("You cannot set a scale on this data type, column = " + this);
439     }
440
441     public final void checkInteger() throws ColumnDefinitionException
442     {
443         if (getLengthType() != LENGTH_NOT_SET)
444             throw new ColumnDefinitionException("You cannot set a length on this data type, column = " + this);
445
446         if (precision <= 0)
447             throw new ColumnDefinitionException("Invalid precision, column = " + this);
448
449         if (scale != 0)
450             throw new ColumnDefinitionException("You cannot set a scale on this data type, column = " + this);
451     }
452
453     public final void checkDecimal() throws ColumnDefinitionException
454     {
455         if (getLengthType() != LENGTH_NOT_SET)
456             throw new ColumnDefinitionException("You cannot set a length on this data type, column = " + this);
457
458         if (precision <= 0)
459             throw new ColumnDefinitionException("Invalid precision, column = " + this);
460
461         if (scale < 0)
462             throw new ColumnDefinitionException("Invalid scale, column = " + this);
463     }
464
465     public final void checkString() throws ColumnDefinitionException
466     {
467         if (getLengthType() == LENGTH_NOT_SET)
468             throw new ColumnDefinitionException("You must set a length on this data type, column = " + this);
469
470         if (precision <= 0 && getLengthType() != UNLIMITED_LENGTH)
471             throw new ColumnDefinitionException("Invalid length, column = " + this);
472
473         if (scale != 0)
474             throw new ColumnDefinitionException("You cannot set a scale on this data type, column = " + this);
475     }
476
477     public boolean equals(Object JavaDoc obj)
478     {
479         if (obj == this)
480             return true;
481
482         if (!(obj instanceof Column))
483             return false;
484
485         Column col = (Column)obj;
486
487         return table.equals(col.table) && name.equals(col.name);
488     }
489
490
491     public int hashCode()
492     {
493         return table.hashCode() ^ name.hashCode();
494     }
495
496     public String JavaDoc toString()
497     {
498         return table.getName() + "." + name;
499     }
500 }
501
Popular Tags