001: /*
002: $Header: /cvsroot/xorm/xorm/src/org/xorm/datastore/Row.java,v 1.11 2003/08/23 01:05:00 wbiggs Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with XORM; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm.datastore;
021:
022: import java.util.Iterator;
023:
024: /**
025: * A row represents a single instance of a data object. The structure
026: * of a Row is defined by its reference to a Table. The terms Row and
027: * Table could be changed to "Data Object" and "Datatype"; they were
028: * chosen to correspond to a relational database notion but are in no
029: * way tied to any particular implementation.
030: *
031: * A Row contains only primitive values; that is, there are no references
032: * to first class objects. When used with a relational database, an
033: * instance of the Row class is meant to correspond exactly with a single
034: * row in a database table. Foreign keys and primary keys are typically
035: * expressed as numeric IDs.
036: *
037: * Each instance of a Row tracks values for each Column specified for a
038: * Table. The column values may or may not have been loaded from
039: * persistent storage. This can be tested for using the containsValue()
040: * method. If the value has been set -- even to null -- containsValue()
041: * will return "true".
042: *
043: * A row also tracks dirty bits for each Column. A value is said to be
044: * dirty if it has been changed since the first time it was set.
045: * DatastoreDriver implementations are encouraged to use this information
046: * to optimize the update() operation.
047: *
048: * Finally, a Row is said to be "hollow" if no values other than the
049: * primary key have been set.
050: *
051: * Because it is possible to have a Table without a primary key
052: * (for example, a many-to-many table in a relational database),
053: * the getPrimaryKeyValue() method is not guaranteed to return a
054: * value.
055: */
056: public class Row implements Cloneable {
057: // The table that contains this row.
058: private Table table;
059:
060: private Object[] values;
061: private long[] dirtyBits; // starts out null -- nothing dirty
062: private boolean hollow;
063: /** Whether this Row instance is the instance that's actually in the
064: cache. This will be used to determine when a clone() is necessary,
065: i.e. if the Row will be modified.
066: */
067: private boolean cached = false;
068:
069: private static final Object NULL_PLACEHOLDER = new Object();
070:
071: /**
072: * Constructs a new, hollow row that references the given Table.
073: */
074: public Row(Table table) {
075: this .table = table;
076: this .values = new Object[table.numColumns];
077: this .hollow = true;
078: }
079:
080: /**
081: * Constructs a new, hollow row with the given primary key.
082: */
083: public Row(Table table, Object primaryKey) {
084: this (table);
085: setValue(table.getPrimaryKey(), primaryKey);
086: }
087:
088: /**
089: * Returns the Table that this Row is conceptually part of.
090: */
091: public Table getTable() {
092: return table;
093: }
094:
095: /**
096: * Tests row equality. Two rows are equal if their entire state
097: * matches. That is, they are both for the same table and each
098: * column configured for their table is equal.
099: */
100: public boolean equals(Object o) {
101: if (this == o)
102: return true;
103: if (o == null || !(o instanceof Row))
104: return false;
105: Row other = (Row) o;
106: if (!(other.table.equals(table)))
107: return false;
108: Iterator i = table.getColumns().iterator();
109: while (i.hasNext()) {
110: Column c = (Column) i.next();
111: // Skip managed columns
112: if (c.managed) {
113: continue;
114: }
115: Object value = getValue(c);
116: Object otherValue = other.getValue(c);
117: if (value == null) {
118: if (otherValue != null) {
119: return false;
120: }
121: } else if (!value.equals(otherValue)) {
122: return false;
123: }
124: }
125: return true;
126: }
127:
128: public String toString() {
129: StringBuffer out = new StringBuffer("{ ");
130: Iterator i = table.getColumns().iterator();
131: while (i.hasNext()) {
132: Column c = (Column) i.next();
133: Object value = getValue(c);
134: out.append(c.getName()).append("=").append(value).append(
135: " ");
136: }
137: return out.append("}").toString();
138: }
139:
140: /**
141: * Creates a clone of this Row's contents. Because a Row
142: * only contains immutable data (Integer, String, etc.)
143: * the contents are merely shallow-cloned.
144: * A cloned Row becomes clean if it wasn't already.
145: */
146: public Object clone() {
147: Row copy = null;
148: try {
149: copy = (Row) super .clone();
150: copy.values = (Object[]) values.clone();
151: copy.dirtyBits = null;
152: } catch (CloneNotSupportedException e) {
153: // can't happen
154: }
155: return copy;
156: }
157:
158: /**
159: * Get the value for the given column. If no value has been set,
160: * returns null. To differentiate between a null value and a
161: * "not present" status, use the containsValue() method.
162: */
163: public Object getValue(Column column) {
164: Object value = values[column.index];
165: if (value == NULL_PLACEHOLDER)
166: return null;
167: return value;
168: }
169:
170: /**
171: * Returns true if the row contains a value for the given column,
172: * even if the value is null.
173: */
174: public boolean containsValue(Column column) {
175: return values[column.index] != null;
176: }
177:
178: /**
179: * Set the value for the given column. Currently this does no
180: * typechecking. If the field is being set for the first time
181: * (i.e., containsValue(column) would return false before this call)
182: * the dirty bit will not be set. Otherwise, if the new value
183: * is different from the old value, the dirty bit will be set.
184: *
185: * @return the new value of the dirty bit for the column
186: */
187: public boolean setValue(Column column, Object value) {
188: if (value == null) {
189: value = NULL_PLACEHOLDER;
190: }
191: Object oldValue = values[column.index];
192: if (oldValue != null) {
193: if (!oldValue.equals(value)) {
194: makeDirty(column);
195: }
196: }
197: values[column.index] = value;
198: if (hollow && (column != table.getPrimaryKey())) {
199: hollow = false;
200: }
201:
202: return isDirty(column);
203: }
204:
205: /**
206: * Explicitly makes a column dirty. Generally this is not
207: * necessary, but it can be used to force an update.
208: */
209: public void makeDirty(Column column) {
210: if (dirtyBits == null) {
211: dirtyBits = new long[1 + values.length / 64];
212: }
213: dirtyBits[column.index / 64] |= 1 << (column.index % 64);
214: }
215:
216: /** Returns true if the specified column value is dirty. */
217: public boolean isDirty(Column column) {
218: return (dirtyBits != null)
219: && ((dirtyBits[column.index / 64] & 1 << (column.index % 64)) != 0);
220: }
221:
222: /** Returns true if any column values are dirty. */
223: public boolean isDirty() {
224: return (dirtyBits != null);
225: }
226:
227: /** Clears all dirty bits. */
228: public void clean() {
229: dirtyBits = null;
230: }
231:
232: /** Returns true if the row is hollow. See the description above. */
233: public boolean isHollow() {
234: return hollow;
235: }
236:
237: /**
238: * Makes the Row hollow by clearing all values except the
239: * primaryKey.
240: */
241: public void makeHollow() {
242: int pkIndex = table.getPrimaryKey().index;
243: for (int i = 0; i < values.length; i++) {
244: if (i != pkIndex) {
245: values[i] = null;
246: }
247: }
248: dirtyBits = null;
249: }
250:
251: /**
252: * Convenience method.
253: * Returns the value of the primary key for this row, or null
254: * if no key has been assigned.
255: */
256: public Object getPrimaryKeyValue() {
257: return getValue(table.getPrimaryKey());
258: }
259:
260: public void setPrimaryKeyValue(Object value) {
261: setValue(table.getPrimaryKey(), value);
262: }
263:
264: /** Is this Row cached? */
265: public synchronized boolean isCached() {
266: return cached;
267: }
268:
269: /** Set the cached flag */
270: public synchronized void setCached(boolean val) {
271: cached = val;
272: }
273: }
|