001: package prefuse.data;
002:
003: import java.util.HashMap;
004:
005: import prefuse.util.PrefuseLib;
006:
007: /**
008: * <p>The Schema class represents a description of a Table's columns, including
009: * column names, data types, and default values. New Table
010: * instances can be created directly from Schema objects through the use of
011: * the {@link #instantiate()} method. If a schema is subsequently changed,
012: * instantiated table instances are not affected, keeping their original
013: * schema.</p>
014: *
015: * <p>Schema instances can be locked to prevent further changes. Any attempt
016: * to alter a locked schema will result in a runtime exception being thrown.
017: * If a schema is not locked, clients are free to add new columns and
018: * edit default values.</p>
019: *
020: * @author <a href="http://jheer.org">jeffrey heer</a>
021: */
022: public class Schema implements Cloneable {
023:
024: private String[] m_names;
025: private Class[] m_types;
026: private Object[] m_dflts;
027: private HashMap m_lookup;
028: private int m_size;
029: private boolean m_locked;
030:
031: // ------------------------------------------------------------------------
032: // Constructors
033:
034: /**
035: * Creates a new empty schema.
036: */
037: public Schema() {
038: this (10);
039: }
040:
041: /**
042: * Creates a new empty schema with a starting capacity for a given number
043: * of columns.
044: * @param ncols the number of columns in this schema
045: */
046: public Schema(int ncols) {
047: m_names = new String[ncols];
048: m_types = new Class[ncols];
049: m_dflts = new Object[ncols];
050: m_size = 0;
051: m_locked = false;
052: }
053:
054: /**
055: * Create a new schema consisting of the given column names and types.
056: * @param names the column names
057: * @param types the column types (as Class instances)
058: */
059: public Schema(String[] names, Class[] types) {
060: this (names.length);
061:
062: // check the schema validity
063: if (names.length != types.length) {
064: throw new IllegalArgumentException(
065: "Input arrays should be the same length");
066: }
067: for (int i = 0; i < names.length; ++i) {
068: addColumn(names[i], types[i], null);
069: }
070: }
071:
072: /**
073: * Create a new schema consisting of the given column names, types, and
074: * default column values.
075: * @param names the column names
076: * @param types the column types (as Class instances)
077: * @param defaults the default values for each column
078: */
079: public Schema(String[] names, Class[] types, Object[] defaults) {
080: this (names.length);
081:
082: // check the schema validity
083: if (names.length != types.length
084: || types.length != defaults.length) {
085: throw new IllegalArgumentException(
086: "Input arrays should be the same length");
087: }
088: for (int i = 0; i < names.length; ++i) {
089: addColumn(names[i], types[i], defaults[i]);
090: }
091: }
092:
093: /**
094: * Creates a copy of this Schema. This might be useful for creating
095: * extended schemas from a shared base schema. Cloned copies
096: * of a locked Schema will not inherit the locked status.
097: * @see java.lang.Object#clone()
098: */
099: public Object clone() {
100: Schema s = new Schema(m_size);
101: for (int i = 0; i < m_size; ++i) {
102: s.addColumn(m_names[i], m_types[i], m_dflts[i]);
103: }
104: return s;
105: }
106:
107: /**
108: * Lazily construct the lookup table for this schema. Used to
109: * accelerate name-based lookups of schema information.
110: */
111: protected void initLookup() {
112: m_lookup = new HashMap();
113: for (int i = 0; i < m_names.length; ++i) {
114: m_lookup.put(m_names[i], new Integer(i));
115: }
116: }
117:
118: // ------------------------------------------------------------------------
119: // Accessors / Mutators
120:
121: /**
122: * Locks the schema, preventing any additional changes. Locked schemas
123: * can not be unlocked! Cloned copies of a locked schema will not inherit
124: * this locked status.
125: * @return a pointer to this schema
126: */
127: public Schema lockSchema() {
128: m_locked = true;
129: return this ;
130: }
131:
132: /**
133: * Indicates if this schema is locked. Locked schemas can not be edited.
134: * @return true if this schema is locked, false otherwise
135: */
136: public boolean isLocked() {
137: return m_locked;
138: }
139:
140: /**
141: * Add a column to this schema.
142: * @param name the column name
143: * @param type the column type (as a Class instance)
144: * @throws IllegalArgumentException is either name or type are null or
145: * the name already exists in this schema.
146: */
147: public void addColumn(String name, Class type) {
148: addColumn(name, type, null);
149: }
150:
151: /**
152: * Add a column to this schema.
153: * @param name the column name
154: * @param type the column type (as a Class instance)
155: * @throws IllegalArgumentException is either name or type are null or
156: * the name already exists in this schema.
157: */
158: public void addColumn(String name, Class type, Object defaultValue) {
159: // check lock status
160: if (m_locked) {
161: throw new IllegalStateException(
162: "Can not add column to a locked Schema.");
163: }
164: // check for validity
165: if (name == null) {
166: throw new IllegalArgumentException(
167: "Null column names are not allowed.");
168: }
169: if (type == null) {
170: throw new IllegalArgumentException(
171: "Null column types are not allowed.");
172: }
173: for (int i = 0; i < m_size; ++i) {
174: if (m_names[i].equals(name)) {
175: throw new IllegalArgumentException(
176: "Duplicate column names are not allowed: "
177: + m_names[i]);
178: }
179: }
180:
181: // resize if necessary
182: // TODO put resizing functionality into library routines?
183: if (m_names.length == m_size) {
184: int capacity = (3 * m_names.length) / 2 + 1;
185: String[] names = new String[capacity];
186: Class[] types = new Class[capacity];
187: Object[] dflts = new Object[capacity];
188: System.arraycopy(m_names, 0, names, 0, m_size);
189: System.arraycopy(m_types, 0, types, 0, m_size);
190: System.arraycopy(m_dflts, 0, dflts, 0, m_size);
191: m_names = names;
192: m_types = types;
193: m_dflts = dflts;
194: }
195:
196: m_names[m_size] = name;
197: m_types[m_size] = type;
198: m_dflts[m_size] = defaultValue;
199:
200: if (m_lookup != null)
201: m_lookup.put(name, new Integer(m_size));
202:
203: ++m_size;
204: }
205:
206: /**
207: * <p>Add a new interpolated column to this data schema. This actually adds
208: * three columns to the schema: a column for the current value of the
209: * field, and columns for starting and ending values. During animation or
210: * operations spread over a time span, the current value can be
211: * interpolated between the start and end values.</p>
212: *
213: * <p>The name for the current value column is the name parameter provided
214: * to the method. The name for the start and end columns will be determined
215: * by the return value of {@link PrefuseLib#getStartField(String)} and
216: * {@link PrefuseLib#getEndField(String)}. The default behavior for these
217: * methods is to append ":start" to the name of a stating value column
218: * and append ":end" to the the name of an ending value column.</p>
219: *
220: * @param name the name of the interpolated column to add
221: * @param type the data type the columns will contain
222: * @param dflt the default value for each of the columns
223: */
224: public void addInterpolatedColumn(String name, Class type,
225: Object dflt) {
226: addColumn(name, type, dflt);
227: addColumn(PrefuseLib.getStartField(name), type, dflt);
228: addColumn(PrefuseLib.getEndField(name), type, dflt);
229: }
230:
231: /**
232: * Add an interpolated column with a null default value.
233: * @see #addInterpolatedColumn(String, Class, Object)
234: * @param name the name of the interpolated column to add
235: * @param type the data type the columns will contain
236: */
237: public void addInterpolatedColumn(String name, Class type) {
238: addInterpolatedColumn(name, type, null);
239: }
240:
241: /**
242: * Get the number of columns in this schema.
243: * @return the number of columns
244: */
245: public int getColumnCount() {
246: return m_size;
247: }
248:
249: /**
250: * The name of the column at the given position.
251: * @param col the column index
252: * @return the column name
253: */
254: public String getColumnName(int col) {
255: return m_names[col];
256: }
257:
258: /**
259: * The column index for the column with the given name.
260: * @param field the column name
261: * @return the column index
262: */
263: public int getColumnIndex(String field) {
264: if (m_lookup == null)
265: initLookup();
266:
267: Integer idx = (Integer) m_lookup.get(field);
268: return (idx == null ? -1 : idx.intValue());
269: }
270:
271: /**
272: * The type of the column at the given position.
273: * @param col the column index
274: * @return the column type
275: */
276: public Class getColumnType(int col) {
277: return m_types[col];
278: }
279:
280: /**
281: * The type of the column with the given name.
282: * @param field the column name
283: * @return the column type
284: */
285: public Class getColumnType(String field) {
286: int idx = getColumnIndex(field);
287: return (idx < 0 ? null : m_types[idx]);
288: }
289:
290: /**
291: * The default value of the column at the given position.
292: * @param col the column index
293: * @return the column's default value
294: */
295: public Object getDefault(int col) {
296: return m_dflts[col];
297: }
298:
299: /**
300: * The default value of the column with the given name.
301: * @param field the column name
302: * @return the column's default value
303: */
304: public Object getDefault(String field) {
305: int idx = getColumnIndex(field);
306: return (idx < 0 ? null : m_dflts[idx]);
307: }
308:
309: /**
310: * Set the default value for the given field.
311: * @param col the column index of the field to set the default for
312: * @param val the new default value
313: */
314: public void setDefault(int col, Object val) {
315: // check lock status
316: if (m_locked) {
317: throw new IllegalStateException(
318: "Can not update default values of a locked Schema.");
319: }
320: m_dflts[col] = val;
321: }
322:
323: /**
324: * Set the default value for the given field.
325: * @param field the name of column to set the default for
326: * @param val the new default value
327: */
328: public void setDefault(String field, Object val) {
329: // check lock status
330: if (m_locked) {
331: throw new IllegalStateException(
332: "Can not update default values of a locked Schema.");
333: }
334: int idx = getColumnIndex(field);
335: m_dflts[idx] = val;
336: }
337:
338: /**
339: * Set the default value for the given field as an int.
340: * @param field the name of column to set the default for
341: * @param val the new default value
342: */
343: public void setDefault(String field, int val) {
344: setDefault(field, new Integer(val));
345: }
346:
347: /**
348: * Set the default value for the given field as a long.
349: * @param field the name of column to set the default for
350: * @param val the new default value
351: */
352: public void setDefault(String field, long val) {
353: setDefault(field, new Long(val));
354: }
355:
356: /**
357: * Set the default value for the given field as a float.
358: * @param field the name of column to set the default for
359: * @param val the new default value
360: */
361: public void setDefault(String field, float val) {
362: setDefault(field, new Float(val));
363: }
364:
365: /**
366: * Set the default value for the given field as a double.
367: * @param field the name of column to set the default for
368: * @param val the new default value
369: */
370: public void setDefault(String field, double val) {
371: setDefault(field, new Double(val));
372: }
373:
374: /**
375: * Set the default value for the given field as a boolean.
376: * @param field the name of column to set the default for
377: * @param val the new default value
378: */
379: public void setDefault(String field, boolean val) {
380: setDefault(field, val ? Boolean.TRUE : Boolean.FALSE);
381: }
382:
383: /**
384: * Set default values for the current, start, and end columns of an
385: * interpolated column.
386: * @param field the field name of the interpolated column
387: * @param val the new default value for all three implicated columns
388: */
389: public void setInterpolatedDefault(String field, Object val) {
390: setDefault(field, val);
391: setDefault(PrefuseLib.getStartField(field), val);
392: setDefault(PrefuseLib.getEndField(field), val);
393: }
394:
395: /**
396: * Set default values for the current, start, and end columns of an
397: * interpolated column as an int.
398: * @param field the field name of the interpolated column
399: * @param val the new default value for all three implicated columns
400: */
401: public void setInterpolatedDefault(String field, int val) {
402: setInterpolatedDefault(field, new Integer(val));
403: }
404:
405: /**
406: * Set default values for the current, start, and end columns of an
407: * interpolated column as a long.
408: * @param field the field name of the interpolated column
409: * @param val the new default value for all three implicated columns
410: */
411: public void setInterpolatedDefault(String field, long val) {
412: setInterpolatedDefault(field, new Long(val));
413: }
414:
415: /**
416: * Set default values for the current, start, and end columns of an
417: * interpolated column as a float.
418: * @param field the field name of the interpolated column
419: * @param val the new default value for all three implicated columns
420: */
421: public void setInterpolatedDefault(String field, float val) {
422: setInterpolatedDefault(field, new Float(val));
423: }
424:
425: /**
426: * Set default values for the current, start, and end columns of an
427: * interpolated column as a double.
428: * @param field the field name of the interpolated column
429: * @param val the new default value for all three implicated columns
430: */
431: public void setInterpolatedDefault(String field, double val) {
432: setInterpolatedDefault(field, new Double(val));
433: }
434:
435: /**
436: * Set default values for the current, start, and end columns of an
437: * interpolated column as a boolean.
438: * @param field the field name of the interpolated column
439: * @param val the new default value for all three implicated columns
440: */
441: public void setInterpolatedDefault(String field, boolean val) {
442: setInterpolatedDefault(field, val ? Boolean.TRUE
443: : Boolean.FALSE);
444: }
445:
446: // ------------------------------------------------------------------------
447: // Comparison and Hashing
448:
449: /**
450: * Compares this schema with another one for equality.
451: */
452: public boolean equals(Object o) {
453: if (!(o instanceof Schema))
454: return false;
455:
456: Schema s = (Schema) o;
457: if (m_size != s.getColumnCount())
458: return false;
459:
460: for (int i = 0; i < m_size; ++i) {
461: if (!(m_names[i].equals(s.getColumnName(i))
462: && m_types[i].equals(s.getColumnType(i)) && m_dflts[i]
463: .equals(s.getDefault(i)))) {
464: return false;
465: }
466: }
467: return true;
468: }
469:
470: /**
471: * Indicates if values from a given Schema can be safely assigned to
472: * data using this Schema. The input Schema must be less than or
473: * equal in length to this Schema, and all contained columns in the
474: * given Schema must have matching names and types to this Schema.
475: * This method does not consider default values settings or the
476: * locked status of the Schemas. For example, if the given Schema
477: * has different default values than this one, this will have no
478: * impact on the assignability of the two.
479: * @param s the input Schema
480: * @return true if data models using this Schema could be assigned values
481: * directly from data using the input Schema, false otherwise.
482: */
483: public boolean isAssignableFrom(Schema s) {
484: int ssize = s.getColumnCount();
485:
486: if (ssize > m_size)
487: return false;
488:
489: for (int i = 0; i < ssize; ++i) {
490: int idx = getColumnIndex(s.getColumnName(i));
491: if (idx < 0)
492: return false;
493:
494: if (!m_types[idx].equals(s.getColumnType(i)))
495: return false;
496: }
497: return true;
498: }
499:
500: /**
501: * Computes a hashcode for this schema.
502: */
503: public int hashCode() {
504: int hashcode = 0;
505: for (int i = 0; i < m_size; ++i) {
506: int idx = i + 1;
507: int code = idx * m_names[i].hashCode();
508: code ^= idx * m_types[i].hashCode();
509: if (m_dflts[i] != null)
510: code ^= m_dflts[i].hashCode();
511: hashcode ^= code;
512: }
513: return hashcode;
514: }
515:
516: /**
517: * Returns a descriptive String for this schema.
518: */
519: public String toString() {
520: StringBuffer sbuf = new StringBuffer();
521: sbuf.append("Schema[");
522: for (int i = 0; i < m_size; ++i) {
523: if (i > 0)
524: sbuf.append(' ');
525: sbuf.append('(').append(m_names[i]).append(", ");
526: sbuf.append(m_types[i].getName()).append(", ");
527: sbuf.append(m_dflts[i]).append(')');
528: }
529: sbuf.append(']');
530: return sbuf.toString();
531: }
532:
533: // ------------------------------------------------------------------------
534: // Table Operations
535:
536: /**
537: * Instantiate this schema as a new Table instance.
538: * @return a new Table with this schema
539: */
540: public Table instantiate() {
541: return instantiate(0);
542: }
543:
544: /**
545: * Instantiate this schema as a new Table instance.
546: * @param nrows the number of starting rows in the table
547: * @return a new Table with this schema
548: */
549: public Table instantiate(int nrows) {
550: Table t = new Table(nrows, m_size);
551: for (int i = 0; i < m_size; ++i) {
552: t.addColumn(m_names[i], m_types[i], m_dflts[i]);
553: }
554: return t;
555: }
556:
557: } // end of class Schema
|