001/*
002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Antoine Taillefer
016 */
017package org.nuxeo.ecm.diff.service.impl;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import org.custommonkey.xmlunit.Difference;
026import org.custommonkey.xmlunit.DifferenceConstants;
027import org.custommonkey.xmlunit.NodeDetail;
028import org.nuxeo.ecm.core.api.NuxeoException;
029import org.nuxeo.ecm.core.io.ExportConstants;
030import org.nuxeo.ecm.diff.model.DifferenceType;
031import org.nuxeo.ecm.diff.model.DocumentDiff;
032import org.nuxeo.ecm.diff.model.PropertyDiff;
033import org.nuxeo.ecm.diff.model.PropertyType;
034import org.nuxeo.ecm.diff.model.SchemaDiff;
035import org.nuxeo.ecm.diff.model.impl.ComplexPropertyDiff;
036import org.nuxeo.ecm.diff.model.impl.ContentProperty;
037import org.nuxeo.ecm.diff.model.impl.ContentPropertyDiff;
038import org.nuxeo.ecm.diff.model.impl.ListPropertyDiff;
039import org.nuxeo.ecm.diff.model.impl.PropertyHierarchyNode;
040import org.nuxeo.ecm.diff.model.impl.SimplePropertyDiff;
041import org.w3c.dom.NamedNodeMap;
042import org.w3c.dom.Node;
043import org.w3c.dom.NodeList;
044
045/**
046 * Helper for computing a field diff.
047 *
048 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a>
049 * @since 5.6
050 */
051public final class FieldDiffHelper {
052
053    private static final Log LOGGER = LogFactory.getLog(FieldDiffHelper.class);
054
055    public static final String FACET_ELEMENT = "facet";
056
057    public static final String SCHEMA_ELEMENT = "schema";
058
059    public static final String NAME_ATTRIBUTE = "name";
060
061    public static final String TYPE_ATTRIBUTE = "type";
062
063    /**
064     * Computes a field diff.
065     * <p>
066     * First gets all needed elements to compute the field diff:
067     * <ul>
068     * <li>propertyHierarchy: list holding the property hierarchy
069     *
070     * <pre>
071     * Every time we encounter a list, a complex or a content node going up in the DOM tree
072     * from the property node to the prefixed field node, we add it to the property
073     * hierarchy.
074     * If it is a list item node, we set its index in the hierarchy.
075     * If it is a complex item node, we set its name in the hierarchy.
076     * If it is a content item node (ie. "encoding", "mime-type", "filename" or "digest"),
077     * we set its name in the hierarchy.
078     *
079     * Example: complex list
080     *
081     * The "true" property's hierarchy is:
082     * [{list,"0"},{complex, "complexBoolean"}]
083     *
084     * The "jack" property's hierarchy is:
085     * [{list,"1"},{complex, "complexString"}]
086     *
087     * The "UTF-8" property's hierarchy is:
088     * [{list,"0"},{complex, "complexString"},{content, "encoding"}]
089     *
090     * <list type="complexList">
091     *   <complexItem type="complex">
092     *     <complexString type="string">joe</complexString>
093     *     <complexBoolean type="boolean">true</complexBoolean>
094     *     <complexContent type="content">
095     *      <encoding>UTF-8</encoding>
096     *      <mime-type>text/plain</mime-type>
097     *      <filename>My_file.txt</filename>
098     *      <digest>5dafdabf966043c8c8cef20011e939a2</digest>
099     *     </complexContent>
100     *   </complexItem>
101     *   <complexItem type="complex">
102     *     <complexString type="string">jack</complexString>
103     *     <complexBoolean type="boolean">false</complexBoolean>
104     *   </complexItem>
105     * </list>
106     * </pre>
107     *
108     * </li>
109     * </ul>
110     *
111     * @param docDiff the doc diff
112     * @param controlNodeDetail the control node detail
113     * @param testNodeDetail the test node detail
114     * @param fieldDifferenceCount the field difference countadd
115     * @param difference the difference
116     * @return true if a field diff has been found
117     */
118    public static boolean computeFieldDiff(DocumentDiff docDiff, NodeDetail controlNodeDetail,
119            NodeDetail testNodeDetail, int fieldDifferenceCount, Difference difference) {
120
121        // Use control node or if null test node to detect schema and
122        // field elements
123        Node currentNode = controlNodeDetail.getNode();
124        if (currentNode == null) {
125            currentNode = testNodeDetail.getNode();
126        }
127        if (currentNode != null) {
128
129            String field = null;
130            String currentNodeName = currentNode.getNodeName();
131            List<PropertyHierarchyNode> propertyHierarchy = new ArrayList<PropertyHierarchyNode>();
132
133            // Detect a schema element,
134            // for instance: <schema name="dublincore" xmlns:dc="...">.
135            // Otherwise build the property hierarchy.
136            // For a content type property (blob) don't take into account a
137            // difference on the "data" field, since what makes the difference
138            // between 2 blobs is either the filename or the digest.
139            Node parentNode = currentNode.getParentNode();
140            while (parentNode != null && !SCHEMA_ELEMENT.equals(currentNodeName)
141                    && !ExportConstants.BLOB_DATA.equals(parentNode.getNodeName())) {
142
143                // Get property type
144                String propertyType = getPropertyType(currentNode);
145                String parentPropertyType = getPropertyType(parentNode);
146
147                // Fill in property hierarchy
148                if (PropertyType.isListType(parentPropertyType)) {
149                    int currentNodePosition = getNodePosition(currentNode);
150                    propertyHierarchy.add(new PropertyHierarchyNode(parentPropertyType,
151                            String.valueOf(currentNodePosition)));
152                } else if (PropertyType.isComplexType(parentPropertyType)
153                        || PropertyType.isContentType(parentPropertyType)) {
154                    propertyHierarchy.add(new PropertyHierarchyNode(parentPropertyType, currentNodeName));
155                }
156
157                // Detect a field element, ie. an element that has a
158                // prefix, for instance: <dc:title>.
159                if (SCHEMA_ELEMENT.equals(parentNode.getNodeName())) {
160                    String currentNodeLocalName = currentNode.getLocalName();
161                    // TODO: manage better the facet case
162                    if (!FACET_ELEMENT.equals(currentNodeLocalName)) {
163                        field = currentNodeLocalName;
164                        if (PropertyType.isSimpleType(propertyType) || PropertyType.isListType(propertyType)
165                                && propertyHierarchy.isEmpty() || PropertyType.isComplexType(propertyType)
166                                && propertyHierarchy.isEmpty() || PropertyType.isContentType(propertyType)
167                                && propertyHierarchy.isEmpty()) {
168                            propertyHierarchy.add(new PropertyHierarchyNode(propertyType, null));
169                        }
170                    }
171                }
172                currentNode = parentNode;
173                currentNodeName = currentNode.getNodeName();
174                parentNode = parentNode.getParentNode();
175            }
176
177            // If we found a schema element (ie. we did not
178            // reached the root element, ie. parentNode != null) and a
179            // nested field element, we can compute the diff for this
180            // field.
181            if (parentNode != null && field != null && !propertyHierarchy.isEmpty()) {
182                String schema = currentNodeName;
183                // Get schema name
184                NamedNodeMap attr = currentNode.getAttributes();
185                if (attr != null && attr.getLength() > 0) {
186                    Node nameAttr = attr.getNamedItem(NAME_ATTRIBUTE);
187                    if (nameAttr != null) {
188                        schema = nameAttr.getNodeValue();
189                    }
190                }
191
192                // Reverse property hierarchy
193                Collections.reverse(propertyHierarchy);
194
195                // Pretty log field difference
196                LOGGER.debug(String.format(
197                        "Found field difference #%d on [%s]/[%s] with hierarchy %s: [%s (%s)] {%s --> %s}",
198                        fieldDifferenceCount + 1, schema, field, propertyHierarchy, difference.getDescription(),
199                        difference.getId(), controlNodeDetail.getValue(), testNodeDetail.getValue()));
200
201                // Compute field diff
202                computeFieldDiff(docDiff, schema, field, propertyHierarchy, difference.getId(), controlNodeDetail,
203                        testNodeDetail);
204                // Return true since a field diff has been found
205                return true;
206
207            } else {// Non-field difference
208                LOGGER.debug(String.format("Found non-field difference: [%s (%s)] {%s --> %s}",
209                        difference.getDescription(), difference.getId(), controlNodeDetail.getValue(),
210                        testNodeDetail.getValue()));
211            }
212        }
213        return false;
214    }
215
216    /**
217     * Gets the node property type.
218     *
219     * @param node the node
220     * @return the property diff type
221     */
222    public static String getPropertyType(Node node) {
223
224        // Default: undefined
225        String propertyType = PropertyType.UNDEFINED;
226
227        NamedNodeMap nodeAttr = node.getAttributes();
228        if (nodeAttr != null) {
229            Node type = nodeAttr.getNamedItem(TYPE_ATTRIBUTE);
230            if (type != null) {
231                propertyType = type.getNodeValue();
232            }
233        }
234
235        return propertyType;
236    }
237
238    /**
239     * Gets the node position.
240     *
241     * @param node the node
242     * @return the node position
243     */
244    private static int getNodePosition(Node node) {
245
246        int nodePos = 0;
247        Node previousSibling = node.getPreviousSibling();
248        while (previousSibling != null) {
249            nodePos++;
250            previousSibling = previousSibling.getPreviousSibling();
251        }
252        return nodePos;
253    }
254
255    /**
256     * Sets the property diff hierarchy.
257     *
258     * @param firstPropertyDiff the first property diff
259     * @param propertyHierarchy the property hierarchy
260     * @return the property diff
261     */
262    public static PropertyDiff applyPropertyHierarchyToDiff(PropertyDiff firstPropertyDiff,
263            List<PropertyHierarchyNode> propertyHierarchy) {
264
265        if (propertyHierarchy.isEmpty()) {
266            throw new NuxeoException("Empty property hierarchy.");
267        }
268
269        // Get first property hierarchy node
270        PropertyHierarchyNode propertyHierarchyNode = propertyHierarchy.get(0);
271        String firstPropertyType = propertyHierarchyNode.getNodeType();
272        String firstPropertyValue = propertyHierarchyNode.getNodeValue();
273
274        if ((PropertyType.isSimpleType(firstPropertyType) || PropertyType.isContentType(firstPropertyType))
275                && propertyHierarchy.size() > 1) {
276            throw new NuxeoException(String.format("Inconsistant property hierarchy %s.", propertyHierarchy));
277        }
278
279        // Go through the property hierarchy
280        PropertyDiff propertyDiff = firstPropertyDiff;
281        String propertyType = firstPropertyType;
282        String propertyValue = firstPropertyValue;
283        for (int i = 1; i < propertyHierarchy.size(); i++) {
284
285            PropertyDiff childPropertyDiff = null;
286            PropertyHierarchyNode childPropertyHierarchyNode = propertyHierarchy.get(i);
287            String childPropertyType = childPropertyHierarchyNode.getNodeType();
288            String childPropertyValue = childPropertyHierarchyNode.getNodeValue();
289
290            // Simple or content type
291            if (PropertyType.isSimpleType(propertyType) || PropertyType.isContentType(propertyType)) {
292                // Nothing to do here (should never happen)
293            }
294            // List type
295            else if (PropertyType.isListType(propertyType)) {
296                int propertyIndex = Integer.parseInt(propertyValue);
297                // Get list diff, if null create a new one
298                childPropertyDiff = ((ListPropertyDiff) propertyDiff).getDiff(propertyIndex);
299                if (childPropertyDiff == null) {
300                    childPropertyDiff = newPropertyDiff(childPropertyType);
301                    ((ListPropertyDiff) propertyDiff).putDiff(propertyIndex, childPropertyDiff);
302                }
303                propertyDiff = childPropertyDiff;
304            }
305            // Complex type
306            else {
307                // Get complex diff, initialize it if null
308                childPropertyDiff = ((ComplexPropertyDiff) propertyDiff).getDiff(propertyValue);
309                if (childPropertyDiff == null) {
310                    childPropertyDiff = newPropertyDiff(childPropertyType);
311                    ((ComplexPropertyDiff) propertyDiff).putDiff(propertyValue, childPropertyDiff);
312                }
313                propertyDiff = childPropertyDiff;
314            }
315
316            propertyType = childPropertyType;
317            propertyValue = childPropertyValue;
318        }
319        return propertyDiff;
320    }
321
322    /**
323     * Computes a field diff.
324     *
325     * @param docDiff the doc diff
326     * @param schema the schema
327     * @param field the field
328     * @param propertyHierarchy the property hierarchy
329     * @param differenceId the difference id
330     * @param controlNodeDetail the control node detail
331     * @param testNodeDetail the test node detail
332     */
333    private static void computeFieldDiff(DocumentDiff docDiff, String schema, String field,
334            List<PropertyHierarchyNode> propertyHierarchy, int differenceId, NodeDetail controlNodeDetail,
335            NodeDetail testNodeDetail) {
336
337        if (propertyHierarchy.isEmpty()) {
338            throw new NuxeoException("Empty property hierarchy.");
339        }
340
341        // Get first property hierarchy node
342        PropertyHierarchyNode propertyHierarchyNode = propertyHierarchy.get(0);
343        String firstPropertyType = propertyHierarchyNode.getNodeType();
344
345        // Get schema diff, initialize it if null
346        SchemaDiff schemaDiff = docDiff.getSchemaDiff(schema);
347        if (schemaDiff == null) {
348            schemaDiff = docDiff.initSchemaDiff(schema);
349        }
350
351        // Get field diff, initialize it if null
352        PropertyDiff fieldDiff = schemaDiff.getFieldDiff(field);
353        if (fieldDiff == null) {
354            fieldDiff = newPropertyDiff(firstPropertyType);
355        }
356
357        PropertyDiff endPropertyDiff = fieldDiff;
358        // Apply property hierarchy to diff if first property type in hierarchy
359        // is list or complex
360        if (!(PropertyType.isSimpleType(firstPropertyType))) {
361            endPropertyDiff = applyPropertyHierarchyToDiff(fieldDiff, propertyHierarchy);
362        }
363
364        // Compute field diff depending on difference type.
365        switch (differenceId) {
366        case DifferenceConstants.TEXT_VALUE_ID:
367            computeTextValueDiff(endPropertyDiff, controlNodeDetail, testNodeDetail);
368            break;
369        case DifferenceConstants.CHILD_NODE_NOT_FOUND_ID:
370            computeChildNodeNotFoundDiff(endPropertyDiff, controlNodeDetail, testNodeDetail);
371            break;
372        case DifferenceConstants.HAS_CHILD_NODES_ID:
373            computeHasChildNodesDiff(endPropertyDiff, controlNodeDetail, testNodeDetail);
374            break;
375        default:
376            computeTextValueDiff(endPropertyDiff, controlNodeDetail, testNodeDetail);
377        }
378
379        schemaDiff.putFieldDiff(field, fieldDiff);
380    }
381
382    /**
383     * New property diff.
384     *
385     * @param propertyType the property type
386     * @return the property diff
387     */
388    private static PropertyDiff newPropertyDiff(String propertyType) {
389
390        if (PropertyType.isSimpleType(propertyType)) {
391            return new SimplePropertyDiff(propertyType);
392        } else if (PropertyType.isListType(propertyType)) {
393            return new ListPropertyDiff(propertyType);
394        } else if (PropertyType.isComplexType(propertyType)) {
395            return new ComplexPropertyDiff();
396        } else { // Content type
397            return new ContentPropertyDiff();
398        }
399    }
400
401    /**
402     * Computes a TEXT_VALUE diff.
403     *
404     * @param fieldDiff the field diff
405     * @param controlNodeDetail the control node detail
406     * @param testNodeDetail the test node detail
407     */
408    private static void computeTextValueDiff(PropertyDiff fieldDiff, NodeDetail controlNodeDetail,
409            NodeDetail testNodeDetail) {
410
411        String leftValue = controlNodeDetail.getValue();
412        String rightValue = testNodeDetail.getValue();
413
414        Node controlNode = controlNodeDetail.getNode();
415        if (controlNode == null) {
416            throw new NuxeoException("Control node should never be null.");
417        }
418
419        Node controlParentNode = controlNode.getParentNode();
420        if (controlParentNode == null) {
421            throw new NuxeoException("Control parent node should never be null.");
422        }
423
424        String controlParentNodePropertyType = getPropertyType(controlParentNode);
425        String fieldDiffPropertyType = fieldDiff.getPropertyType();
426        // Simple type
427        if (PropertyType.isSimpleType(fieldDiffPropertyType)) {
428            ((SimplePropertyDiff) fieldDiff).setLeftValue(leftValue);
429            ((SimplePropertyDiff) fieldDiff).setRightValue(rightValue);
430        }
431        // List type
432        else if (PropertyType.isListType(fieldDiffPropertyType)) {
433            ((ListPropertyDiff) fieldDiff).putDiff(getNodePosition(controlParentNode), new SimplePropertyDiff(
434                    controlParentNodePropertyType, leftValue, rightValue));
435        }
436        // Complex type
437        else if (PropertyType.isComplexType(fieldDiffPropertyType)) {
438            ((ComplexPropertyDiff) fieldDiff).putDiff(controlParentNode.getNodeName(), new SimplePropertyDiff(
439                    controlParentNodePropertyType, leftValue, rightValue));
440        }
441        // Content type
442        else {
443            ContentPropertyDiff contentPropertyDiff = ((ContentPropertyDiff) fieldDiff);
444            setContentSubPropertyDiff(contentPropertyDiff, controlParentNode.getNodeName(), leftValue, rightValue);
445        }
446    }
447
448    /**
449     * Computes a CHILD_NODE_NOT_FOUND diff.
450     *
451     * @param fieldDiff the field diff
452     * @param controlNodeDetail the control node detail
453     * @param testNodeDetail the test node detail
454     */
455    private static void computeChildNodeNotFoundDiff(PropertyDiff fieldDiff, NodeDetail controlNodeDetail,
456            NodeDetail testNodeDetail) {
457
458        Node childNode;
459        boolean isTestNodeNotFound = "null".equals(testNodeDetail.getValue());
460        if (!isTestNodeNotFound) {
461            childNode = testNodeDetail.getNode();
462        } else {
463            childNode = controlNodeDetail.getNode();
464        }
465
466        if (childNode == null) {
467            throw new NuxeoException("Child node should never be null.");
468        }
469
470        String propertyType = fieldDiff.getPropertyType();
471        // Simple type
472        if (PropertyType.isSimpleType(propertyType)) {
473            // Should never happen as then it would be marked as a
474            // HAS_CHILD_NODES difference.
475            throw new NuxeoException("A CHILD_NODE_NOT_FOUND difference should never be found within a simple type.");
476        }
477        // List type
478        else if (PropertyType.isListType(propertyType)) {
479            PropertyDiff childNodeDiff = getChildNodePropertyDiff(childNode, isTestNodeNotFound);
480            ((ListPropertyDiff) fieldDiff).putDiff(getNodePosition(childNode), childNodeDiff);
481        }
482        // Complex type
483        else if (PropertyType.isComplexType(propertyType)) { // Complex type
484            throw new NuxeoException("A CHILD_NODE_NOT_FOUND difference should never be found within a complex type.");
485        }
486        // Content type
487        else {
488            throw new NuxeoException("A CHILD_NODE_NOT_FOUND difference should never be found within a content type.");
489        }
490    }
491
492    /**
493     * Computes a HAS_CHILD_NODES diff.
494     *
495     * @param fieldDiff the field diff
496     * @param controlNodeDetail the control node detail
497     * @param testNodeDetail the test node detail
498     */
499    private static void computeHasChildNodesDiff(PropertyDiff fieldDiff, NodeDetail controlNodeDetail,
500            NodeDetail testNodeDetail) {
501
502        Node nodeWithChildren;
503        boolean hasControlNodeChildNodes = Boolean.valueOf(controlNodeDetail.getValue());
504        if (hasControlNodeChildNodes) {
505            nodeWithChildren = controlNodeDetail.getNode();
506        } else {
507            nodeWithChildren = testNodeDetail.getNode();
508        }
509
510        if (nodeWithChildren == null) {
511            throw new NuxeoException("Node with children should never be null.");
512        }
513
514        String propertyType = fieldDiff.getPropertyType();
515        // Simple type
516        if (PropertyType.isSimpleType(propertyType)) {
517            setSimplePropertyDiff((SimplePropertyDiff) fieldDiff, nodeWithChildren, hasControlNodeChildNodes);
518        }
519        // List type
520        else if (PropertyType.isListType(propertyType)) {
521            PropertyDiff childNodeDiff = getChildNodePropertyDiff(nodeWithChildren, hasControlNodeChildNodes);
522            if (PropertyType.isListType(getPropertyType(nodeWithChildren))) {
523                ((ListPropertyDiff) fieldDiff).putAllDiff((ListPropertyDiff) childNodeDiff);
524            } else {
525                ((ListPropertyDiff) fieldDiff).putDiff(getNodePosition(nodeWithChildren), childNodeDiff);
526            }
527        }
528        // Complex type
529        else if (PropertyType.isComplexType(propertyType)) {
530            PropertyDiff childNodeDiff = getChildNodePropertyDiff(nodeWithChildren, hasControlNodeChildNodes);
531            if (PropertyType.isComplexType(getPropertyType(nodeWithChildren))) {
532                ((ComplexPropertyDiff) fieldDiff).putAllDiff((ComplexPropertyDiff) childNodeDiff);
533            } else {
534                ((ComplexPropertyDiff) fieldDiff).putDiff(nodeWithChildren.getNodeName(), childNodeDiff);
535            }
536        }
537        // Content type
538        else {
539            if (PropertyType.isContentType(getPropertyType(nodeWithChildren))) {
540                PropertyDiff childNodeDiff = getChildNodePropertyDiff(nodeWithChildren, hasControlNodeChildNodes);
541                ((ContentPropertyDiff) fieldDiff).setLeftContent(((ContentPropertyDiff) childNodeDiff).getLeftContent());
542                ((ContentPropertyDiff) fieldDiff).setRightContent(((ContentPropertyDiff) childNodeDiff).getRightContent());
543            } else {
544                setContentPropertyDiff((ContentPropertyDiff) fieldDiff, nodeWithChildren, hasControlNodeChildNodes);
545            }
546        }
547    }
548
549    /**
550     * Gets the child node property diff.
551     *
552     * @param node the node
553     * @param hasControlNodeChildNodes the test node was not found
554     */
555    private static PropertyDiff getChildNodePropertyDiff(Node node, boolean hasControlNodeChildNodes)
556            {
557
558        PropertyDiff propertyDiff;
559
560        String nodePropertyType = getPropertyType(node);
561
562        // Simple type
563        if (PropertyType.isSimpleType(nodePropertyType)) {
564            propertyDiff = new SimplePropertyDiff(nodePropertyType);
565            setSimplePropertyDiff((SimplePropertyDiff) propertyDiff, node, hasControlNodeChildNodes);
566        }
567        // List type
568        else if (PropertyType.isListType(nodePropertyType)) {
569            propertyDiff = new ListPropertyDiff(nodePropertyType);
570            NodeList childNodes = node.getChildNodes();
571            for (int i = 0; i < childNodes.getLength(); i++) {
572                ((ListPropertyDiff) propertyDiff).putDiff(i,
573                        getChildNodePropertyDiff(childNodes.item(i), hasControlNodeChildNodes));
574            }
575        }
576        // Complex type
577        else if (PropertyType.isComplexType(nodePropertyType)) {
578            propertyDiff = new ComplexPropertyDiff();
579            NodeList childNodes = node.getChildNodes();
580            for (int i = 0; i < childNodes.getLength(); i++) {
581                Node childNode = childNodes.item(i);
582                ((ComplexPropertyDiff) propertyDiff).putDiff(childNode.getNodeName(),
583                        getChildNodePropertyDiff(childNode, hasControlNodeChildNodes));
584            }
585        }
586        // Content type
587        else {
588            propertyDiff = new ContentPropertyDiff();
589            NodeList childNodes = node.getChildNodes();
590            for (int i = 0; i < childNodes.getLength(); i++) {
591                Node childNode = childNodes.item(i);
592                setContentPropertyDiff((ContentPropertyDiff) propertyDiff, childNode, hasControlNodeChildNodes);
593            }
594        }
595        return propertyDiff;
596    }
597
598    /**
599     * Sets the text content of textNode on {@link SimplePropertyDiff} field diff.
600     *
601     * @param fieldDiff the field diff
602     * @param textNode the text node
603     * @param hasControlNodeContent the has control node content
604     */
605    private static void setSimplePropertyDiff(SimplePropertyDiff fieldDiff, Node textNode, boolean hasControlNodeContent) {
606
607        String textNodeValue = textNode.getTextContent();
608
609        String leftValue = hasControlNodeContent ? textNodeValue : null;
610        String rightValue = hasControlNodeContent ? null : textNodeValue;
611
612        fieldDiff.setLeftValue(leftValue);
613        fieldDiff.setRightValue(rightValue);
614    }
615
616    /**
617     * Sets the text content of textNode on a {@link ContentPropertyDiff} field diff.
618     *
619     * @param fieldDiff the field diff
620     * @param textNode the text node
621     * @param hasControlNodeContent the has control node content
622     */
623    private static void setContentPropertyDiff(ContentPropertyDiff fieldDiff, Node textNode,
624            boolean hasControlNodeContent) {
625
626        String textNodeValue = textNode.getTextContent();
627
628        String leftValue = hasControlNodeContent ? textNodeValue : null;
629        String rightValue = hasControlNodeContent ? null : textNodeValue;
630
631        setContentSubPropertyDiff(fieldDiff, textNode.getNodeName(), leftValue, rightValue);
632    }
633
634    protected static void setContentSubPropertyDiff(ContentPropertyDiff fieldDiff, String subPropertyName,
635            String leftSubPropertyValue, String rightSubPropertyValue) {
636
637        // Get or initialize left and right content
638        ContentProperty leftContent = fieldDiff.getLeftContent();
639        ContentProperty rightContent = fieldDiff.getRightContent();
640        if (leftContent == null) {
641            leftContent = new ContentProperty();
642            fieldDiff.setLeftContent(leftContent);
643        }
644        if (rightContent == null) {
645            rightContent = new ContentProperty();
646            fieldDiff.setRightContent(rightContent);
647        }
648
649        // Set sub property on left and right content
650        leftContent.setSubProperty(subPropertyName, leftSubPropertyValue);
651        rightContent.setSubProperty(subPropertyName, rightSubPropertyValue);
652
653        // Set difference type on content property diff
654        if (ExportConstants.BLOB_FILENAME.equals(subPropertyName)) {
655            if (DifferenceType.differentDigest.equals(fieldDiff.getDifferenceType())) {
656                fieldDiff.setDifferenceType(DifferenceType.different);
657            } else {
658                fieldDiff.setDifferenceType(DifferenceType.differentFilename);
659            }
660        } else if (ExportConstants.BLOB_DIGEST.equals(subPropertyName)) {
661            if (DifferenceType.differentFilename.equals(fieldDiff.getDifferenceType())) {
662                fieldDiff.setDifferenceType(DifferenceType.different);
663            } else {
664                fieldDiff.setDifferenceType(DifferenceType.differentDigest);
665            }
666        }
667    }
668}