001: package com.workingdogs.village;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.ByteArrayOutputStream;
023: import java.io.PrintWriter;
024:
025: import java.sql.Connection;
026: import java.sql.ResultSet;
027: import java.sql.ResultSetMetaData;
028: import java.sql.SQLException;
029: import java.sql.Statement;
030:
031: import java.util.Enumeration;
032: import java.util.Hashtable;
033:
034: /**
035: * The Schema object represents the <a href="Column.html">Columns</a> in a database table. It contains a collection of <a
036: * href="Column.html">Column</a> objects.
037: *
038: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
039: * @author John D. McNally
040: * @version $Revision: 568 $
041: */
042: public final class Schema {
043: /** TODO: DOCUMENT ME! */
044: private String tableName;
045:
046: /** TODO: DOCUMENT ME! */
047: private String columnsAttribute;
048:
049: /** TODO: DOCUMENT ME! */
050: private int numberOfColumns;
051:
052: /** TODO: DOCUMENT ME! */
053: private Column[] columns;
054:
055: /** TODO: DOCUMENT ME! */
056: private static Hashtable schemaCache = new Hashtable();
057:
058: /**
059: * This attribute is used to complement columns in the event that this schema represents more than one table. Its keys are
060: * String contains table names and its elements are Hashtables containing columns.
061: */
062: private Hashtable tableHash = null;
063:
064: /** TODO: DOCUMENT ME! */
065: private boolean singleTable = true;
066:
067: /**
068: * A blank Schema object
069: */
070: public Schema() {
071: this .tableName = "";
072: this .columnsAttribute = null;
073: this .numberOfColumns = 0;
074: }
075:
076: /**
077: * Creates a Schema with all columns
078: *
079: * @param conn
080: * @param tableName
081: *
082: * @return an instance of myself
083: *
084: * @exception SQLException
085: * @exception DataSetException
086: */
087: public Schema schema(Connection conn, String tableName)
088: throws SQLException, DataSetException {
089: return schema(conn, tableName, "*");
090: }
091:
092: /**
093: * Creates a Schema with the named columns in the columnsAttribute
094: *
095: * @param conn
096: * @param tableName
097: * @param columnsAttribute
098: *
099: * @return an instance of myself
100: *
101: * @exception SQLException
102: * @exception DataSetException
103: */
104: public synchronized Schema schema(Connection conn,
105: String tableName, String columnsAttribute)
106: throws SQLException, DataSetException {
107: if (columnsAttribute == null) {
108: columnsAttribute = "*";
109: }
110:
111: Statement stmt = null;
112:
113: try {
114: String keyValue = conn.getMetaData().getURL() + tableName;
115: Schema tableSchema = (Schema) schemaCache.get(keyValue);
116:
117: if (tableSchema == null) {
118: String sql = "SELECT " + columnsAttribute + " FROM "
119: + tableName + " WHERE 1 = -1";
120: stmt = conn.createStatement();
121:
122: ResultSet rs = stmt.executeQuery(sql);
123:
124: if (rs != null) {
125: tableSchema = new Schema();
126: tableSchema.setTableName(tableName);
127: tableSchema.setAttributes(columnsAttribute);
128: tableSchema.populate(rs.getMetaData(), tableName);
129: schemaCache.put(keyValue, tableSchema);
130: } else {
131: throw new DataSetException(
132: "Couldn't retrieve schema for " + tableName);
133: }
134: }
135:
136: return tableSchema;
137: } finally {
138: if (stmt != null) {
139: stmt.close();
140: }
141: }
142: }
143:
144: /**
145: * Appends data to the tableName that this schema was first created with.
146: *
147: * <P></p>
148: *
149: * @param app String to append to tableName
150: *
151: * @see TableDataSet#tableQualifier(java.lang.String)
152: */
153: void appendTableName(String app) {
154: this .tableName = this .tableName + " " + app;
155: }
156:
157: /**
158: * List of columns to select from the table
159: *
160: * @return the list of columns to select from the table
161: */
162: public String attributes() {
163: return this .columnsAttribute;
164: }
165:
166: /**
167: * Returns the requested Column object at index i
168: *
169: * @param i
170: *
171: * @return the requested column
172: *
173: * @exception DataSetException
174: */
175: public Column column(int i) throws DataSetException {
176: if (i == 0) {
177: throw new DataSetException("Columns are 1 based");
178: } else if (i > numberOfColumns) {
179: throw new DataSetException("There are only "
180: + numberOfColumns() + " available!");
181: }
182:
183: try {
184: return columns[i];
185: } catch (Exception e) {
186: throw new DataSetException("Column number: "
187: + numberOfColumns() + " does not exist!");
188: }
189: }
190:
191: /**
192: * Returns the requested Column object by name
193: *
194: * @param colName
195: *
196: * @return the requested column
197: *
198: * @exception DataSetException
199: */
200: public Column column(String colName) throws DataSetException {
201: return column(index(colName));
202: }
203:
204: /**
205: * Returns the requested Column object by name
206: *
207: * @param colName
208: *
209: * @return the requested column
210: *
211: * @exception DataSetException
212: */
213: public Column getColumn(String colName) throws DataSetException {
214: int dot = colName.indexOf('.');
215:
216: if (dot > 0) {
217: String table = colName.substring(0, dot);
218: String col = colName.substring(dot + 1);
219:
220: return getColumn(table, col);
221: }
222:
223: return column(index(colName));
224: }
225:
226: /**
227: * Returns the requested Column object belonging to the specified table by name
228: *
229: * @param tableName
230: * @param colName
231: *
232: * @return the requested column, null if a column by the specified name does not exist.
233: *
234: * @exception DataSetException
235: */
236: public Column getColumn(String tableName, String colName)
237: throws DataSetException {
238: return (Column) ((Hashtable) tableHash.get(tableName))
239: .get(colName);
240: }
241:
242: /**
243: * Returns an array of columns
244: *
245: * @return an array of columns
246: */
247: Column[] getColumns() {
248: return this .columns;
249: }
250:
251: /**
252: * returns the table name that this Schema represents
253: *
254: * @return the table name that this Schema represents
255: *
256: * @throws DataSetException TODO: DOCUMENT ME!
257: */
258: public String getTableName() throws DataSetException {
259: if (singleTable) {
260: return tableName;
261: } else {
262: throw new DataSetException(
263: "This schema represents several tables.");
264: }
265: }
266:
267: /**
268: * returns all table names that this Schema represents
269: *
270: * @return the table names that this Schema represents
271: */
272: public String[] getAllTableNames() {
273: Enumeration e = tableHash.keys();
274: String[] tableNames = new String[tableHash.size()];
275:
276: for (int i = 0; e.hasMoreElements(); i++) {
277: tableNames[i] = (String) e.nextElement();
278: }
279:
280: return tableNames;
281: }
282:
283: /**
284: * Gets the index position of a named column. If multiple tables are represented and they have columns with the same name,
285: * this method returns the first one listed, if the table name is not specified.
286: *
287: * @param colName
288: *
289: * @return the requested column index integer
290: *
291: * @exception DataSetException
292: */
293: public int index(String colName) throws DataSetException {
294: int dot = colName.indexOf('.');
295:
296: if (dot > 0) {
297: String table = colName.substring(0, dot);
298: String col = colName.substring(dot + 1);
299:
300: return index(table, col);
301: }
302:
303: for (int i = 1; i <= numberOfColumns(); i++) {
304: if (columns[i].name().equalsIgnoreCase(colName)) {
305: return i;
306: }
307: }
308:
309: throw new DataSetException("Column name: " + colName
310: + " does not exist!");
311: }
312:
313: /**
314: * Gets the index position of a named column.
315: *
316: * @param tableName
317: * @param colName
318: *
319: * @return the requested column index integer
320: *
321: * @exception DataSetException
322: */
323: public int index(String tableName, String colName)
324: throws DataSetException {
325: for (int i = 1; i <= numberOfColumns(); i++) {
326: if (columns[i].name().equalsIgnoreCase(colName)
327: && columns[i].getTableName().equalsIgnoreCase(
328: tableName)) {
329: return i;
330: }
331: }
332:
333: throw new DataSetException("Column name: " + colName
334: + " does not exist!");
335: }
336:
337: /**
338: * Checks to see if this DataSet represents one table in the database.
339: *
340: * @return true if only one table is represented, false otherwise.
341: */
342: public boolean isSingleTable() {
343: return singleTable;
344: }
345:
346: /**
347: * Gets the number of columns in this Schema
348: *
349: * @return integer number of columns
350: */
351: public int numberOfColumns() {
352: return this .numberOfColumns;
353: }
354:
355: /**
356: * Internal method which populates this Schema object with Columns.
357: *
358: * @param meta The meta data of the ResultSet used to build this Schema.
359: * @param tableName The name of the table referenced in this schema, or null if unknown or multiple tables are involved.
360: *
361: * @exception SQLException
362: * @exception DataSetException
363: */
364: void populate(ResultSetMetaData meta, String tableName)
365: throws SQLException, DataSetException {
366: this .numberOfColumns = meta.getColumnCount();
367: columns = new Column[numberOfColumns() + 1];
368:
369: for (int i = 1; i <= numberOfColumns(); i++) {
370: Column col = new Column();
371: col.populate(meta, i, tableName);
372: columns[i] = col;
373:
374: if ((i > 1)
375: && !col.getTableName().equalsIgnoreCase(
376: columns[i - 1].getTableName())) {
377: singleTable = false;
378: }
379: }
380:
381: // Avoid creating a Hashtable in the most common case where only one
382: // table is involved, even though this makes the multiple table case
383: // more expensive because the table/column info is duplicated.
384: if (singleTable) {
385: // If available, use a the caller supplied table name.
386: if ((tableName != null) && (tableName.length() > 0)) {
387: setTableName(tableName);
388: } else {
389: // Since there's only one table involved, attempt to set the
390: // table name to that of the first column. Sybase jConnect
391: // 5.2 and older will fail, in which case we are screwed.
392: try {
393: setTableName(meta.getTableName(1));
394: } catch (Exception e) {
395: setTableName("");
396: }
397: }
398: } else {
399: tableHash = new Hashtable(
400: (int) ((1.25 * numberOfColumns) + 1));
401:
402: for (int i = 1; i <= numberOfColumns(); i++) {
403: if (tableHash.containsKey(columns[i].getTableName())) {
404: ((Hashtable) tableHash.get(columns[i]
405: .getTableName())).put(columns[i].name(),
406: columns[i]);
407: } else {
408: Hashtable columnHash = new Hashtable(
409: (int) ((1.25 * numberOfColumns) + 1));
410: columnHash.put(columns[i].name(), columns[i]);
411: tableHash
412: .put(columns[i].getTableName(), columnHash);
413: }
414: }
415: }
416: }
417:
418: /**
419: * Sets the columns to select from the table
420: *
421: * @param attributes comma separated list of column names
422: */
423: void setAttributes(String attributes) {
424: this .columnsAttribute = attributes;
425: }
426:
427: /**
428: * Sets the table name that this Schema represents
429: *
430: * @param tableName
431: */
432: void setTableName(String tableName) {
433: this .tableName = tableName;
434: }
435:
436: /**
437: * returns the table name that this Schema represents
438: *
439: * @return the table name that this Schema represents
440: *
441: * @throws DataSetException TODO: DOCUMENT ME!
442: */
443: public String tableName() throws DataSetException {
444: return getTableName();
445: }
446:
447: /**
448: * This returns a representation of this Schema
449: *
450: * @return a string
451: */
452: public String toString() {
453: ByteArrayOutputStream bout = new ByteArrayOutputStream();
454: PrintWriter out = new PrintWriter(bout);
455: out.print('{');
456:
457: for (int i = 1; i <= numberOfColumns; i++) {
458: out.print('\'');
459:
460: if (!singleTable) {
461: out.print(columns[i].getTableName() + '.');
462: }
463:
464: out.print(columns[i].name() + '\'');
465:
466: if (i < numberOfColumns) {
467: out.print(',');
468: }
469: }
470:
471: out.print('}');
472: out.flush();
473:
474: return bout.toString();
475: }
476: }
|