001: //$Id: ActionBase.java 250 2006-08-25 21:30:26Z 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.lang.reflect.Array;
041: import java.lang.reflect.Field;
042: import java.util.HashMap;
043: import java.util.List;
044: import java.util.Map;
045: import java.util.Vector;
046:
047: import junitx.ddtunit.DDTException;
048: import junitx.ddtunit.data.DDTTestDataException;
049: import junitx.ddtunit.data.IDataSet;
050: import junitx.ddtunit.data.TestDataSet;
051: import junitx.ddtunit.data.TypedObject;
052: import junitx.ddtunit.util.ClassAnalyser;
053:
054: import org.apache.log4j.Logger;
055:
056: /**
057: * Base class for representing objects and assert information created from xml
058: *
059: * @author jg
060: */
061: abstract class ActionBase implements IAction {
062: protected Logger log = Logger.getLogger(ActionBase.class);
063:
064: private List<ILinkChangeListener> changeListeners;
065:
066: private List<IReferenceListener> referenceListeners;
067:
068: protected Map<String, String> attrMap;
069:
070: protected TypedObject injectedObject;
071:
072: protected IAction previous;
073:
074: protected IAction next;
075:
076: protected static final String LF = System
077: .getProperty("line.separator");
078:
079: /**
080: * specifies if a successing tag is allready processed.<br/> can be used to
081: * detect empty tags
082: */
083: boolean successorProcessed = false;
084:
085: /**
086: *
087: * Constructor used as standard constructor to instanciate objects of this
088: * this type
089: *
090: * @param attrMap provided during scan with all attributes of actual parsing
091: * action
092: */
093: public ActionBase(Map<String, String> attrMap) {
094: if (this .attrMap == null) {
095: this .attrMap = new HashMap<String, String>();
096: }
097: this .attrMap.putAll(attrMap);
098: this .changeListeners = new Vector<ILinkChangeListener>();
099: this .referenceListeners = new Vector<IReferenceListener>();
100: }
101:
102: public void processNoSuccessor() {
103: // do nothing in default implementation
104: }
105:
106: public void registerLinkChangeListener(ILinkChangeListener listener) {
107: this .changeListeners.add(listener);
108: }
109:
110: public void removeLinkChangeListener(ILinkChangeListener listener) {
111: this .changeListeners.remove(listener);
112: }
113:
114: public void pop() {
115: for (int count = 0; count < this .changeListeners.size(); count++) {
116: ((ILinkChangeListener) this .changeListeners.get(count))
117: .pop();
118: }
119: this .removeFromLinkChangeListener(this );
120: }
121:
122: private void removeFromLinkChangeListener(IAction action) {
123: for (int count = 0; count < this .changeListeners.size(); count++) {
124: action
125: .removeLinkChangeListener(((ILinkChangeListener) this .changeListeners
126: .get(count)));
127: }
128: }
129:
130: public void promoteLinkChangeListener(IAction action) {
131: for (int count = 0; count < this .changeListeners.size(); count++) {
132: ActionStack actionStack = (ActionStack) this .changeListeners
133: .get(count);
134: action.registerLinkChangeListener(actionStack);
135: if (actionStack.last == this ) {
136: actionStack.last = action;
137: }
138: }
139: }
140:
141: public void registerReferenceListener(IReferenceListener listener) {
142: this .referenceListeners.add(listener);
143: }
144:
145: public void add(IReferenceInfo info) {
146: for (int count = 0; count < this .referenceListeners.size(); count++) {
147: ((IReferenceListener) this .referenceListeners.get(count))
148: .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
167: .get(ParserConstants.XML_ATTR_HINT);
168: if (hint == null) {
169: hint = HintTypes.FIELDS.toString();
170: }
171: return hint;
172: }
173:
174: public String getId() {
175: String id = null;
176: if (this .injectedObject != null) {
177: id = this .injectedObject.getId();
178: }
179: return id;
180: }
181:
182: public String getType() {
183: String type = null;
184: if (this .injectedObject != null) {
185: type = this .injectedObject.getType();
186: if (TypedObject.UNKNOWN_TYPE.equals(type)) {
187: type = getTypeFromRoot();
188: if (type == null) {
189: throw new DDTTestDataException(
190: "Could not specify type of object");
191: }
192: this .injectedObject.setType(type);
193: }
194: } else {
195: type = getTypeFromRoot();
196: }
197: return type;
198: }
199:
200: public void setType(String type) {
201: if (this .injectedObject != null) {
202: this .injectedObject.setType(type);
203: }
204: }
205:
206: public Object getValue() {
207: Object obj = null;
208: if (this .injectedObject != null) {
209: obj = this .injectedObject.getValue();
210: }
211: return obj;
212: }
213:
214: public TypedObject getObject() {
215: return this .injectedObject;
216: }
217:
218: public void setObject(TypedObject newObject) {
219: this .injectedObject = newObject;
220: }
221:
222: public void setValue(Object obj) {
223: if (this .injectedObject != null) {
224: this .injectedObject.setValue(obj);
225: }
226: }
227:
228: /**
229: * Extract object type of associated root action on action stack. <br/>If no
230: * root action exists return null
231: *
232: * @return full qualified class name of root object type or null if no root
233: * exists.
234: */
235: public String getTypeFromRoot() {
236: String mapEntryType = "junitx.ddtunit.data.processing.MapEntry";
237: log.debug("getTypeFromRoot() - START " + this );
238:
239: String objectType = null;
240: IAction rootAction = this .getPrevious();
241: // field information
242: if (rootAction == null) {
243: throw new DDTException("Corrupt internal Action structure");
244: }
245: // objectType = rootAction.getType();
246: String hintValue = rootAction.getHint();
247: if (HintTypes.ATTRLIST.equals(hintValue)) {
248: rootAction = rootAction.getPrevious();
249: hintValue = rootAction.getHint();
250: }
251: if (HintTypes.INTERNAL_MAPENTRY.equals(hintValue)
252: || mapEntryType.equals(rootAction.getType())) {
253: rootAction.createObject();
254: if ("key".equals(this .injectedObject.getId())) {
255: objectType = rootAction
256: .getAttribute(ParserConstants.XML_ATTR_KEYTYPE);
257: } else if ("value".equals(this .injectedObject.getId())) {
258: objectType = rootAction
259: .getAttribute(ParserConstants.XML_ATTR_VALUETYPE);
260: }
261: // objectType = mapEntryType;
262: // this.setAttribute(ParserConstants.XML_ATTR_HINT,
263: // HintTypes.INTERNAL_MAPENTRY.toString());
264: // this.setType(objectType);
265: } else if (HintTypes.MAP.equals(hintValue)) {
266: String keyType = rootAction
267: .getAttribute(ParserConstants.XML_ATTR_KEYTYPE);
268: String valueType = rootAction
269: .getAttribute(ParserConstants.XML_ATTR_VALUETYPE);
270: if (keyType != null && !keyType.equals("")) {
271: this .setAttribute(ParserConstants.XML_ATTR_KEYTYPE,
272: keyType);
273: }
274: if (valueType != null && !valueType.equals("")) {
275: this .setAttribute(ParserConstants.XML_ATTR_VALUETYPE,
276: 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
289: .length() - 1);
290: }
291: } else {
292: objectType = rootAction.extractFieldType(getId());
293: }
294: log.debug("getTypeFromRoot() - END (" + objectType + ")");
295: return objectType;
296: }
297:
298: private void setAttribute(String key, String value) {
299: this .attrMap.put(key, value);
300: }
301:
302: /**
303: * Extract field type information from object sitting on the stack. <br/>If
304: * rootAction is a generated container action used for construction of
305: * fields or constructor call of the underlying object then skip the
306: * container action and retrieve requested type from underlying object.
307: *
308: * @param fieldName to extract type from
309: * @return full type of field
310: */
311: public String extractFieldType(String fieldName) {
312: log.debug("extractFieldType(" + fieldName + ") - START");
313: String fieldType = null;
314: String rootType = null;
315: IAction rootAction = getPrevious();
316: if ("java.util.Vector".equals(getType())
317: && "attrlist".equals(getId())) {
318: rootType = rootAction.getType();
319: } else {
320: rootType = getType();
321: }
322:
323: if (rootAction != null
324: && TypedObject.UNKNOWN_TYPE.equals(rootType)
325: && HintTypes.INTERNAL_MAPENTRY.equals(rootAction
326: .getHint())) {
327: fieldType = rootAction.getAttribute(fieldName + "type");
328: } else {
329: Field field = ClassAnalyser.getSelectedField(rootType,
330: fieldName);
331: if (field != null) {
332: fieldType = field.getType().getName();
333: // check for primitive type
334: try {
335: fieldType = TypeAbbreviator.getInstance().resolve(
336: fieldType);
337: } catch (IOException ex) {
338: throw new DDTTestDataException(
339: "could not identify field of type "
340: + fieldType);
341: }
342: }
343: }
344: log.debug("extractFieldType(" + fieldName + ")=" + fieldType
345: + " - END");
346: return fieldType;
347: }
348:
349: /**
350: * Create injected object to according type specification on this action.
351: * Just do nothing if object allready instanciated.
352: *
353: * @return RecordBase
354: * @throws ClassNotFoundException
355: * @throws InstantiationException
356: * @throws IllegalAccessException
357: */
358: public TypedObject createObject() {
359: log.debug("Instanciate TypedObject ...");
360: if (getInjectedObject() == null) {
361: inject();
362: }
363: if (this .getValue() == null) {
364: String recordType = getType();
365: if (TypedObject.UNKNOWN_TYPE.equals(recordType)) {
366: recordType = getTypeFromRoot();
367: }
368: Class clazz;
369: Object obj = null;
370: try {
371: clazz = Class.forName(recordType);
372: if (HintTypes.ARRAY.equals(this .getHint())) {
373: obj = Array.newInstance(clazz, 1);
374: } else {
375: obj = clazz.newInstance();
376: }
377: } catch (Exception ex) {
378: throw new DDTException(
379: "Error on object creation of class "
380: + recordType, ex);
381: }
382: setValue(obj);
383: setType(recordType);
384: }
385: return this .getInjectedObject();
386: }
387:
388: public IAction getNext() {
389: return next;
390: }
391:
392: public void setNext(IAction next) {
393: this .next = next;
394: }
395:
396: public IAction getPrevious() {
397: return previous;
398: }
399:
400: public void setPrevious(IAction previous) {
401: this .previous = previous;
402: }
403:
404: /**
405: * Insert a new Record entry after this.
406: *
407: * @param action to insert
408: */
409: public void insert(IAction action) {
410: IAction nextAction = this .getNext();
411: this .setNext(action);
412: if (nextAction != null) {
413: action.setNext(nextAction);
414: nextAction.setPrevious(action);
415: }
416: action.setPrevious(this );
417: promoteLinkChangeListener(action);
418: }
419:
420: public String toString() {
421: StringBuffer buffer = new StringBuffer();
422: buffer.append("[").append(this .getClass().getName());
423: if (this .injectedObject != null) {
424: buffer.append(" id: ");
425: buffer.append(this .injectedObject.getId());
426: buffer.append(" type: ");
427: buffer.append(this .injectedObject.getType());
428: }
429: buffer.append("]");
430: return buffer.toString();
431: }
432:
433: public String getAttribute(String id) {
434: String attrValue = (String) this .attrMap.get(id);
435: return attrValue;
436: }
437:
438: public boolean hasReferenceInfo() {
439: boolean check = false;
440: String refid = this
441: .getAttribute(ParserConstants.XML_ATTR_REFID);
442: if (refid != null) {
443: check = true;
444: }
445: return check;
446: }
447:
448: class ObjectReferenceInfo extends ReferenceInfoBase {
449:
450: /**
451: * @param source
452: * @param destination
453: */
454: public ObjectReferenceInfo(TypedObject source,
455: TypedObject destination) {
456: super (source, destination);
457: }
458:
459: public void resolve(IDataSet dataSet, String groupId,
460: String testId) {
461: if (ParserConstants.UNKNOWN.equals(groupId)) {
462: if (ParserConstants.UNKNOWN.equals(testId)) {
463: TypedObject dest = dataSet.findObject(this
464: .getDestId(), this .getDestType());
465: TypedObject source = dataSet.getObject(this
466: .getSourceId(), this .getSourceType());
467: if (source == null
468: && dataSet instanceof TestDataSet) {
469: source = ((TestDataSet) dataSet).getAssert(this
470: .getSourceId(), this .getSourceType());
471: }
472: if (dest != null && source != null) {
473: source.setValue(dest.getValue());
474: source.setType(dest.getType());
475: } else {
476: throw new DDTTestDataException(
477: "Error on processing references on testdata.");
478: }
479: } else {
480: throw new DDTTestDataException(
481: "No testId expected because groupId is unspecified");
482: }
483: } else {
484: if (!ParserConstants.UNKNOWN.equals(testId)) {
485: IDataSet groupSet = dataSet.get(groupId);
486: IDataSet testDataSet = null;
487: if (groupSet != null) {
488: testDataSet = groupSet.get(testId);
489: }
490: TypedObject dest = testDataSet.findObject(this
491: .getDestId(), this .getDestType());
492: TypedObject source = testDataSet.getObject(this
493: .getSourceId(), this .getSourceType());
494: if (source == null
495: && testDataSet instanceof TestDataSet) {
496: source = ((TestDataSet) testDataSet).getAssert(
497: this .getSourceId(), this
498: .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: }
|