View Javadoc

1   //$Id: ActionBase.java 359 2009-08-05 23:54:59Z 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.lang.reflect.Array;
42  import java.lang.reflect.Field;
43  import java.lang.reflect.Method;
44  import java.util.HashMap;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Vector;
48  
49  import junitx.ddtunit.DDTException;
50  import junitx.ddtunit.data.DDTTestDataException;
51  import junitx.ddtunit.data.IDataSet;
52  import junitx.ddtunit.data.TestDataSet;
53  import junitx.ddtunit.data.TypedObject;
54  import junitx.ddtunit.util.ClassAnalyser;
55  
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  
59  /**
60   * Base class for representing objects and assert information created from xml
61   * 
62   * @author jg
63   */
64  abstract class ActionBase implements IAction {
65      protected Logger log = LoggerFactory.getLogger(ActionBase.class);
66  
67      private List<ILinkChangeListener> changeListeners;
68  
69      private List<IReferenceListener> referenceListeners;
70  
71      protected Map<String, String> attrMap;
72  
73      protected TypedObject injectedObject;
74  
75      protected IAction previous;
76  
77      protected IAction next;
78  
79      protected static final String LF = System.getProperty("line.separator");
80  
81      /**
82       * specifies if a successing tag is allready processed.<br/> can be used to
83       * detect empty tags
84       */
85      boolean successorProcessed = false;
86  
87      /**
88       * 
89       * Constructor used as standard constructor to instanciate objects of this
90       * this type
91       * 
92       * @param attrMap provided during scan with all attributes of actual parsing
93       *        action
94       */
95      public ActionBase(Map<String, String> attrMap) {
96          if (this.attrMap == null) {
97              this.attrMap = new HashMap<String, String>();
98          }
99          this.attrMap.putAll(attrMap);
100         this.changeListeners = new Vector<ILinkChangeListener>();
101         this.referenceListeners = new Vector<IReferenceListener>();
102     }
103 
104     public void processNoSuccessor() {
105         // do nothing in default implementation
106     }
107 
108     public void registerLinkChangeListener(ILinkChangeListener listener) {
109         this.changeListeners.add(listener);
110     }
111 
112     public void removeLinkChangeListener(ILinkChangeListener listener) {
113         this.changeListeners.remove(listener);
114     }
115 
116     public void pop() {
117         for (int count = 0; count < this.changeListeners.size(); count++) {
118             ((ILinkChangeListener) this.changeListeners.get(count)).pop();
119         }
120         this.removeFromLinkChangeListener(this);
121     }
122 
123     private void removeFromLinkChangeListener(IAction action) {
124         for (int count = 0; count < this.changeListeners.size(); count++) {
125             action
126                 .removeLinkChangeListener(((ILinkChangeListener) this.changeListeners
127                     .get(count)));
128         }
129     }
130 
131     public void promoteLinkChangeListener(IAction action) {
132         for (int count = 0; count < this.changeListeners.size(); count++) {
133             ActionStack actionStack = (ActionStack) this.changeListeners
134                 .get(count);
135             action.registerLinkChangeListener(actionStack);
136             if (actionStack.last == this) {
137                 actionStack.last = action;
138             }
139         }
140     }
141 
142     public void registerReferenceListener(IReferenceListener listener) {
143         this.referenceListeners.add(listener);
144     }
145 
146     public void add(IReferenceInfo info) {
147         for (int count = 0; count < this.referenceListeners.size(); count++) {
148             ((IReferenceListener) this.referenceListeners.get(count)).add(info);
149         }
150     }
151 
152     public void removeReferenceListener(IReferenceListener listener) {
153         this.referenceListeners.remove(listener);
154     }
155 
156     protected void desintegrate() {
157         this.attrMap.clear();
158         this.injectedObject = null;
159     }
160 
161     public TypedObject getInjectedObject() {
162         return injectedObject;
163     }
164 
165     public String getHint() {
166         String hint = (String) this.attrMap.get(ParserConstants.XML_ATTR_HINT);
167         if (hint == null) {
168             hint = HintTypes.FIELDS.toString();
169         }
170         return hint;
171     }
172 
173     public String getId() {
174         String id = null;
175         if (this.injectedObject != null) {
176             id = this.injectedObject.getId();
177         }
178         return id;
179     }
180 
181     public String getType() {
182         String type = null;
183         if (this.injectedObject != null) {
184             type = this.injectedObject.getType();
185             if (TypedObject.UNKNOWN_TYPE.equals(type)) {
186                 type = getTypeFromRoot();
187                 if (type == null) {
188                     throw new DDTTestDataException(
189                             "Could not specify type of object");
190                 }
191                 this.injectedObject.setType(type);
192             }
193         } else {
194             type = getTypeFromRoot();
195         }
196         return type;
197     }
198 
199     public void setType(String type) {
200         if (this.injectedObject != null) {
201             this.injectedObject.setType(type);
202         }
203     }
204 
205     public Object getValue() {
206         Object obj = null;
207         if (this.injectedObject != null) {
208             obj = this.injectedObject.getValue();
209         }
210         return obj;
211     }
212 
213     public TypedObject getObject() {
214         return this.injectedObject;
215     }
216 
217     public void setObject(TypedObject newObject) {
218         this.injectedObject = newObject;
219     }
220 
221     public void setValue(Object obj) {
222         if (this.injectedObject != null) {
223             this.injectedObject.setValue(obj);
224         }
225     }
226 
227     /**
228      * Extract object type of associated root action on action stack. <br/>If no
229      * root action exists return null
230      * 
231      * @return full qualified class name of root object type or null if no root
232      *         exists.
233      */
234     public String getTypeFromRoot() {
235         String mapEntryType = "junitx.ddtunit.data.processing.MapEntry";
236         log.debug("getTypeFromRoot() - START " + this);
237 
238         String objectType = null;
239         IAction rootAction = this.getPrevious();
240         // field information
241         if (rootAction == null) {
242             throw new DDTException("Corrupt internal Action structure");
243         }
244         // objectType = rootAction.getType();
245         String hintValue = rootAction.getHint();
246         String rootTypeValue = rootAction.getType();
247         if (HintTypes.ATTRLIST.equals(hintValue)) {
248             rootAction = rootAction.getPrevious();
249             hintValue = rootAction.getHint();
250             rootTypeValue = rootAction.getType();
251         }
252         if (HintTypes.INTERNAL_MAPENTRY.equals(hintValue)
253                 || mapEntryType.equals(rootTypeValue)) {
254             rootAction.createObject();
255             if ("key".equals(this.injectedObject.getId())) {
256                 objectType = rootAction
257                     .getAttribute(ParserConstants.XML_ATTR_KEYTYPE);
258             } else if ("value".equals(this.injectedObject.getId())) {
259                 objectType = rootAction
260                     .getAttribute(ParserConstants.XML_ATTR_VALUETYPE);
261             }
262             // objectType = mapEntryType;
263             // this.setAttribute(ParserConstants.XML_ATTR_HINT,
264             // HintTypes.INTERNAL_MAPENTRY.toString());
265             // this.setType(objectType);
266         } else if (HintTypes.MAP.equals(hintValue)) {
267             String keyType = rootAction
268                 .getAttribute(ParserConstants.XML_ATTR_KEYTYPE);
269             String valueType = rootAction
270                 .getAttribute(ParserConstants.XML_ATTR_VALUETYPE);
271             if (keyType != null && !keyType.equals("")) {
272                 this.setAttribute(ParserConstants.XML_ATTR_KEYTYPE, keyType);
273             }
274             if (valueType != null && !valueType.equals("")) {
275                 this
276                     .setAttribute(ParserConstants.XML_ATTR_VALUETYPE, valueType);
277             }
278             objectType = mapEntryType;
279             this.setAttribute(ParserConstants.XML_ATTR_HINT,
280                 HintTypes.INTERNAL_MAPENTRY.toString());
281             this.setType(objectType);
282         } else if (HintTypes.COLLECTION.equals(hintValue)) {
283             objectType = rootAction
284                 .getAttribute(ParserConstants.XML_ATTR_VALUETYPE);
285         } else if (HintTypes.ARRAY.equals(hintValue)) {
286             objectType = rootAction.getType();
287             if (objectType.startsWith("[L")) {
288                 objectType = objectType.substring(2, objectType.length() - 1);
289             }
290         } else if (HintTypes.BEAN.equals(hintValue)){
291         	// specify type from setter Definition of field name
292         	StringBuffer setterName = new StringBuffer("set").append(getId().substring(0,1).toUpperCase())
293         	.append(getId().substring(1));
294         	Method setter = ClassAnalyser.findMethodByName(rootTypeValue, setterName.toString());
295         	objectType = setter.getParameterTypes()[0].getName();
296         	try {
297 				objectType = TypeAbbreviator.getInstance().resolve(objectType);
298 			} catch (IOException e) {
299 				// TODO Auto-generated catch block
300 				e.printStackTrace();
301 			}
302         } else {
303             objectType = rootAction.extractFieldType(getId());
304         }
305         log.debug("getTypeFromRoot() - END (" + objectType + ")");
306         return objectType;
307     }
308 
309     private void setAttribute(String key, String value) {
310         this.attrMap.put(key, value);
311     }
312 
313     /**
314      * Extract field type information from object sitting on the stack. <br/>If
315      * rootAction is a generated container action used for construction of
316      * fields or constructor call of the underlying object then skip the
317      * container action and retrieve requested type from underlying object.
318      * 
319      * @param fieldName to extract type from
320      * @return full type of field
321      */
322     public String extractFieldType(String fieldName) {
323         log.debug("extractFieldType(" + fieldName + ") - START");
324         String fieldType = null;
325         String rootType = null;
326         IAction rootAction = getPrevious();
327         if ("java.util.Vector".equals(getType()) && "attrlist".equals(getId())) {
328             rootType = rootAction.getType();
329         } else {
330             rootType = getType();
331         }
332 
333         if (rootAction != null && TypedObject.UNKNOWN_TYPE.equals(rootType)
334                 && HintTypes.INTERNAL_MAPENTRY.equals(rootAction.getHint())) {
335             fieldType = rootAction.getAttribute(fieldName + "type");
336         } else {
337             Field field = ClassAnalyser.getSelectedField(rootType, fieldName);
338             if (field != null) {
339                 fieldType = field.getType().getName();
340                 // check for primitive type
341                 try {
342                     fieldType = TypeAbbreviator.getInstance()
343                         .resolve(fieldType);
344                 } catch (IOException ex) {
345                     throw new DDTTestDataException(
346                             "could not identify field of type " + fieldType);
347                 }
348             }
349         }
350         log
351             .debug("extractFieldType(" + fieldName + ")=" + fieldType
352                     + " - END");
353         return fieldType;
354     }
355 
356     /**
357      * Create injected object to according type specification on this action.
358      * Just do nothing if object allready instanciated.
359      * 
360      * @return RecordBase
361      * @throws ClassNotFoundException
362      * @throws InstantiationException
363      * @throws IllegalAccessException
364      */
365     public TypedObject createObject() {
366         log.debug("Instanciate TypedObject ...");
367         if (getInjectedObject() == null) {
368             inject();
369         }
370         if (this.getValue() == null) {
371             String recordType = getType();
372             if (TypedObject.UNKNOWN_TYPE.equals(recordType)) {
373                 recordType = getTypeFromRoot();
374             }
375             Class clazz;
376             Object obj = null;
377             try {
378                 clazz = Class.forName(recordType);
379                 if (HintTypes.ARRAY.equals(this.getHint())) {
380                     obj = Array.newInstance(clazz, 1);
381                 } else {
382                     obj = clazz.newInstance();
383                 }
384             } catch (Exception ex) {
385                 throw new DDTException("Error on object creation of class "
386                         + recordType, ex);
387             }
388             setValue(obj);
389             setType(recordType);
390         }
391         return this.getInjectedObject();
392     }
393 
394     public IAction getNext() {
395         return next;
396     }
397 
398     public void setNext(IAction next) {
399         this.next = next;
400     }
401 
402     public IAction getPrevious() {
403         return previous;
404     }
405 
406     public void setPrevious(IAction previous) {
407         this.previous = previous;
408     }
409 
410     /**
411      * Insert a new Record entry after this.
412      * 
413      * @param action to insert
414      */
415     public void insert(IAction action) {
416         IAction nextAction = this.getNext();
417         this.setNext(action);
418         if (nextAction != null) {
419             action.setNext(nextAction);
420             nextAction.setPrevious(action);
421         }
422         action.setPrevious(this);
423         promoteLinkChangeListener(action);
424     }
425 
426     public String toString() {
427         StringBuffer buffer = new StringBuffer();
428         buffer.append("[").append(this.getClass().getName());
429         if (this.injectedObject != null) {
430             buffer.append(" id: ");
431             buffer.append(this.injectedObject.getId());
432             buffer.append(" type: ");
433             buffer.append(this.injectedObject.getType());
434         }
435         buffer.append("]");
436         return buffer.toString();
437     }
438 
439     public String getAttribute(String id) {
440         String attrValue = (String) this.attrMap.get(id);
441         return attrValue;
442     }
443 
444     public boolean hasReferenceInfo() {
445         boolean check = false;
446         String refid = this.getAttribute(ParserConstants.XML_ATTR_REFID);
447         if (refid != null) {
448             check = true;
449         }
450         return check;
451     }
452 
453     class ObjectReferenceInfo extends ReferenceInfoBase {
454 
455         /**
456          * @param source
457          * @param destination
458          */
459         public ObjectReferenceInfo(TypedObject source, TypedObject destination) {
460             super(source, destination);
461         }
462 
463         public void resolve(IDataSet dataSet, String groupId, String testId) {
464             if (ParserConstants.UNKNOWN.equals(groupId)) {
465                 if (ParserConstants.UNKNOWN.equals(testId)) {
466                     TypedObject dest = dataSet.findObject(this.getDestId(),
467                         this.getDestType());
468                     TypedObject source = dataSet.getObject(this.getSourceId(),
469                         this.getSourceType());
470                     if (source == null && dataSet instanceof TestDataSet) {
471                         source = ((TestDataSet) dataSet).getAssert(this
472                             .getSourceId(), this.getSourceType());
473                     }
474                     if (dest != null && source != null) {
475                         source.setValue(dest.getValue());
476                         source.setType(dest.getType());
477                     } else {
478                         throw new DDTTestDataException(
479                                 "Error on processing references on testdata.");
480                     }
481                 } else {
482                     throw new DDTTestDataException(
483                             "No testId expected because groupId is unspecified");
484                 }
485             } else {
486                 if (!ParserConstants.UNKNOWN.equals(testId)) {
487                     IDataSet groupSet = dataSet.get(groupId);
488                     IDataSet testDataSet = null;
489                     if (groupSet != null) {
490                         testDataSet = groupSet.get(testId);
491                     }
492                     TypedObject dest = testDataSet.findObject(this.getDestId(),
493                         this.getDestType());
494                     TypedObject source = testDataSet.getObject(this
495                         .getSourceId(), this.getSourceType());
496                     if (source == null && testDataSet instanceof TestDataSet) {
497                         source = ((TestDataSet) testDataSet).getAssert(this
498                             .getSourceId(), this.getSourceType());
499                     }
500                     if (dest != null && source != null) {
501                         source.setValue(dest.getValue());
502                         source.setType(dest.getType());
503                     } else {
504                         throw new DDTTestDataException(
505                                 "Error on processing references on testdata.");
506                     }
507                 } else {
508                     throw new DDTTestDataException(
509                             "Do not process group data without testId");
510                 }
511             }
512         }
513 
514         /**
515          * If this object is referencing to the provided info object then raise
516          * rank of referenced object info.<br/> A reference from an
517          * ObjectAction to another Action is detected if destination info is
518          * equal to the source info of provided info.
519          * 
520          * @param info to check reference on and raise rank respectivly
521          */
522         public void raiseRankOf(IReferenceInfo info) {
523             if (this.getDestId().equals(info.getSourceId())
524                     && (this.getDestType().equals(info.getSourceType()) || TypedObject.UNKNOWN_TYPE
525                         .equals(info.getDestType()))) {
526                 if (this.getRank() >= info.getRank()) {
527                     info.setRank(this.getRank() + 1);
528                 }
529             }
530         }
531     }
532 
533 }