001: //$Id: Engine.java 282 2007-07-19 22:46:27Z jg_hamburg $
002: /********************************************************************************
003: * DDTUnit, a Datadriven Approach to Unit- and Moduletesting
004: * Copyright (c) 2004, Joerg and Kai Gellien
005: * All rights reserved.
006: *
007: * The Software is provided under the terms of the Common Public License 1.0
008: * as provided with the distribution of DDTUnit in the file cpl-v10.html.
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * + Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * + Redistributions in binary form must reproduce the above
017: * copyright notice, this list of conditions and the following
018: * disclaimer in the documentation and/or other materials provided
019: * with the distribution.
020: *
021: * + Neither the name of the authors or DDTUnit, nor the
022: * names of its contributors may be used to endorse or promote
023: * products derived from this software without specific prior
024: * written permission.
025: *
026: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
027: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
028: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
029: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
030: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
031: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
032: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
033: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
036: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: ********************************************************************************/package junitx.ddtunit.data.processing;
038:
039: import java.io.IOException;
040: import java.util.HashMap;
041: import java.util.Map;
042:
043: import junitx.ddtunit.data.DDTTestDataException;
044: import junitx.ddtunit.data.DDTDataRepository;
045: import junitx.ddtunit.data.ExceptionAsserter;
046: import junitx.ddtunit.data.IDataSet;
047: import junitx.ddtunit.data.ObjectAsserter;
048: import junitx.ddtunit.data.TestDataSet;
049: import junitx.ddtunit.data.TestGroupDataSet;
050:
051: import org.apache.log4j.Logger;
052: import org.xml.sax.Attributes;
053:
054: /**
055: * Class used for creation of a {@link junitx.ddtunit.data.TestClusterDataSet}
056: * by using SAX parsing event information.
057: *
058: * @author jg
059: */
060: public class Engine {
061: final static int LEVEL_CLUSTER_GLOBAL_OBJ = 4;
062:
063: final static int LEVEL_RESOURCE_OBJ = 3;
064:
065: final static int LEVEL_SINGLE_OBJ = 1;
066:
067: final static int LEVEL_GROUP = 3;
068:
069: final static int LEVEL_GROUP_GLOBAL = 3;
070:
071: final static int LEVEL_TEST = 4;
072:
073: final static int LEVEL_TEST_OBJS = 5;
074:
075: final static int LEVEL_TEST_OBJ = 6;
076:
077: private Logger log = Logger.getLogger(Engine.class);
078:
079: private IDataSet clusterDataSet;
080:
081: /**
082: * processCluster is set to true if parsing the selected class substructure
083: * of xml resource.
084: */
085: private boolean processCluster = false;
086:
087: private boolean processResource = false;
088:
089: private String clusterId;
090:
091: private String groupId;
092:
093: private IDataSet actualDataSet;
094:
095: private ActionStack actionStack;
096:
097: private boolean cdataProcessing;
098:
099: private ReferenceProcessor refProcessor;
100:
101: /**
102: * Instanciate processing engin to fill provided clusterDataSet
103: *
104: * @param clusterDataSet to be filled during engine processing
105: */
106: public Engine(IDataSet clusterDataSet) {
107: this .cdataProcessing = false;
108: this .clusterDataSet = clusterDataSet;
109: }
110:
111: /**
112: * Process start of a SAX startElement event by extracting information an
113: * storing it in an {@link ActionStack}
114: *
115: * @param qname
116: * @param attribs
117: * @param level
118: */
119: public void processStartElement(String qName, Attributes attribs,
120: int level) {
121: Map<String, String> attrMap = getAttrMap(attribs);
122: if (ParserConstants.XML_ELEM_RESOURCES.equals(qName)
123: && level == (LEVEL_RESOURCE_OBJ - 1)) {
124: this .refProcessor = new ReferenceProcessor(
125: this .clusterDataSet);
126: } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
127: && level == LEVEL_SINGLE_OBJ) {
128: this .processCluster = true;
129: this .actionStack = new ActionStack();
130: pushAction(qName, attrMap);
131: } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
132: && level == LEVEL_RESOURCE_OBJ) {
133: this .processResource = true;
134: this .actionStack = new ActionStack();
135: pushAction(qName, attrMap);
136: } else if (ParserConstants.XML_ELEM_CLUSTER.equals(qName)
137: && this .clusterId.equals(attrMap
138: .get(ParserConstants.XML_ATTR_ID))) {
139: log.debug("Found clusterid=" + this .clusterId);
140: processCluster = true;
141: this .actualDataSet = this .clusterDataSet;
142: } else if (processCluster) {
143: log.debug("Process Start Element <" + qName + "> Level "
144: + level + " Attrs " + attrMap.size());
145: // process method tag
146: if (ParserConstants.XML_ELEM_OBJS.equals(qName)
147: && level == (LEVEL_CLUSTER_GLOBAL_OBJ - 1)) {
148: if (this .refProcessor == null) {
149: this .refProcessor = new ReferenceProcessor(
150: this .clusterDataSet);
151: }
152: } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
153: && (level == LEVEL_CLUSTER_GLOBAL_OBJ || level == LEVEL_TEST_OBJ)) {
154: this .actionStack = new ActionStack();
155: pushAction(qName, attrMap);
156: } else if (ParserConstants.XML_ELEM_EXCEPTION.equals(qName)
157: && level == LEVEL_TEST_OBJ) {
158: this .actionStack = new ActionStack();
159: IAction action = pushAction(qName, attrMap);
160: String type = (String) attrMap
161: .get(ParserConstants.XML_ATTR_TYPE);
162: String id = (String) attrMap
163: .get(ParserConstants.XML_ATTR_ID);
164: String actionAttr = (String) attrMap
165: .get(ParserConstants.XML_ATTR_ACTION);
166:
167: action.setObject(new ExceptionAsserter(id, type,
168: actionAttr));
169: } else if (ParserConstants.XML_ELEM_ASSERT.equals(qName)
170: && level == LEVEL_TEST_OBJ) {
171: this .actionStack = new ActionStack();
172: IAction action = pushAction(qName, attrMap);
173: String type = (String) attrMap
174: .get(ParserConstants.XML_ATTR_TYPE);
175: String id = (String) attrMap
176: .get(ParserConstants.XML_ATTR_ID);
177: String actionAttr = (String) attrMap
178: .get(ParserConstants.XML_ATTR_ACTION);
179: action.setObject(new ObjectAsserter(id, type,
180: actionAttr));
181: } else if (ParserConstants.XML_ELEM_TEST.equals(qName)
182: && level == LEVEL_TEST) {
183: // initialize temporary methoddataset only once
184: TestGroupDataSet methodDataSet = (TestGroupDataSet) this .clusterDataSet
185: .get(groupId);
186: String testName = (String) attrMap
187: .get(ParserConstants.XML_ATTR_ID);
188:
189: this .actualDataSet = new TestDataSet(testName,
190: methodDataSet);
191: methodDataSet
192: .put(testName, (TestDataSet) actualDataSet);
193: methodDataSet.addSubKey(testName);
194: this .refProcessor.setTestId(testName);
195: } else if (ParserConstants.XML_ELEM_GROUP.equals(qName)
196: && level == LEVEL_GROUP) {
197: this .groupId = (String) attrMap
198: .get(ParserConstants.XML_ATTR_ID);
199: if (this .refProcessor == null) {
200: this .refProcessor = new ReferenceProcessor(
201: this .clusterDataSet);
202: }
203: this .refProcessor.setGroupId(this .groupId);
204: this .actualDataSet = new TestGroupDataSet(this .groupId,
205: this .clusterDataSet);
206: this .clusterDataSet.put(this .groupId,
207: (TestGroupDataSet) this .actualDataSet);
208:
209: // process test tag
210: } else if (!contains(ParserConstants.XML_IGNORE_ELEM, qName)) {
211: // process generic tag
212: // check for valid id attribute. If not exist set tag name
213: pushAction(qName, attrMap);
214: }
215: } else {
216: log.debug("Ignore Start Element <" + qName + "> Level "
217: + level + " Attrs " + attrMap.size());
218: }
219:
220: }
221:
222: /**
223: * @param qName
224: * @param attrMap
225: */
226: private IAction pushAction(String qName, Map<String, String> attrMap) {
227: IAction action;
228: String id = (String) attrMap.get(ParserConstants.XML_ATTR_ID);
229: if (id == null) {
230: attrMap.put("id", qName);
231: }
232: String hint = (String) attrMap
233: .get(ParserConstants.XML_ATTR_HINT);
234: if (HintTypes.COLLECTION.equals(hint)) {
235: action = push(attrMap, ActionState.COLLECTION_CREATION);
236: } else if (HintTypes.ARRAY.equals(hint)) {
237: action = push(attrMap, ActionState.ARRAY_CREATION);
238: } else if (HintTypes.MAP.equals(hint)) {
239: action = push(attrMap, ActionState.MAP_CREATION);
240: } else if (HintTypes.BEAN.equals(hint)) {
241: action = push(attrMap, ActionState.BEAN_CREATION);
242: } else if (HintTypes.CONSTRUCTOR.equals(hint)
243: || HintTypes.CALL.equals(hint)) {
244: action = push(attrMap, ActionState.CALL_CREATION);
245: } else if (HintTypes.CONTENT.equals(hint)) {
246: action = push(attrMap, ActionState.CONTENT_CREATION);
247: } else if (HintTypes.CONSTANT.equals(hint)) {
248: action = push(attrMap, ActionState.CONSTANT_CREATION);
249: } else if (HintTypes.DATE.equals(hint)) {
250: action = push(attrMap, ActionState.DATE_CREATION);
251: } else {
252: // no special processing for constructor elements, use
253: // generic
254: action = push(attrMap, ActionState.SUBELEMENT_CREATION);
255: }
256: return action;
257: }
258:
259: /**
260: * @param or
261: */
262: private IAction push(Map attrMap, ActionState processState) {
263: IAction action = ActionFactory.getAction(processState, attrMap);
264: this .actionStack.push(action);
265: action.registerReferenceListener(this .refProcessor);
266: log.debug("push(" + action.toString() + ") - END");
267: return action;
268: }
269:
270: /**
271: * @param attribs
272: * @return
273: */
274: private Map<String, String> getAttrMap(Attributes attribs) {
275: Map<String, String> attrMap = new HashMap<String, String>();
276: for (int count = 0; count < attribs.getLength(); count++) {
277: String key = attribs.getQName(count);
278: String value = attribs.getValue(count);
279: if (ParserConstants.XML_ATTR_TYPE.equals(key)
280: || ParserConstants.XML_ATTR_VALUETYPE.equals(key)
281: || ParserConstants.XML_ATTR_KEYTYPE.equals(key)) {
282: try {
283: value = TypeAbbreviator.getInstance()
284: .resolve(value);
285: } catch (IOException ex) {
286: throw new DDTTestDataException(
287: "Problem using TypeAbbreviator.", ex);
288: }
289: }
290: attrMap.put(key, value);
291: }
292: if (!attrMap.containsKey(ParserConstants.XML_ATTR_HINT)) {
293: attrMap.put(ParserConstants.XML_ATTR_HINT, HintTypes.FIELDS
294: .toString());
295: }
296: return attrMap;
297: }
298:
299: /**
300: * Check if searchText is contained in String array
301: *
302: * @param array to search
303: * @param searchText to look for
304: * @return true if searchText is found in array
305: */
306: private boolean contains(String[] array, String searchText) {
307: boolean found = false;
308: for (int i = 0; i < array.length; i++) {
309: if (array[i].equals(searchText)) {
310: found = true;
311: break;
312: }
313: }
314: return found;
315: }
316:
317: /**
318: * Process SAX events of elemet end status
319: *
320: * @param qName
321: * @param level
322: */
323: public void processEndElement(String qName, long level) {
324: if (processCluster || processResource) {
325: log.debug("process End Element <" + qName + "> Level "
326: + level + " - START");
327: if ((ParserConstants.XML_ELEM_RESOURCES.equals(qName)
328: && level == (LEVEL_RESOURCE_OBJ - 1) || (ParserConstants.XML_ELEM_OBJS
329: .equals(qName) && level == (LEVEL_CLUSTER_GLOBAL_OBJ - 1)))) {
330: this .refProcessor.resolve();
331: } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
332: && level == LEVEL_SINGLE_OBJ) {
333: IAction action = null;
334: while (!this .actionStack.isEmpty()) {
335: action = this .actionStack.process();
336: }
337: this .clusterDataSet.putObject("singleton", action
338: .getObject());
339: this .processResource = false;
340: } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
341: && level == LEVEL_RESOURCE_OBJ) {
342: IAction action = null;
343: while (!this .actionStack.isEmpty()) {
344: action = this .actionStack.process();
345: }
346: // select method-test based dataset and store ObjectRecord
347: DDTDataRepository.getInstance().putObject(
348: action.getId(), action.getObject());
349: this .processResource = false;
350: } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
351: && level == LEVEL_CLUSTER_GLOBAL_OBJ) {
352: IAction action = null;
353: while (!this .actionStack.isEmpty()) {
354: action = this .actionStack.process();
355: }
356: // select method-test based dataset and store ObjectRecord
357: this .clusterDataSet.putObject(action.getId(), action
358: .getObject());
359: } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
360: && level == LEVEL_TEST_OBJ) {
361: IAction action = null;
362: while (!this .actionStack.isEmpty()) {
363: action = this .actionStack.process();
364: }
365: // select method-test based dataset and store ObjectRecord
366: this .actualDataSet.putObject(action.getId(), action
367: .getObject());
368: } else if ((ParserConstants.XML_ELEM_ASSERT.equals(qName) || ParserConstants.XML_ELEM_EXCEPTION
369: .equals(qName))
370: && level == LEVEL_TEST_OBJ) {
371: IAction action = null;
372: while (!this .actionStack.isEmpty()) {
373: action = this .actionStack.process();
374: }
375: // select method-test based dataset and store ObjectRecords
376: ((TestDataSet) this .actualDataSet).getAssertMap().put(
377: action.getId(), action.getObject());
378: } else if (ParserConstants.XML_ELEM_TEST.equals(qName)
379: && level == LEVEL_TEST && this .processCluster) {
380: this .refProcessor.resolve();
381: this .refProcessor.setTestId(null);
382: } else if (ParserConstants.XML_ELEM_GROUP.equals(qName)
383: && level == LEVEL_GROUP && this .processCluster) {
384: this .groupId = null;
385: this .refProcessor.resolve();
386: } else if (ParserConstants.XML_ELEM_CLUSTER.equals(qName)
387: && this .processCluster) {
388: this .refProcessor.resolve();
389: processCluster = false;
390: } else if (!ParserConstants.XML_ELEM_TEST.equals(qName)
391: && !contains(ParserConstants.XML_IGNORE_ELEM, qName)) {
392: // process generic tag
393: log.debug("Process generic tag <" + qName + ">");
394: this .actionStack.process();
395: }
396: } else {
397: log.debug("Ignore End Element </" + qName + "> Level "
398: + level);
399: }
400: StringBuffer sb = new StringBuffer("process End Element <")
401: .append(qName).append("> Level ").append(level).append(
402: " - END");
403: if (this .actionStack != null) {
404: sb.append(", ").append(this .actionStack.infoOf());
405: }
406: log.debug(sb.toString());
407: }
408:
409: /**
410: * Process sax content only if specified cluster tag is processed and an
411: * action stack is allready instanciated. If the trimmed content equals an
412: * empty string or Else ignore event.
413: *
414: * @param buffer
415: * @param offset
416: * @param length
417: */
418: public void processCharacters(char[] buffer, int offset, int length) {
419: if ((processCluster || processResource)
420: && this .actionStack != null) {
421: StringBuffer sb = new StringBuffer();
422: sb.append(buffer, offset, length);
423: if (!"".equals(sb.toString().trim())
424: || this .cdataProcessing) {
425: log
426: .debug("Process content <\"" + sb.toString()
427: + "\">");
428: Map<String, String> attrMap = new HashMap<String, String>();
429: attrMap.put(ParserConstants.XML_ATTR_CONTENT, sb
430: .toString());
431: attrMap.put(ParserConstants.XML_ATTR_ID,
432: ParserConstants.XML_ATTR_CONTENT);
433: attrMap.put(ParserConstants.XML_ATTR_HINT,
434: ParserConstants.XML_ATTR_CONTENT);
435: attrMap.put(ParserConstants.XML_ATTR_TYPE,
436: "java.lang.StringBuffer");
437: attrMap.put(ParserConstants.XML_ATTR_PICDATA, Boolean
438: .toString(this .cdataProcessing));
439: pushAction("content", attrMap);
440: }
441: }
442: }
443:
444: public String getClusterId() {
445: return clusterId;
446: }
447:
448: public void setClusterId(String classId) {
449: this .clusterId = classId;
450: }
451:
452: /**
453: *
454: */
455: public void endCDATA() {
456: this .cdataProcessing = false;
457: }
458:
459: /**
460: *
461: */
462: public void startCDATA() {
463: this .cdataProcessing = true;
464: }
465:
466: }
|