001: /*
002: * $Id: SymbolTable.java,v 1.44 2006/06/25 22:42:32 spal Exp $
003: * $Source: /cvsroot/sqlunit/sqlunit/src/net/sourceforge/sqlunit/SymbolTable.java,v $
004: * SQLUnit - a test harness for unit testing database stored procedures.
005: * Copyright (C) 2003 The SQLUnit Team
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021: package net.sourceforge.sqlunit;
022:
023: import java.util.ArrayList;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028:
029: import net.sourceforge.sqlunit.beans.Col;
030: import net.sourceforge.sqlunit.beans.DatabaseResult;
031: import net.sourceforge.sqlunit.beans.OutParam;
032: import net.sourceforge.sqlunit.beans.ResultSetBean;
033: import net.sourceforge.sqlunit.beans.Row;
034: import net.sourceforge.sqlunit.parsers.ParseException;
035: import net.sourceforge.sqlunit.parsers.SymbolParser;
036:
037: import org.apache.log4j.Logger;
038:
039: /**
040: * Models a HashMap as a symbol table to store temporary variables and their
041: * values that are needed for a SQLUnit test case.
042: * @author Sujit Pal (spal@users.sourceforge.net)
043: * @version $Revision: 1.44 $
044: */
045: public final class SymbolTable {
046:
047: private static final Logger LOG = Logger
048: .getLogger(SymbolTable.class);
049:
050: private static Map symbols = new HashMap();
051:
052: /** Indicates if Java Object Support is enabled */
053: public static final String JAVA_OBJECT_SUPPORT = "${__JavaObjectSupport__}";
054: /** Contains the failure message if available */
055: public static final String FAILURE_MESSAGE_OBJ = "${__FailureMessage__}";
056: /** Contains the debugging logger if set */
057: public static final String DEBUG_LOGGER = "${__debuglogger__}";
058: /** Indicates that the value is a LOB file name */
059: public static final String FILE_HEADER = "${__file:";
060: /** Contains outparam index mapping header */
061: public static final String OUT_PARAM = "${__OutParam:";
062: /** Contains elapsed time for a test in milliseconds */
063: public static final String TEST_ELAPSED_TIME = "${__ElapsedMillisStr__}";
064: /** Keeps track of current resultset */
065: public static final String CURRENT_RESULTSET_KEY = "${__TID.resultset__}";
066: /** Keeps track of current row in resultset */
067: public static final String CURRENT_ROW_KEY = "${__TID.row__}";
068: /** Keeps track of current column in row */
069: public static final String CURRENT_COL_KEY = "${__TID.col__}";
070: /** Holds a reference to the Reporter object */
071: public static final String REPORTER_KEY = "${__Reporter__}";
072: /** Holds a reference to the most recent skipped reason */
073: public static final String SKIP_REASON = "${__SkipReason__}";
074:
075: /**
076: * Private Constructor. Cannot be instantiated.
077: */
078: private SymbolTable() {
079: // private constructor, cannot be instantiated
080: }
081:
082: /**
083: * Returns an object keyed by the param string from the symbol table.
084: * @param param the key into the symbol table.
085: * @return an Object at that position, null if none is available.
086: */
087: public static synchronized Object getObject(final String param) {
088: LOG.debug(">> getObject(" + param + ")");
089: if (SymbolTable.isVariableName(param)) {
090: return symbols.get(param);
091: } else {
092: return param;
093: }
094: }
095:
096: /**
097: * Sets an object keyed by the param string into the symbol table.
098: * @param param the key into the symbol table.
099: * @param obj the object keyed by the param value.
100: */
101: public static synchronized void setObject(final String param,
102: final Object obj) {
103: LOG.debug(">> setObject(" + param + ","
104: + obj.getClass().getName());
105: symbols.put(param, obj);
106: }
107:
108: /**
109: * Returns the value of the named variable. The variable should look
110: * like ${varname}.
111: * @param param the key into the symbol table.
112: * @return the value of the named variable, or null if the variable
113: * does not exist in the symbol table.
114: */
115: public static synchronized String getValue(final String param) {
116: LOG.debug(">> getValue(" + param + ")");
117: Object value = getObject(param);
118: if (isVariableName(param)) {
119: if (value != null) {
120: if (value instanceof String) {
121: return (String) value;
122: } else {
123: return value.toString();
124: }
125: } else {
126: return (String) null;
127: }
128: } else {
129: return param;
130: }
131: }
132:
133: /**
134: * Updates the symbol table with the symbol's value if it exists or
135: * creates a new entry in tha table with the given value if it does not.
136: * @param param the variable name.
137: * @param value the value of the variable.
138: */
139: public static synchronized void setValue(final String param,
140: final String value) {
141: LOG.debug(">> setValue(" + param + "," + value + ")");
142: symbols.put(param, value);
143: }
144:
145: /**
146: * Returns an Iterator containing all the symbols in the Symbol table.
147: * @return an Iterator.
148: */
149: public static synchronized Iterator getSymbols() {
150: LOG.debug(">> getSymbols()");
151: return symbols.keySet().iterator();
152: }
153:
154: /**
155: * Removes a symbol from the symbol table.
156: * @param param the variable name to remove.
157: * @return the value of the variable, may be null if symbol does not exist.
158: */
159: public static synchronized String removeSymbol(final String param) {
160: LOG.debug(">> removeSymbol(" + param + ")");
161: return (String) symbols.remove(param);
162: }
163:
164: /**
165: * Updates the symbol table with variables from a target resultset that
166: * are populated by a SQL or stored procedure call. The variables will
167: * be prefixed by the value of namespace.
168: * @param target the target DatabaseResult containing variables.
169: * @param source the source DatabaseResult generated by a SQL or stored
170: * procedure call.
171: * @param namespace the namespace in which the variables will be stored.
172: * @exception SQLUnitException if there was a problem.
173: */
174: public static synchronized void setSymbols(
175: final DatabaseResult target, final DatabaseResult source,
176: final String namespace) throws SQLUnitException {
177: LOG.debug(">> setSymbols(target,source," + namespace + ")");
178: ResultSetBean[] rsbs = target.getResultSets();
179: for (int i = 0; i < rsbs.length; i++) {
180: Row[] rows = rsbs[i].getRows();
181: for (int j = 0; j < rows.length; j++) {
182: Col[] cols = rows[j].getCols();
183: for (int k = 0; k < cols.length; k++) {
184: String colValue = cols[k].getValue();
185: if (SymbolTable.isVariableName(colValue)) {
186: // we were called from the SetHandler.process()
187: // add in the namespace to the column value
188: if (namespace != null) {
189: colValue = namespace + "." + colValue;
190: colValue = colValue.replaceAll(
191: "\\}\\.\\$\\{", ".");
192: }
193: try {
194: SymbolTable.setValue(colValue, (((source
195: .getResultSets()[i]).getRows()[j])
196: .getCols()[k]).getValue());
197: } catch (ArrayIndexOutOfBoundsException e) {
198: // do not update the variable, it will
199: // show up a NULL value later
200: LOG.warn("No value for " + colValue
201: + " at result[" + (i + 1) + ","
202: + (j + 1) + "," + (k + 1) + "]");
203: }
204: }
205: }
206: }
207: }
208: }
209:
210: /**
211: * Scans the source DatabaseResult and the symbol table and updates
212: * the target DatabaseResult object in place. The client will have
213: * access to the updated target DatabaseResult since it is passed
214: * by reference. The variables to be substituted will need to be
215: * specified in the form ${variable}.
216: * @param source the source DatabaseResult returned from the SQL call.
217: * @param target the target DatabaseResult to update.
218: * @exception SQLUnitException if a variable does not appear in either
219: * the source or the symbol table.
220: */
221: public static synchronized void update(final DatabaseResult target,
222: final DatabaseResult source) throws SQLUnitException {
223: LOG.debug(">> update(target,source)");
224: // update the outparams
225: OutParam[] targetOutParams = target.getOutParams();
226: OutParam[] sourceOutParams = source.getOutParams();
227: for (int i = 0; i < targetOutParams.length; i++) {
228: OutParam targetOutParam = targetOutParams[i];
229: OutParam sourceOutParam = sourceOutParams[i];
230: String targetValue = targetOutParam.getValue();
231: String sourceValue = sourceOutParam.getValue();
232: if (SymbolTable.isVariableName(targetValue)) {
233: targetOutParam.setValue(SymbolTable
234: .getValue(sourceValue));
235: }
236: }
237: // then update the result beans
238: ResultSetBean[] rsbs = target.getResultSets();
239: for (int i = 0; i < rsbs.length; i++) {
240: Row[] rows = rsbs[i].getRows();
241: for (int j = 0; j < rows.length; j++) {
242: Col[] cols = rows[j].getCols();
243: for (int k = 0; k < cols.length; k++) {
244: // is this a variable, if not skip this
245: String colValue = cols[k].getValue();
246: LOG.debug("looking at colValue: " + colValue);
247: if (SymbolTable.isVariableName(colValue)) {
248: boolean isUpdated = false;
249: // replace with value from symbol table (rvalue)
250: String valueToSet = SymbolTable
251: .getValue(colValue);
252: if (valueToSet != null) {
253: LOG.debug("replacing " + colValue
254: + " with " + valueToSet);
255: cols[k].setValue(valueToSet);
256: isUpdated = true;
257: }
258: // go to next one if the value is already set
259: if (isUpdated) {
260: continue;
261: }
262: // replace with value from source object (lvalue)
263: // and update the symbol table in case this needs
264: // to be used later
265: try {
266: valueToSet = (((source.getResultSets()[i])
267: .getRows()[j]).getCols()[k])
268: .getValue();
269: cols[k].setValue(valueToSet);
270: SymbolTable.setValue(colValue, valueToSet);
271: isUpdated = true;
272: } catch (ArrayIndexOutOfBoundsException e) {
273: // skip if the variable specified in the target
274: // does not exist in the source
275: LOG.warn("No value found for " + colValue
276: + " at result[" + (i + 1) + ","
277: + (j + 1) + "," + (k + 1) + "]");
278: continue;
279: }
280: } else {
281: continue;
282: }
283: }
284: }
285: }
286: SymbolTable.dump();
287: }
288:
289: /**
290: * By definition, a string which matches the pattern ${var} is
291: * considered to be a variable name in SQLUnit.
292: * @param str the String to check if it is a symbol.
293: * @return true if the str is a symbol, false if not.
294: */
295: public static boolean isVariableName(final String str) {
296: LOG.debug(">> isVariableName(" + str + ")");
297: if (str == null) {
298: return false;
299: }
300: return (str.startsWith("${") && str.endsWith("}"));
301: }
302:
303: /**
304: * Replaces all variables in a string from the symbol table.
305: * @param text the text with replaceable variables.
306: * @return the text with the variables replaced with the values.
307: * @exception SQLUnitException if one or more variables is not found.
308: */
309: public static synchronized String replaceVariables(final String text)
310: throws SQLUnitException {
311: LOG.debug(">> replaceVariables(" + text + ")");
312: try {
313: return SymbolParser.parse(text, symbols, false);
314: } catch (ParseException e) {
315: throw new SQLUnitException(IErrorCodes.REPLACE_ERROR,
316: new String[] { text, e.getMessage() }, e);
317: }
318: }
319:
320: /**
321: * Sets the current resultset for the test.
322: * @param resultSetId the id of the resultset tag.
323: */
324: public static void setCurrentResultSet(final String resultSetId) {
325: LOG.debug(">> setCurrentResultSet(" + resultSetId + ")");
326: SymbolTable.setValue(CURRENT_RESULTSET_KEY.replaceAll("TID",
327: ThreadIdentifier.getIdentifier()), resultSetId);
328: }
329:
330: /**
331: * Sets the current row for the test.
332: * @param rowId the id of the row tag.
333: */
334: public static void setCurrentRow(final String rowId) {
335: LOG.debug(">> setCurrentRow(" + rowId + ")");
336: SymbolTable.setValue(CURRENT_ROW_KEY.replaceAll("TID",
337: ThreadIdentifier.getIdentifier()), rowId);
338: }
339:
340: /**
341: * Sets the current col for the test.
342: * @param colId the id of the col tag.
343: */
344: public static void setCurrentCol(final String colId) {
345: LOG.debug(">> setCurrentCol(" + colId + ")");
346: SymbolTable.setValue(CURRENT_COL_KEY.replaceAll("TID",
347: ThreadIdentifier.getIdentifier()), colId);
348: }
349:
350: /**
351: * Returns the current result key as a String. The format of the String
352: * is result[resultsetId,rowId,colId].
353: * @return the current result as String.
354: */
355: public static String getCurrentResultKey() {
356: LOG.debug(">> getCurrentResultKey()");
357: StringBuffer buf = new StringBuffer("${result[");
358: String currentThread = ThreadIdentifier.getIdentifier();
359: buf.append(
360: SymbolTable.getValue(CURRENT_RESULTSET_KEY.replaceAll(
361: "TID", currentThread))).append(",").append(
362: SymbolTable.getValue(CURRENT_ROW_KEY.replaceAll("TID",
363: currentThread))).append(",").append(
364: SymbolTable.getValue(CURRENT_COL_KEY.replaceAll("TID",
365: currentThread))).append("]}");
366: return buf.toString();
367: }
368:
369: /**
370: * Removes the references to the user variables set in a test after
371: * all tests are completed.
372: */
373: public static void removeUserVariables() {
374: LOG.debug(">> removeUserVariables()");
375: List userVariables = new ArrayList();
376: for (Iterator it = symbols.keySet().iterator(); it.hasNext();) {
377: Object key = it.next();
378: if (!(key instanceof String)) {
379: continue;
380: }
381: String varName = (String) key;
382: // do not delete internal variables.
383: if (varName.startsWith("${__") && varName.endsWith("__}")) {
384: continue;
385: }
386: // do not delete ant global variables
387: if (varName.startsWith("${ant.")) {
388: continue;
389: }
390: userVariables.add(varName);
391: }
392: for (Iterator it = userVariables.iterator(); it.hasNext();) {
393: symbols.remove((String) it.next());
394: }
395: }
396:
397: /**
398: * Dumps the contents of the symbol table for inspection.
399: */
400: public static void dump() {
401: LOG.debug(">> dump()");
402: Iterator diter = getSymbols();
403: LOG.debug("\nSymbol table dump");
404: LOG.debug("-----------------");
405: while (diter.hasNext()) {
406: String key = (String) diter.next();
407: Object value = symbols.get(key);
408: String strValue = null;
409: if (value != null) {
410: strValue = value.toString();
411: }
412: }
413: LOG.debug("-----------------");
414: return;
415: }
416: }
|