001: /*
002: * RowData.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.storage;
013:
014: import java.io.IOException;
015: import java.io.InputStream;
016: import java.io.Reader;
017: import java.sql.ResultSet;
018: import java.sql.SQLException;
019: import java.sql.Types;
020:
021: import java.util.List;
022: import workbench.log.LogMgr;
023: import workbench.resource.Settings;
024: import workbench.util.FileUtil;
025: import workbench.util.SqlUtil;
026: import workbench.util.StringUtil;
027:
028: /**
029: * A class to hold the data for a single row retrieved from the database.
030: * It will also save the originally retrieved information in case the
031: * data is changed.
032: * A row can be in three different status:
033: * NEW - the row has not been retrieved from the database (i.e. was created on the client)
034: * MODIFIED - the row has been retrieved but has been changed since then
035: * NOT_MODIFIED - The row has not been changed since it has been retrieved
036: */
037: public class RowData {
038: public static final int NOT_MODIFIED = 0;
039: public static final int MODIFIED = 1;
040: public static final int NEW = 2;
041:
042: private Object NO_CHANGE_MARKER = new Object();
043:
044: private int status = NOT_MODIFIED;
045:
046: /**
047: * This flag will be used by the {@link DataStore}
048: * to store the information for which rows the SQL statements
049: * have been sent to the database during the update process
050: */
051: private boolean dmlSent = false;
052:
053: private boolean trimCharData = false;
054:
055: private Object[] colData;
056: private Object[] originalData;
057: private List<String> dependencyDeletes;
058:
059: public RowData(ResultInfo info) {
060: this (info.getColumnCount());
061: }
062:
063: public RowData(int aColCount) {
064: this .colData = new Object[aColCount];
065: this .setNew();
066: }
067:
068: public void setTrimCharData(boolean flag) {
069: this .trimCharData = flag;
070: }
071:
072: public Object[] getData() {
073: return this .colData;
074: }
075:
076: /**
077: * Read the row data from the supplied ResultSet
078: */
079: public synchronized void read(ResultSet rs, ResultInfo info)
080: throws SQLException {
081: int colCount = this .colData.length;
082: Object value = null;
083: for (int i = 0; i < colCount; i++) {
084: int type = info.getColumnType(i);
085: try {
086: // Not using getObject() for timestamp columns
087: // is a workaround for Oracle, because
088: // it does not return the correct object class
089: // when using getObject() on a TIMESTAMP column
090: // I simply assume that this is working properly
091: // for other JDBC drivers as well.
092: if (type == java.sql.Types.TIMESTAMP) {
093: value = rs.getTimestamp(i + 1);
094: } else if (type == java.sql.Types.DATE) {
095: value = rs.getDate(i + 1);
096: } else if (SqlUtil.isBlobType(type)) {
097: // BLOB columns are always converted bot byte[] internally
098: InputStream in = null;
099: try {
100: in = rs.getBinaryStream(i + 1);
101: if (in != null && !rs.wasNull()) {
102: value = FileUtil.readBytes(in);
103: } else {
104: value = null;
105: }
106: } catch (IOException e) {
107: LogMgr.logError("RowData.read()",
108: "Error retrieving binary data for column '"
109: + info.getColumnName(i) + "'",
110: e);
111: value = null;
112: } finally {
113: try {
114: in.close();
115: } catch (Throwable th) {
116: }
117: }
118: } else if (SqlUtil.isClobType(type)) {
119: // CLOB columns are always converted to String objects internally
120: Reader in = null;
121: try {
122: in = rs.getCharacterStream(i + 1);
123: if (in != null && !rs.wasNull()) {
124: value = FileUtil.readCharacters(in);
125: } else {
126: value = null;
127: }
128: } catch (IOException e) {
129: LogMgr.logWarning("RowData.read()",
130: "Error retrieving data for column '"
131: + info.getColumnName(i) + "'",
132: e);
133: value = rs.getObject(i + 1);
134: }
135: } else {
136: value = rs.getObject(i + 1);
137: if (type == Types.CHAR && trimCharData
138: && value != null) {
139: try {
140: value = StringUtil.rtrim((String) value);
141: } catch (Throwable th) {
142: LogMgr.logError("RowData.read()",
143: "Error trimming CHAR data", th);
144: }
145: }
146: }
147: } catch (SQLException e) {
148: if (Settings.getInstance().getBoolProperty(
149: "workbench.db.ignore.readerror", false)) {
150: value = null;
151: LogMgr.logError("RowData.read()",
152: "Error retrieving data for column '"
153: + info.getColumnName(i)
154: + "'. Using NULL!!", e);
155: } else {
156: throw e;
157: }
158: }
159:
160: this .colData[i] = value;
161: }
162: this .resetStatus();
163: }
164:
165: /**
166: * Create a deep copy of this object.
167: * The status of the new row will be NOT_MODIFIED
168: */
169: public RowData createCopy() {
170: RowData result = new RowData(this .colData.length);
171: for (int i = 0; i < this .colData.length; i++) {
172: result.colData[i] = this .colData[i];
173: }
174: return result;
175: }
176:
177: public int getColumnCount() {
178: if (this .colData == null)
179: return 0;
180: return this .colData.length;
181: }
182:
183: private void createOriginalData() {
184: this .originalData = new Object[this .colData.length];
185: for (int i = 0; i < this .originalData.length; i++) {
186: this .originalData[i] = NO_CHANGE_MARKER;
187: }
188: }
189:
190: /**
191: * Sets the new data for the given column.
192: * After a call isModified() will return true
193: * if the row was not modified before. If the
194: * row is new the status will not change.
195: *
196: * @throws IndexOutOfBoundsException
197: */
198: public synchronized void setValue(int aColIndex, Object aValue)
199: throws IndexOutOfBoundsException {
200: if (!this .isNew()) {
201: Object oldValue = this .colData[aColIndex];
202: if (oldValue != null && oldValue.equals(aValue))
203: return;
204: if (this .originalData == null) {
205: createOriginalData();
206: }
207:
208: if (this .originalData[aColIndex] == NO_CHANGE_MARKER) {
209: this .originalData[aColIndex] = this .colData[aColIndex];
210: }
211: }
212: this .colData[aColIndex] = aValue;
213: this .setModified();
214: }
215:
216: /**
217: * Returns the value for the given column
218: *
219: * @throws IndexOutOfBoundsException
220: */
221: public synchronized Object getValue(int aColumn)
222: throws IndexOutOfBoundsException {
223: return this .colData[aColumn];
224: }
225:
226: /**
227: * Returns the value from the specified column as it was retrieved from
228: * the database
229: */
230: public synchronized Object getOriginalValue(int aColumn)
231: throws IndexOutOfBoundsException {
232: if (this .isColumnModified(aColumn)) {
233: return this .originalData[aColumn];
234: }
235: return this .getValue(aColumn);
236: }
237:
238: public synchronized void restoreOriginalValues() {
239: if (this .originalData == null)
240: return;
241: for (int i = 0; i < this .originalData.length; i++) {
242: if (this .originalData[i] != null) {
243: this .colData[i] = this .originalData[i];
244: }
245: }
246: this .originalData = null;
247: this .resetStatus();
248: }
249:
250: /**
251: * Returns true if the indicated column has been modified since the
252: * initial retrieve (i.e. since the last time resetStatus() was called
253: *
254: */
255: public synchronized boolean isColumnModified(int aColumn) {
256: if (this .isOriginal())
257: return false;
258: if (this .isNew()) {
259: return this .colData[aColumn] != null;
260: } else {
261: if (this .originalData == null)
262: return false;
263: return (this .originalData[aColumn] != NO_CHANGE_MARKER);
264: }
265: }
266:
267: /**
268: * Resets the internal status. After a call to resetStatus()
269: * isModified() will return false, and isOriginal() will return true.
270: */
271: public synchronized void resetStatus() {
272: this .status = NOT_MODIFIED;
273: this .dmlSent = false;
274: this .originalData = null;
275: }
276:
277: /**
278: * Resets data and status
279: */
280: public synchronized void reset() {
281: for (int i = 0; i < this .colData.length; i++) {
282: colData[i] = null;
283: }
284: this .resetStatus();
285: }
286:
287: /**
288: * Sets the status of this row to new.
289: */
290: public synchronized void setNew() {
291: this .status = NEW;
292: }
293:
294: /**
295: * Returns true if the row is neither modified nor is a new row.
296: *
297: * @return true if the row has not been altered since retrieval
298: */
299: public synchronized boolean isOriginal() {
300: return this .status == NOT_MODIFIED;
301: }
302:
303: /**
304: * Check if the row has been modified.
305: *
306: * @return true if the row has been modified since retrieval
307: *
308: */
309: public synchronized boolean isModified() {
310: return (this .status & MODIFIED) == MODIFIED;
311: }
312:
313: /**
314: * Check if the row has been added to the DataStore
315: * after the initial retrieve.
316: *
317: * @return true if it's a new row
318: */
319: public synchronized boolean isNew() {
320: return (this .status & NEW) == NEW;
321: }
322:
323: /**
324: * Set the status to modified.
325: */
326: public synchronized void setModified() {
327: this .status = this .status | MODIFIED;
328: }
329:
330: synchronized void setDmlSent(boolean aFlag) {
331: this .dmlSent = aFlag;
332: }
333:
334: public synchronized boolean isDmlSent() {
335: return this .dmlSent;
336: }
337:
338: public List<String> getDependencyDeletes() {
339: return this .dependencyDeletes;
340: }
341:
342: public void setDependencyDeletes(List<String> statements) {
343: this .dependencyDeletes = statements;
344: }
345:
346: public String toString() {
347: int count = this .colData.length;
348: StringBuilder result = new StringBuilder(count * 20);
349:
350: result.append('{');
351: for (int c = 0; c < count; c++) {
352: result.append('[');
353: result.append(this .getValue(c));
354: result.append(']');
355: }
356: result.append('}');
357: return result.toString();
358: }
359: }
|