001: // THIS SOFTWARE IS PROVIDED BY SOFTARIS PTY.LTD. AND OTHER METABOSS
002: // CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
003: // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
004: // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTARIS PTY.LTD.
005: // OR OTHER METABOSS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
006: // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
007: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
008: // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
009: // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
010: // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
011: // EVEN IF SOFTARIS PTY.LTD. OR OTHER METABOSS CONTRIBUTORS ARE ADVISED OF THE
012: // POSSIBILITY OF SUCH DAMAGE.
013: //
014: // Copyright 2000-2005 © Softaris Pty.Ltd. All Rights Reserved.
015: package com.metaboss.sdlctools.applications.systemtester;
016:
017: import java.util.ArrayList;
018: import java.util.Iterator;
019: import java.util.List;
020:
021: import org.w3c.dom.Attr;
022: import org.w3c.dom.CDATASection;
023: import org.w3c.dom.Document;
024: import org.w3c.dom.Element;
025: import org.w3c.dom.NamedNodeMap;
026: import org.w3c.dom.Node;
027: import org.w3c.dom.NodeList;
028: import org.w3c.dom.Text;
029:
030: import com.metaboss.util.DOMUtils;
031:
032: /** Utilities used to ease some org.w3c.dom related programming tasks */
033: public class SpecimenComparator {
034: private static final String sXmlsnsNamespace = "http://www.w3.org/2000/xmlns/";
035: private static final String sChildElementMatchTypeAttributeName = "childElementMatchType";
036: private static final String sChildElementMatchTypeValue_preciseMatch = "preciseMatch";
037: private static final String sChildElementMatchTypeValue_unorderedMatch = "unorderedMatch";
038:
039: /** Ensures that each and every element present in specimen document is present in
040: * the sample document with all attributes.
041: * @param pSpecimenDocument - the document we want to test against
042: * @param pSampleDocument - the document being tested
043: * @param pAbortOnFirstError - if this flag is set to true - the first comparison error will stop comparison and this method will
044: * return with only first error text populated. If this flag is false - comparison will run till the very end and all
045: * comparison errors will be reported
046: * @param pTestForRegularExpressions - if equals true - this comparison will atempt to use
047: * values of attributes and text fields in the specimen as regular expressions to match sample value with.
048: * This is only done after straight comparison fails.
049: * @return null if comparison completed successfully or array of strings with error messages if comparison
050: * completed with error. Note that this array may not contain full list of errors if pAbortOnFirstError parameter has been
051: * set to true
052: */
053: public static String[] compareAgainstSpecimen(
054: Document pSpecimenDocument, Document pSampleDocument,
055: boolean pAbortOnFirstError,
056: boolean pTestForRegularExpressions) {
057: List lErrors = new ArrayList();
058: compareAgainstSpecimen(pSpecimenDocument.getDocumentElement(),
059: pSampleDocument.getDocumentElement(),
060: pAbortOnFirstError, pTestForRegularExpressions, lErrors);
061: int lNumberOfErrors = lErrors.size();
062: if (lNumberOfErrors == 0)
063: return null; // Successfull comparison
064: return (String[]) lErrors.toArray(new String[lNumberOfErrors]);
065: }
066:
067: /** Ensures that content of speciment element matches sample element. This includes
068: * element tag name, attributes and sub elements.
069: * @param pSpecimenNode - the element we want to test against
070: * @param pSampleNode - the element being tested
071: * @param pAbortOnFirstError - if this flag is set to true - the first comparison error will stop comparison and this method will
072: * return with only first error text populated. If this flag is false - comparison will run till the very end and all
073: * comparison errors will be reported
074: * @param pTestForRegularExpressions - if equals true - this comparison will atempt to use
075: * values of attributes and text fields in the specimen as regular expressions to match sample value with.
076: * This is only done after straight comparison fails.
077: * @return null if comparison completed successfully or array of strings with error messages if comparison
078: * completed with error. Note that this array may not contain full list of errors if pAbortOnFirstError parameter has been
079: * set to true
080: */
081: public static String[] compareAgainstSpecimen(Node pSpecimenNode,
082: Node pSampleNode, boolean pAbortOnFirstError,
083: boolean pTestForRegularExpressions) {
084: List lErrors = new ArrayList();
085: compareAgainstSpecimen(pSpecimenNode, pSampleNode,
086: pAbortOnFirstError, pTestForRegularExpressions, lErrors);
087: int lNumberOfErrors = lErrors.size();
088: if (lNumberOfErrors == 0)
089: return null; // Successfull comparison
090: return (String[]) lErrors.toArray(new String[lNumberOfErrors]);
091: }
092:
093: // Helper comparison method
094: private static boolean compareAgainstSpecimen(Node pSpecimenNode,
095: Node pSampleNode, boolean pAbortOnFirstError,
096: boolean pTestForRegularExpressions, List pErrorsList) {
097: if (pSpecimenNode == null)
098: return true; // Specimen is null - no need to compare any further
099: // Keep initial error count, so we will be able to find out whether we uad an error or not
100: int lInitialErrorCount = pErrorsList.size();
101: if (pSampleNode == null) {
102: pErrorsList.add(DOMUtils.getAbsoluteNodePath(pSpecimenNode)
103: + ": Corresponding sample node is absent.");
104: return false; // Sample node is absent - have no choice but exit
105: }
106: short lSpecimenNodeType = pSpecimenNode.getNodeType();
107: short lSampleNodeType = pSampleNode.getNodeType();
108: if (lSpecimenNodeType != lSampleNodeType) {
109: pErrorsList
110: .add(DOMUtils.getAbsoluteNodePath(pSpecimenNode)
111: + ": Corresponding sample node has a different type (expecting '"
112: + DOMUtils
113: .getNoteTypeName(lSpecimenNodeType)
114: + "', got '"
115: + DOMUtils.getNoteTypeName(lSampleNodeType)
116: + "')");
117: return false; // Difference is too big to continue - have no choice but exit
118: }
119: String lSpecimenNamespace = pSpecimenNode.getNamespaceURI();
120: String lSampleNamespace = pSampleNode.getNamespaceURI();
121: if (lSpecimenNamespace == null && lSampleNamespace != null) {
122: pErrorsList
123: .add(DOMUtils.getAbsoluteNodePath(pSpecimenNode)
124: + ": Corresponding sample node has a different namespace (expecting default namespace, got '"
125: + lSampleNamespace + "')");
126: if (pAbortOnFirstError)
127: return false; // Different namespaces
128: } else if (lSpecimenNamespace != null
129: && lSampleNamespace == null) {
130: pErrorsList
131: .add(DOMUtils.getAbsoluteNodePath(pSpecimenNode)
132: + ": Corresponding sample node has a different namespace (expecting '"
133: + lSpecimenNamespace
134: + "', got default namespace)");
135: if (pAbortOnFirstError)
136: return false; // Different namespaces
137: } else if (lSpecimenNamespace != null
138: && lSampleNamespace != null
139: && lSpecimenNamespace.equals(lSampleNamespace) == false) {
140: pErrorsList
141: .add(DOMUtils.getAbsoluteNodePath(pSpecimenNode)
142: + ": Corresponding sample node has a different namespace (expecting '"
143: + lSpecimenNamespace + "', got '"
144: + lSampleNamespace + "')");
145: if (pAbortOnFirstError)
146: return false; // Different namespaces
147: }
148: if (lSpecimenNodeType == Node.ELEMENT_NODE) {
149: Element lSpecimenElement = (Element) pSpecimenNode;
150: Element lSampleElement = (Element) pSampleNode;
151: String lSpecimenTagName = lSpecimenElement.getTagName();
152: String lSampleTagName = lSampleElement.getTagName();
153: if (lSpecimenTagName.equals(lSampleTagName) == false) {
154: pErrorsList
155: .add(DOMUtils
156: .getAbsoluteNodePath(pSpecimenNode)
157: + ": Corresponding sample element has a different tag (expecting '"
158: + lSpecimenTagName
159: + "', got '"
160: + lSampleTagName + "')");
161: if (pAbortOnFirstError)
162: return false; // Elements have different tag names
163: }
164: // By default this entity will be looked at as exactMatch
165: String lSpecimenElementType = sChildElementMatchTypeValue_preciseMatch;
166:
167: // Work on attributes
168: NamedNodeMap lSpecimenAttributeNodes = lSpecimenElement
169: .getAttributes();
170: int lSpecimenAttributesCount = lSpecimenAttributeNodes
171: .getLength();
172: for (int i = 0; i < lSpecimenAttributesCount; i++) {
173: Attr lSpecimenAttributeNode = (Attr) lSpecimenAttributeNodes
174: .item(i);
175: // Ignore for comparison purposes and process special specimenType attribute
176: if (lSpecimenAttributeNode.getName().equals(
177: sChildElementMatchTypeAttributeName)) {
178: // Remember the value
179: lSpecimenElementType = lSpecimenAttributeNode
180: .getValue();
181: } else {
182: String lSpecimenAttributeNodeNamespaceURI = lSpecimenAttributeNode
183: .getNamespaceURI();
184: if (lSpecimenAttributeNodeNamespaceURI == null) {
185: if (!compareAgainstSpecimen(
186: lSpecimenAttributeNode,
187: lSampleElement
188: .getAttributeNode(lSpecimenAttributeNode
189: .getName()),
190: pAbortOnFirstError,
191: pTestForRegularExpressions, pErrorsList)) {
192: if (pAbortOnFirstError)
193: return false; // Attribute does not exists or have diferent value in the sample node.
194: }
195: } else {
196: // If namespace is xmlns and the attribute name statrs with xmlns: - skip the comparison
197: // at this does absolutely nothing to content - just declares namespace prefix
198: if (lSpecimenAttributeNodeNamespaceURI
199: .equals(sXmlsnsNamespace)
200: && lSpecimenAttributeNode.getName()
201: .startsWith("xmlns:"))
202: continue; // Skip this abnormal attribute
203: if (!compareAgainstSpecimen(
204: lSpecimenAttributeNode,
205: lSampleElement
206: .getAttributeNodeNS(
207: lSpecimenAttributeNodeNamespaceURI,
208: lSpecimenAttributeNode
209: .getName()),
210: pAbortOnFirstError,
211: pTestForRegularExpressions, pErrorsList)) {
212: if (pAbortOnFirstError)
213: return false; // Attribute does not exists or have diferent value in the sample node.
214: }
215: }
216: }
217: }
218: // Work on child nodes. Iterate through specimen nodes
219: // and for each specimen node try to find matching child node
220: NodeList lSpecimenChildNodes = lSpecimenElement
221: .getChildNodes();
222: int lSpecimenChildrenCount = lSpecimenChildNodes
223: .getLength();
224: int lNextSpecimenChildIndex = 0;
225:
226: NodeList lSampleChildNodes = lSampleElement.getChildNodes();
227: int lSampleChildrenCount = lSampleChildNodes.getLength();
228: int lNextSampleChildIndex = 0;
229: for (; lNextSpecimenChildIndex < lSpecimenChildrenCount; lNextSpecimenChildIndex++) {
230: Node lSpecimenChildNode = lSpecimenChildNodes
231: .item(lNextSpecimenChildIndex);
232: short lSpecimenChildNodeType = lSpecimenChildNode
233: .getNodeType();
234: // Ignore anything but Element, Text and CDATA
235: if (lSpecimenChildNodeType != Node.ELEMENT_NODE
236: && lSpecimenChildNodeType != Node.TEXT_NODE
237: && lSpecimenChildNodeType != Node.CDATA_SECTION_NODE)
238: continue; // All other child elements (i.e things like comments) are ignored
239: if (lSpecimenChildNodeType == Node.TEXT_NODE
240: && ((Text) lSpecimenChildNode).getData().trim()
241: .length() == 0)
242: continue; // Ignore white space
243: // Now look through sample element child list looking for the match
244: boolean lFoundMatchingSampleChildNode = false;
245: // In case we are not after precise order of elements we need to set search index back to zero every time
246: List lTempErrorsList = new ArrayList();
247: if (lSpecimenElementType
248: .equals(sChildElementMatchTypeValue_unorderedMatch))
249: lNextSampleChildIndex = 0;
250: for (; lFoundMatchingSampleChildNode == false
251: && lNextSampleChildIndex < lSampleChildrenCount; lNextSampleChildIndex++) {
252: Node lSampleChildNode = lSampleChildNodes
253: .item(lNextSampleChildIndex);
254: short lSampleChildNodeType = lSampleChildNode
255: .getNodeType();
256: if (lSpecimenChildNodeType != lSampleChildNodeType)
257: continue; // Node type mismatch - skip it
258: // For text and CDATA it is enough to get the same type of node in the sample
259: if (lSpecimenChildNodeType == Node.TEXT_NODE
260: || lSpecimenChildNodeType == Node.CDATA_SECTION_NODE) {
261: // Node type has matched - this is enough
262: lFoundMatchingSampleChildNode = true;
263: if (!compareAgainstSpecimen(lSpecimenChildNode,
264: lSampleChildNode, pAbortOnFirstError,
265: pTestForRegularExpressions, pErrorsList)) {
266: if (pAbortOnFirstError)
267: return false; // Attribute does not exists or have diferent value in the sample node.
268: }
269: } else
270: // For text and Element - it is enough to get the element in the same namespace and same tag name
271: if (lSpecimenChildNodeType == Node.ELEMENT_NODE) {
272: String lSpecimenChildNoneNamespaceURI = lSpecimenChildNode
273: .getNamespaceURI();
274: String lSampleChildNoneNamespaceURI = lSampleChildNode
275: .getNamespaceURI();
276: if ((lSpecimenChildNoneNamespaceURI == null && lSampleChildNoneNamespaceURI != null)
277: || (lSpecimenChildNoneNamespaceURI != null && lSampleChildNoneNamespaceURI == null)
278: || (lSpecimenChildNoneNamespaceURI != null
279: && lSampleChildNoneNamespaceURI != null && lSpecimenChildNoneNamespaceURI
280: .equals(lSampleChildNoneNamespaceURI) == false))
281: continue; // Namespace mismatch
282: String lSpecimenChildElementTagName = ((Element) lSpecimenChildNode)
283: .getTagName();
284: String lSampleChildElementTagName = ((Element) lSampleChildNode)
285: .getTagName();
286: if (lSpecimenChildElementTagName
287: .equals(lSampleChildElementTagName) == false)
288: continue; // Element tag mismatch
289: // In case we are after unordered match - we need to just compare and move on
290: // to the next one if match has not occurred
291: if (lSpecimenElementType
292: .equals(sChildElementMatchTypeValue_unorderedMatch)) {
293: lFoundMatchingSampleChildNode = compareAgainstSpecimen(
294: lSpecimenChildNode,
295: lSampleChildNode, true,
296: pTestForRegularExpressions,
297: lTempErrorsList);
298: } else {
299: // Tag name and namespace has matched - this is enough to report a match and
300: // report the rest of the problems as a mismatch
301: lFoundMatchingSampleChildNode = true;
302: if (!compareAgainstSpecimen(
303: lSpecimenChildNode,
304: lSampleChildNode,
305: pAbortOnFirstError,
306: pTestForRegularExpressions,
307: pErrorsList)) {
308: if (pAbortOnFirstError)
309: return false; // Element mismatch
310: }
311: }
312: }
313: }
314: // Speciment possibly not found at the sample
315: if (!lFoundMatchingSampleChildNode) {
316: if ((lSpecimenElementType
317: .equals(sChildElementMatchTypeValue_unorderedMatch))
318: && (!lTempErrorsList.isEmpty())) {
319: pErrorsList
320: .add(DOMUtils
321: .getAbsoluteNodePath(lSpecimenChildNode)
322: + ": Unable to match this node against any node in the sample due to the following errors:");
323: for (Iterator lIter = lTempErrorsList
324: .iterator(); lIter.hasNext();)
325: pErrorsList.add(" "
326: + (String) lIter.next());
327: } else
328: pErrorsList
329: .add(DOMUtils
330: .getAbsoluteNodePath(lSpecimenChildNode)
331: + ": Corresponding sample node is absent.");
332: if (pAbortOnFirstError)
333: return false; // Node is absent
334: }
335: }
336: // Attributes are done, Child nodes are done - thats all !!
337: } else if (lSpecimenNodeType == Node.ATTRIBUTE_NODE) {
338: Attr lSpecimenAttr = (Attr) pSpecimenNode;
339: Attr lSampleAttr = (Attr) pSampleNode;
340: String lSpecimenName = lSpecimenAttr.getName();
341: String lSampleName = lSampleAttr.getName();
342: if (lSpecimenName.equals(lSampleName) == false) {
343: pErrorsList
344: .add(DOMUtils
345: .getAbsoluteNodePath(pSpecimenNode)
346: + ": Corresponding sample attribute has a different name (expecting '"
347: + lSpecimenName
348: + "', got '"
349: + lSampleName + "')");
350: if (pAbortOnFirstError)
351: return false; // Attribute name mismatch
352: }
353: String lSpecimenValue = lSpecimenAttr.getValue();
354: String lSampleValue = lSampleAttr.getValue();
355: if (lSampleValue.equals(lSpecimenValue) == false) {
356: // Try regex - it is slower, but may in fact match
357: if ((pTestForRegularExpressions = false)
358: || (lSampleValue.matches(lSpecimenValue) == false)) {
359: pErrorsList
360: .add(DOMUtils
361: .getAbsoluteNodePath(pSpecimenNode)
362: + ": Corresponding sample attribute has a different value (expecting '"
363: + lSpecimenValue
364: + "', got '"
365: + lSampleValue + "')");
366: if (pAbortOnFirstError)
367: return false; // Attribute name value
368: }
369: }
370: // Name and value is done thats all !!
371: } else if (lSpecimenNodeType == Node.TEXT_NODE) {
372: Text lSpecimenTextSection = (Text) pSpecimenNode;
373: Text lSampleTextSection = (Text) pSampleNode;
374: String lSpecimenText = lSpecimenTextSection.getData();
375: String lSampleText = lSampleTextSection.getData();
376: if (lSampleText.equals(lSpecimenText) == false) {
377: // Try regex - it is slower, but may in fact match
378: if ((pTestForRegularExpressions = false)
379: || (lSampleText.matches(lSpecimenText) == false)) {
380: String lSpecimenDisplayText = lSpecimenText
381: .length() <= 15 ? lSpecimenText
382: : (lSpecimenText.substring(0, 15) + "...");
383: String lSampleDisplayText = lSampleText.length() <= 15 ? lSampleText
384: : (lSampleText.substring(0, 15) + "...");
385: pErrorsList
386: .add(DOMUtils
387: .getAbsoluteNodePath(pSpecimenNode)
388: + ": Corresponding sample text section has a different value (expecting '"
389: + lSpecimenDisplayText
390: + "', got '"
391: + lSampleDisplayText + "')");
392: if (pAbortOnFirstError)
393: return false; // Attribute name value
394: }
395: }
396: // Text is done thats all !!
397: } else if (lSpecimenNodeType == Node.CDATA_SECTION_NODE) {
398: CDATASection lSpecimenCDATASection = (CDATASection) pSpecimenNode;
399: CDATASection lSampleCDATASection = (CDATASection) pSampleNode;
400: String lSpecimenCDATA = lSpecimenCDATASection.getData();
401: String lSampleCDATA = lSampleCDATASection.getData();
402: if (lSampleCDATA.equals(lSpecimenCDATA) == false) {
403: // Try regex - it is slower, but may in fact match
404: if ((pTestForRegularExpressions = false)
405: || (lSampleCDATA.matches(lSpecimenCDATA) == false)) {
406: String lSpecimenDisplayText = lSpecimenCDATA
407: .length() <= 15 ? lSpecimenCDATA
408: : (lSpecimenCDATA.substring(0, 10) + "...");
409: String lSampleDisplayText = lSampleCDATA.length() <= 15 ? lSampleCDATA
410: : (lSampleCDATA.substring(0, 10) + "...");
411: pErrorsList
412: .add(DOMUtils
413: .getAbsoluteNodePath(pSpecimenNode)
414: + ": Corresponding sample CDATA section has a different value (expecting '"
415: + lSpecimenDisplayText
416: + "...', got '"
417: + lSampleDisplayText + "...')");
418: if (pAbortOnFirstError)
419: return false; // Attribute name value
420: }
421: }
422: // Text is done thats all !!
423: } else
424: throw new IllegalArgumentException(
425: "Unsupported type of Node for the operation. Only Attribute and Element, Text and CDATASection are supported.");
426: // Return true if there was no errors
427: return pErrorsList.size() == lInitialErrorCount;
428: }
429: }
|