001: /*
002: * Copyright 2004 (C) TJDO.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the TJDO License version 1.0.
006: * See the terms of the TJDO License in the documentation provided with this software.
007: *
008: * $Id: Column.java,v 1.6 2004/03/22 04:58:13 jackknifebarber Exp $
009: */
010:
011: package com.triactive.jdo.store;
012:
013: import com.triactive.jdo.model.ColumnOptions;
014: import java.sql.Types;
015: import java.util.StringTokenizer;
016:
017: /**
018: * A column in a database table.
019: * Column objects are used primarily for generating the DDL for a CREATE TABLE
020: * statement or for validating against an existing column in the database.
021: * <p>
022: * All columns are deemed to be serving as backing for a particular Java type.
023: * The Java type and other column attributes are used, usually by a
024: * ColumnMapping, to help determine a suitable corresponding SQL type.
025: * The {@link #setTypeInfo} method must be called at some point to establish the
026: * column's SQL type.
027: *
028: * @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
029: * @version $Revision: 1.6 $
030: */
031:
032: public class Column {
033: public static final int LENGTH_NOT_SET = 0;
034: public static final int FIXED_LENGTH = 1;
035: public static final int MAXIMUM_LENGTH = 2;
036: public static final int UNLIMITED_LENGTH = 3;
037:
038: protected static final int LENGTH_TYPE_MASK = 3;
039: protected static final int PRIMARY_KEY_PART = 4;
040: protected static final int EXACT_PRECISION = 8;
041: protected static final int NULLABLE = 16;
042: protected static final int UNIQUE = 32;
043:
044: protected final Table table;
045: protected final Class type;
046: protected final SQLIdentifier name;
047:
048: protected int precision = 0;
049: protected int scale = 0;
050: protected int flags = 0;
051:
052: protected TypeInfo typeInfo = null;
053: protected String constraints = null;
054:
055: public Column(Table table, Class type, SQLIdentifier name) {
056: this .table = table;
057: this .type = type;
058: this .name = name;
059: }
060:
061: public Table getTable() {
062: return table;
063: }
064:
065: public StoreManager getStoreManager() {
066: return table.getStoreManager();
067: }
068:
069: public SQLIdentifier getName() {
070: return name;
071: }
072:
073: public Class getType() {
074: return type;
075: }
076:
077: private void assertSQLTypeEstablished() {
078: if (typeInfo == null)
079: throw new IllegalStateException(
080: "SQL type not yet established");
081: }
082:
083: private int getSQLPrecision() {
084: DatabaseAdapter dba = getStoreManager().getDatabaseAdapter();
085: int sqlPrecision = precision;
086:
087: if (getLengthType() == UNLIMITED_LENGTH) {
088: int ulpv = dba.getUnlimitedLengthPrecisionValue(typeInfo);
089:
090: if (ulpv > 0)
091: sqlPrecision = ulpv;
092: }
093:
094: /*
095: * Databases like Cloudscape that use BIT types for binary need to
096: * have the length expressed in bits, not bytes.
097: */
098: if (typeInfo.typeName.toLowerCase().startsWith("bit"))
099: return sqlPrecision * 8;
100: else
101: return sqlPrecision;
102: }
103:
104: /**
105: * Returns a SQL DDL string defining this column as would be included in a
106: * CREATE TABLE statement.
107: *
108: * @return
109: * The SQL DDL defining this column.
110: */
111:
112: public String getSQLDefinition() {
113: assertSQLTypeEstablished();
114:
115: StringBuffer def = new StringBuffer(name.toString());
116: StringBuffer typeSpec = new StringBuffer(typeInfo.typeName);
117:
118: /*
119: * Parse and append createParams to the typeName if it looks like it's
120: * supposed to be appended, i.e. if it contain parentheses and the type
121: * name itself does not.
122: *
123: * createParams is mighty ill-defined by the JDBC spec, but we try to
124: * give our best shot at interpreting it.
125: */
126: if (typeInfo.createParams != null
127: && typeInfo.createParams.indexOf('(') >= 0
128: && typeInfo.typeName.indexOf('(') < 0) {
129: StringTokenizer toks = new StringTokenizer(
130: typeInfo.createParams);
131:
132: while (toks.hasMoreTokens()) {
133: String tok = toks.nextToken();
134:
135: if (tok.startsWith("[") && tok.endsWith("]")) {
136: /*
137: * The brackets look like they indicate an optional
138: * parameter, skip it.
139: */
140: continue;
141: }
142:
143: typeSpec.append(' ').append(tok);
144: }
145: }
146:
147: StringBuffer precSpec = new StringBuffer();
148: int sqlPrecision = getSQLPrecision();
149:
150: if (sqlPrecision > 0) {
151: precSpec.append(sqlPrecision);
152:
153: if (scale > 0)
154: precSpec.append(",").append(scale);
155: }
156:
157: /*
158: * Some databases (like DB2) give you typeNames with ()'s already
159: * present ready for you to insert the values instead of appending them.
160: */
161: int lParenIdx = typeSpec.toString().indexOf('(');
162: int rParenIdx = typeSpec.toString().indexOf(')', lParenIdx);
163:
164: if (lParenIdx > 0 && rParenIdx > 0) {
165: if (precSpec.length() > 0)
166: typeSpec.replace(lParenIdx + 1, rParenIdx, precSpec
167: .toString());
168: else if (rParenIdx == lParenIdx + 1)
169: throw new ColumnDefinitionException(
170: "Invalid precision, column = " + this );
171: } else if (precSpec.length() > 0)
172: typeSpec.append('(').append(precSpec).append(')');
173:
174: def.append(" ").append(typeSpec);
175:
176: if (!isNullable())
177: def.append(" NOT NULL");
178:
179: if (isUnique())
180: def.append(" UNIQUE");
181:
182: if (constraints != null)
183: def.append(' ').append(constraints);
184:
185: return def.toString();
186: }
187:
188: /**
189: * Asserts that an existing database column matches, or is effectively
190: * compatible with, the column described by this Column object.
191: *
192: * @param ci
193: * Metadata on the existing column obtained from JDBC.
194: *
195: * @exception SchemaValidationException
196: * If the existing column is not compatible with this Column object.
197: */
198:
199: public void validate(ColumnInfo ci) {
200: assertSQLTypeEstablished();
201:
202: if (!typeInfo.isCompatibleWith(ci))
203: throw new IncompatibleDataTypeException(this ,
204: typeInfo.dataType, ci.dataType);
205:
206: if (table instanceof BaseTable) {
207: /*
208: * Only validate precision, scale, and nullability for base tables,
209: * not for views.
210: */
211: int actualPrecision = ci.columnSize;
212: int actualScale = ci.decimalDigits;
213: String actualIsNullable = ci.isNullable;
214:
215: int sqlPrecision = getSQLPrecision();
216:
217: if (sqlPrecision > 0 && actualPrecision > 0) {
218: if (sqlPrecision != actualPrecision)
219: throw new WrongPrecisionException(this ,
220: sqlPrecision, actualPrecision);
221: }
222:
223: if (scale >= 0 && actualScale >= 0) {
224: if (scale != actualScale)
225: throw new WrongScaleException(this , scale,
226: actualScale);
227: }
228:
229: if (actualIsNullable.length() > 0) {
230: switch (Character.toUpperCase(actualIsNullable
231: .charAt(0))) {
232: case 'Y':
233: if (!isNullable())
234: throw new IsNullableException(this );
235: break;
236:
237: case 'N':
238: if (isNullable())
239: throw new IsNotNullableException(this );
240: break;
241:
242: default:
243: break;
244: }
245: }
246: }
247: }
248:
249: public void setOptions(ColumnOptions co) {
250: String option = co.getLength();
251:
252: if (option != null) {
253: try {
254: if (option.toLowerCase().equals("unlimited"))
255: setUnlimitedLength();
256: else if (option.startsWith("max"))
257: setMaximumLength(Integer.parseInt(option.substring(
258: 3).trim()));
259: else
260: setFixedLength(Integer.parseInt(option));
261: } catch (NumberFormatException e) {
262: throw new ColumnDefinitionException(
263: "Invalid length option for field " + toString()
264: + ": " + option);
265: }
266: }
267:
268: option = co.getPrecision();
269:
270: if (option != null) {
271: try {
272: if (option.startsWith("min"))
273: setMinimumPrecision(Integer.parseInt(option
274: .substring(3).trim()));
275: else
276: setExactPrecision(Integer.parseInt(option));
277: } catch (NumberFormatException e) {
278: throw new ColumnDefinitionException(
279: "Invalid precision option for field "
280: + toString() + ": " + option);
281: }
282: }
283:
284: option = co.getScale();
285:
286: if (option != null) {
287: try {
288: setScale(Integer.parseInt(option));
289: } catch (NumberFormatException e) {
290: throw new ColumnDefinitionException(
291: "Invalid scale option for field " + toString()
292: + ": " + option);
293: }
294: }
295: }
296:
297: public final Column setTypeInfo(TypeInfo typeInfo) {
298: this .typeInfo = typeInfo;
299: return this ;
300: }
301:
302: public final Column setConstraints(String constraints) {
303: this .constraints = constraints;
304: return this ;
305: }
306:
307: private void setLengthType(int type) {
308: flags &= ~LENGTH_TYPE_MASK;
309: flags |= type;
310: }
311:
312: public final Column setFixedLength(int length) {
313: precision = length;
314: setLengthType(FIXED_LENGTH);
315: return this ;
316: }
317:
318: public final Column setMaximumLength(int length) {
319: precision = length;
320: setLengthType(MAXIMUM_LENGTH);
321: return this ;
322: }
323:
324: public final Column setUnlimitedLength() {
325: precision = 0;
326: setLengthType(UNLIMITED_LENGTH);
327: return this ;
328: }
329:
330: public final Column setPrimaryKeyPart() {
331: flags |= PRIMARY_KEY_PART;
332: return this ;
333: }
334:
335: public final Column setExactPrecision(int precision) {
336: this .precision = precision;
337: flags |= EXACT_PRECISION;
338: return this ;
339: }
340:
341: public final Column setMinimumPrecision(int precision) {
342: this .precision = precision;
343: flags &= ~EXACT_PRECISION;
344: return this ;
345: }
346:
347: public final Column setScale(int scale) {
348: this .scale = scale;
349: return this ;
350: }
351:
352: public final Column setNullable() {
353: flags |= NULLABLE;
354: return this ;
355: }
356:
357: public final Column setUnique() {
358: flags |= UNIQUE;
359: return this ;
360: }
361:
362: public final int getPrecision() {
363: return precision;
364: }
365:
366: public final int getScale() {
367: return scale;
368: }
369:
370: public int getLengthType() {
371: return (flags & LENGTH_TYPE_MASK);
372: }
373:
374: public final boolean isPrimaryKeyPart() {
375: return ((flags & PRIMARY_KEY_PART) != 0);
376: }
377:
378: public final boolean isExactPrecision() {
379: return ((flags & EXACT_PRECISION) != 0);
380: }
381:
382: public final boolean isNullable() {
383: return ((flags & NULLABLE) != 0);
384: }
385:
386: public final boolean isUnique() {
387: return ((flags & UNIQUE) != 0);
388: }
389:
390: public final void checkPrimitive() throws ColumnDefinitionException {
391: if (getLengthType() != LENGTH_NOT_SET)
392: throw new ColumnDefinitionException(
393: "You cannot set a length on this data type, column = "
394: + this );
395:
396: if (scale != 0)
397: throw new ColumnDefinitionException(
398: "You cannot set a scale on this data type, column = "
399: + this );
400: }
401:
402: public final void checkInteger() throws ColumnDefinitionException {
403: if (getLengthType() != LENGTH_NOT_SET)
404: throw new ColumnDefinitionException(
405: "You cannot set a length on this data type, column = "
406: + this );
407:
408: if (precision <= 0)
409: throw new ColumnDefinitionException(
410: "Invalid precision, column = " + this );
411:
412: if (scale != 0)
413: throw new ColumnDefinitionException(
414: "You cannot set a scale on this data type, column = "
415: + this );
416: }
417:
418: public final void checkDecimal() throws ColumnDefinitionException {
419: if (getLengthType() != LENGTH_NOT_SET)
420: throw new ColumnDefinitionException(
421: "You cannot set a length on this data type, column = "
422: + this );
423:
424: if (precision <= 0)
425: throw new ColumnDefinitionException(
426: "Invalid precision, column = " + this );
427:
428: if (scale < 0)
429: throw new ColumnDefinitionException(
430: "Invalid scale, column = " + this );
431: }
432:
433: public final void checkString() throws ColumnDefinitionException {
434: if (getLengthType() == LENGTH_NOT_SET)
435: throw new ColumnDefinitionException(
436: "You must set a length on this data type, column = "
437: + this );
438:
439: if (precision <= 0 && getLengthType() != UNLIMITED_LENGTH)
440: throw new ColumnDefinitionException(
441: "Invalid length, column = " + this );
442:
443: if (scale != 0)
444: throw new ColumnDefinitionException(
445: "You cannot set a scale on this data type, column = "
446: + this );
447: }
448:
449: public boolean equals(Object obj) {
450: if (obj == this )
451: return true;
452:
453: if (!(obj instanceof Column))
454: return false;
455:
456: Column col = (Column) obj;
457:
458: return table.equals(col.table) && name.equals(col.name);
459: }
460:
461: public int hashCode() {
462: return table.hashCode() ^ name.hashCode();
463: }
464:
465: public String toString() {
466: return table.getName() + "." + name;
467: }
468: }
|