View Javadoc

1   //$Id: Engine.java 351 2008-08-14 20:20:56Z jg_hamburg $
2   /********************************************************************************
3    * DDTUnit, a Datadriven Approach to Unit- and Moduletesting
4    * Copyright (c) 2004, Joerg and Kai Gellien
5    * All rights reserved.
6    *
7    * The Software is provided under the terms of the Common Public License 1.0
8    * as provided with the distribution of DDTUnit in the file cpl-v10.html.
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   *     + Redistributions of source code must retain the above copyright
14   *       notice, this list of conditions and the following disclaimer.
15   *
16   *     + Redistributions in binary form must reproduce the above
17   *       copyright notice, this list of conditions and the following
18   *       disclaimer in the documentation and/or other materials provided
19   *       with the distribution.
20   *
21   *     + Neither the name of the authors or DDTUnit, nor the
22   *       names of its contributors may be used to endorse or promote
23   *       products derived from this software without specific prior
24   *       written permission.
25   *
26   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
30   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   ********************************************************************************/
38  package junitx.ddtunit.data.processing;
39  
40  import java.io.IOException;
41  import java.util.HashMap;
42  import java.util.Map;
43  
44  import junitx.ddtunit.data.DDTTestDataException;
45  import junitx.ddtunit.data.DDTDataRepository;
46  import junitx.ddtunit.data.ExceptionAsserter;
47  import junitx.ddtunit.data.IDataSet;
48  import junitx.ddtunit.data.ObjectAsserter;
49  import junitx.ddtunit.data.TestDataSet;
50  import junitx.ddtunit.data.TestGroupDataSet;
51  
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  import org.xml.sax.Attributes;
55  
56  /**
57   * Class used for creation of a {@link junitx.ddtunit.data.TestClusterDataSet}
58   * by using SAX parsing event information.
59   * 
60   * @author jg
61   */
62  public class Engine {
63      final static int LEVEL_CLUSTER_GLOBAL_OBJ = 4;
64  
65      final static int LEVEL_RESOURCE_OBJ = 3;
66  
67      final static int LEVEL_SINGLE_OBJ = 1;
68  
69      final static int LEVEL_GROUP = 3;
70  
71      final static int LEVEL_GROUP_GLOBAL = 3;
72  
73      final static int LEVEL_TEST = 4;
74  
75      final static int LEVEL_TEST_OBJS = 5;
76  
77      final static int LEVEL_TEST_OBJ = 6;
78  
79      private Logger log = LoggerFactory.getLogger(Engine.class);
80  
81      private IDataSet clusterDataSet;
82  
83      /**
84       * processCluster is set to true if parsing the selected class substructure
85       * of xml resource.
86       */
87      private boolean processCluster = false;
88  
89      private boolean processResource = false;
90  
91      private String clusterId;
92  
93      private String groupId;
94  
95      private IDataSet actualDataSet;
96  
97      private ActionStack actionStack;
98  
99      private boolean cdataProcessing;
100 
101     private ReferenceProcessor refProcessor;
102 
103     /**
104      * Instanciate processing engin to fill provided clusterDataSet
105      * 
106      * @param clusterDataSet to be filled during engine processing
107      */
108     public Engine(IDataSet clusterDataSet) {
109         this.cdataProcessing = false;
110         this.clusterDataSet = clusterDataSet;
111     }
112 
113     /**
114      * Process start of a SAX startElement event by extracting information an
115      * storing it in an {@link ActionStack}
116      * 
117      * @param qname
118      * @param attribs
119      * @param level
120      */
121     public void processStartElement(String qName, Attributes attribs, int level) {
122         Map<String, String> attrMap = getAttrMap(attribs);
123         if (ParserConstants.XML_ELEM_RESOURCES.equals(qName)
124                 && level == (LEVEL_RESOURCE_OBJ - 1)) {
125             this.refProcessor = new ReferenceProcessor(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 " + level
144                     + " 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.get(ParserConstants.XML_ATTR_ID);
163                 String actionAttr = (String) attrMap
164                     .get(ParserConstants.XML_ATTR_ACTION);
165 
166                 action.setObject(new ExceptionAsserter(id, type, actionAttr));
167             } else if (ParserConstants.XML_ELEM_ASSERT.equals(qName)
168                     && level == LEVEL_TEST_OBJ) {
169                 this.actionStack = new ActionStack();
170                 IAction action = pushAction(qName, attrMap);
171                 String type = (String) attrMap
172                     .get(ParserConstants.XML_ATTR_TYPE);
173                 String id = (String) attrMap.get(ParserConstants.XML_ATTR_ID);
174                 String actionAttr = (String) attrMap
175                     .get(ParserConstants.XML_ATTR_ACTION);
176                 action.setObject(new ObjectAsserter(id, type, actionAttr));
177             } else if (ParserConstants.XML_ELEM_TEST.equals(qName)
178                     && level == LEVEL_TEST) {
179                 // initialize temporary methoddataset only once
180                 TestGroupDataSet methodDataSet = (TestGroupDataSet) this.clusterDataSet
181                     .get(groupId);
182                 String testName = (String) attrMap
183                     .get(ParserConstants.XML_ATTR_ID);
184 
185                 this.actualDataSet = new TestDataSet(testName, methodDataSet);
186                 methodDataSet.put(testName, (TestDataSet) actualDataSet);
187                 methodDataSet.addSubKey(testName);
188                 this.refProcessor.setTestId(testName);
189             } else if (ParserConstants.XML_ELEM_GROUP.equals(qName)
190                     && level == LEVEL_GROUP) {
191                 this.groupId = (String) attrMap
192                     .get(ParserConstants.XML_ATTR_ID);
193                 if (this.refProcessor == null) {
194                     this.refProcessor = new ReferenceProcessor(
195                             this.clusterDataSet);
196                 }
197                 this.refProcessor.setGroupId(this.groupId);
198                 this.actualDataSet = new TestGroupDataSet(this.groupId,
199                         this.clusterDataSet);
200                 this.clusterDataSet.put(this.groupId,
201                     (TestGroupDataSet) this.actualDataSet);
202 
203                 // process test tag
204             } else if (!contains(ParserConstants.XML_IGNORE_ELEM, qName)) {
205                 // process generic tag
206                 // check for valid id attribute. If not exist set tag name
207                 pushAction(qName, attrMap);
208             }
209         } else {
210             log.debug("Ignore Start Element <" + qName + "> Level " + level
211                     + " Attrs " + attrMap.size());
212         }
213 
214     }
215 
216     /**
217      * @param qName
218      * @param attrMap
219      */
220     private IAction pushAction(String qName, Map<String, String> attrMap) {
221         IAction action;
222         String id = (String) attrMap.get(ParserConstants.XML_ATTR_ID);
223         if (id == null) {
224             attrMap.put("id", qName);
225         }
226         String hint = (String) attrMap.get(ParserConstants.XML_ATTR_HINT);
227         if (HintTypes.COLLECTION.equals(hint)) {
228             action = push(attrMap, ActionState.COLLECTION_CREATION);
229         } else if (HintTypes.ARRAY.equals(hint)) {
230             action = push(attrMap, ActionState.ARRAY_CREATION);
231         } else if (HintTypes.MAP.equals(hint)) {
232             action = push(attrMap, ActionState.MAP_CREATION);
233         } else if (HintTypes.BEAN.equals(hint)) {
234             action = push(attrMap, ActionState.BEAN_CREATION);
235         } else if (HintTypes.CONSTRUCTOR.equals(hint)
236                 || HintTypes.CALL.equals(hint)) {
237             action = push(attrMap, ActionState.CALL_CREATION);
238         } else if (HintTypes.CONTENT.equals(hint)) {
239             action = push(attrMap, ActionState.CONTENT_CREATION);
240         } else if (HintTypes.CONSTANT.equals(hint)) {
241             action = push(attrMap, ActionState.CONSTANT_CREATION);
242         } else if (HintTypes.DATE.equals(hint)) {
243             action = push(attrMap, ActionState.DATE_CREATION);
244         } else {
245             // no special processing for constructor elements, use
246             // generic
247             action = push(attrMap, ActionState.SUBELEMENT_CREATION);
248         }
249         return action;
250     }
251 
252     /**
253      * @param or
254      */
255     private IAction push(Map attrMap, ActionState processState) {
256         IAction action = ActionFactory.getAction(processState, attrMap);
257         this.actionStack.push(action);
258         action.registerReferenceListener(this.refProcessor);
259         log.debug("push(" + action.toString() + ") - END");
260         return action;
261     }
262 
263     /**
264      * @param attribs
265      * @return
266      */
267     private Map<String, String> getAttrMap(Attributes attribs) {
268         Map<String, String> attrMap = new HashMap<String, String>();
269         for (int count = 0; count < attribs.getLength(); count++) {
270             String key = attribs.getQName(count);
271             String value = attribs.getValue(count);
272             if (ParserConstants.XML_ATTR_TYPE.equals(key)
273                     || ParserConstants.XML_ATTR_VALUETYPE.equals(key)
274                     || ParserConstants.XML_ATTR_KEYTYPE.equals(key)) {
275                 try {
276                     value = TypeAbbreviator.getInstance().resolve(value);
277                 } catch (IOException ex) {
278                     throw new DDTTestDataException(
279                             "Problem using TypeAbbreviator.", ex);
280                 }
281             }
282             attrMap.put(key, value);
283         }
284         if (!attrMap.containsKey(ParserConstants.XML_ATTR_HINT)) {
285             attrMap.put(ParserConstants.XML_ATTR_HINT, HintTypes.FIELDS
286                 .toString());
287         }
288         return attrMap;
289     }
290 
291     /**
292      * Check if searchText is contained in String array
293      * 
294      * @param array to search
295      * @param searchText to look for
296      * @return true if searchText is found in array
297      */
298     private boolean contains(String[] array, String searchText) {
299         boolean found = false;
300         for (int i = 0; i < array.length; i++) {
301             if (array[i].equals(searchText)) {
302                 found = true;
303                 break;
304             }
305         }
306         return found;
307     }
308 
309     /**
310      * Process SAX events of elemet end status
311      * 
312      * @param qName
313      * @param level
314      */
315     public void processEndElement(String qName, long level) {
316         if (processCluster || processResource) {
317             log.debug("process End Element <" + qName + "> Level " + level
318                     + " - START");
319             if ((ParserConstants.XML_ELEM_RESOURCES.equals(qName)
320                     && level == (LEVEL_RESOURCE_OBJ - 1) || (ParserConstants.XML_ELEM_OBJS
321                 .equals(qName) && level == (LEVEL_CLUSTER_GLOBAL_OBJ - 1)))) {
322                 this.refProcessor.resolve();
323             } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
324                     && level == LEVEL_SINGLE_OBJ) {
325                 IAction action = null;
326                 while (!this.actionStack.isEmpty()) {
327                     action = this.actionStack.process();
328                 }
329                 this.clusterDataSet.putObject("singleton", action.getObject());
330                 this.processResource = false;
331             } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
332                     && level == LEVEL_RESOURCE_OBJ) {
333                 IAction action = null;
334                 while (!this.actionStack.isEmpty()) {
335                     action = this.actionStack.process();
336                 }
337                 // select method-test based dataset and store ObjectRecord
338                 DDTDataRepository.getInstance().putObject(action.getId(),
339                     action.getObject());
340                 this.processResource = false;
341             } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
342                     && level == LEVEL_CLUSTER_GLOBAL_OBJ) {
343                 IAction action = null;
344                 while (!this.actionStack.isEmpty()) {
345                     action = this.actionStack.process();
346                 }
347                 // select method-test based dataset and store ObjectRecord
348                 this.clusterDataSet.putObject(action.getId(), action
349                     .getObject());
350             } else if (ParserConstants.XML_ELEM_OBJ.equals(qName)
351                     && level == LEVEL_TEST_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.actualDataSet
358                     .putObject(action.getId(), action.getObject());
359             } else if ((ParserConstants.XML_ELEM_ASSERT.equals(qName) || ParserConstants.XML_ELEM_EXCEPTION
360                 .equals(qName))
361                     && level == LEVEL_TEST_OBJ) {
362                 IAction action = null;
363                 while (!this.actionStack.isEmpty()) {
364                     action = this.actionStack.process();
365                 }
366                 // select method-test based dataset and store ObjectRecords
367                 ((TestDataSet) this.actualDataSet).getAssertMap().put(
368                     action.getId(), action.getObject());
369             } else if (ParserConstants.XML_ELEM_TEST.equals(qName)
370                     && level == LEVEL_TEST && this.processCluster) {
371                 this.refProcessor.resolve();
372                 this.refProcessor.setTestId(null);
373             } else if (ParserConstants.XML_ELEM_GROUP.equals(qName)
374                     && level == LEVEL_GROUP && this.processCluster) {
375                 this.groupId = null;
376                 this.refProcessor.resolve();
377             } else if (ParserConstants.XML_ELEM_CLUSTER.equals(qName)
378                     && this.processCluster) {
379                 this.refProcessor.resolve();
380                 processCluster = false;
381             } else if (!ParserConstants.XML_ELEM_TEST.equals(qName)
382                     && !contains(ParserConstants.XML_IGNORE_ELEM, qName)) {
383                 // process generic tag
384                 log.debug("Process generic tag <" + qName + ">");
385                 this.actionStack.process();
386             }
387         } else {
388             log.debug("Ignore End Element </" + qName + "> Level " + level);
389         }
390         StringBuffer sb = new StringBuffer("process End Element <").append(
391             qName).append("> Level ").append(level).append(" - END");
392         if (this.actionStack != null) {
393             sb.append(", ").append(this.actionStack.infoOf());
394         }
395         log.debug(sb.toString());
396     }
397 
398     /**
399      * Process sax content only if specified cluster tag is processed and an
400      * action stack is allready instanciated. If the trimmed content equals an
401      * empty string or Else ignore event.
402      * 
403      * @param buffer
404      * @param offset
405      * @param length
406      */
407     public void processCharacters(char[] buffer, int offset, int length) {
408         if ((processCluster || processResource) && this.actionStack != null) {
409             StringBuffer sb = new StringBuffer();
410             sb.append(buffer, offset, length);
411             if (!"".equals(sb.toString().trim()) || this.cdataProcessing) {
412                 log.debug("Process content <\"" + sb.toString() + "\">");
413                 Map<String, String> attrMap = new HashMap<String, String>();
414                 attrMap.put(ParserConstants.XML_ATTR_CONTENT, sb.toString());
415                 attrMap.put(ParserConstants.XML_ATTR_ID,
416                     ParserConstants.XML_ATTR_CONTENT);
417                 attrMap.put(ParserConstants.XML_ATTR_HINT,
418                     ParserConstants.XML_ATTR_CONTENT);
419                 attrMap.put(ParserConstants.XML_ATTR_TYPE,
420                     "java.lang.StringBuffer");
421                 attrMap.put(ParserConstants.XML_ATTR_PICDATA, Boolean
422                     .toString(this.cdataProcessing));
423                 pushAction("content", attrMap);
424             }
425         }
426     }
427 
428     public String getClusterId() {
429         return clusterId;
430     }
431 
432     public void setClusterId(String classId) {
433         this.clusterId = classId;
434     }
435 
436     /**
437      * 
438      */
439     public void endCDATA() {
440         this.cdataProcessing = false;
441     }
442 
443     /**
444      * 
445      */
446     public void startCDATA() {
447         this.cdataProcessing = true;
448     }
449 
450 }