org.eclipse.skalli.core.rest.ProjectsV1V2Diff.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.skalli.core.rest.ProjectsV1V2Diff.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2014 SAP AG and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     SAP AG - initial API and implementation
 *******************************************************************************/
package org.eclipse.skalli.core.rest;

import static org.junit.Assert.fail;

import java.text.MessageFormat;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.custommonkey.xmlunit.Difference;
import org.custommonkey.xmlunit.DifferenceConstants;
import org.custommonkey.xmlunit.DifferenceListener;
import org.custommonkey.xmlunit.NodeDetail;
import org.eclipse.skalli.commons.ComparatorUtils;
import org.w3c.dom.Node;

@SuppressWarnings("nls")
public class ProjectsV1V2Diff implements DifferenceListener {

    protected final Pattern ROOT_XPATH_PATTERN = getPattern("");
    protected final Pattern EXTENSION_XPATH_PATTERN = getPattern("/extensions\\[1\\]/.+\\[1\\]");
    protected final Pattern LINK_XPATH_PATTERN = getPattern("/link\\[\\d+\\]");
    protected final Pattern LINK_HREF_XPATH_PATTERN = getPattern("/link\\[\\d+\\]/@href");
    protected final Pattern LINK_REL_XPATH_PATTERN = getPattern("/link\\[\\d+\\]/@rel");
    protected final Pattern PHASE_XPATH_PATTERN = getPattern("/phase\\[1\\]");
    protected final Pattern REGISTERED_XPATH_PATTERN = getPattern("/registered\\[1\\]");
    protected final Pattern DESCRIPTION_XPATH_PATTERN = getPattern("/description\\[1\\]");
    protected final Pattern DESCRIPTION_FORMAT_XPATH_PATTERN = getPattern("/descriptionFormat\\[1\\]");
    protected final Pattern DESCRIPTION_TEXT_XPATH_PATTERN = getPattern("/description\\[1\\]/text\\(\\)\\[1\\]");
    protected final Pattern SUBPROJECTS_XPATH_PATTERN = getPattern("/subprojects\\[1\\]");
    protected final Pattern EXTENSIONS_XPATH_PATTERN = getPattern("/extensions\\[1\\]");
    protected final Pattern MEMBERS_XPATH_PATTERN = getPattern("/members\\[1\\]");

    protected final String webLocator;

    public ProjectsV1V2Diff(String webLocator) {
        this.webLocator = webLocator;
    }

    @Override
    public int differenceFound(Difference difference) {
        int result = RETURN_ACCEPT_DIFFERENCE;
        NodeDetail expected = difference.getControlNodeDetail();
        NodeDetail actual = difference.getTestNodeDetail();
        switch (difference.getId()) {
        case DifferenceConstants.ELEMENT_NUM_ATTRIBUTES_ID:
            // <project> tag has an additional "lastModifiedMillis" attribute in the new API
            if (equalsAndMatchesAnyXPath(expected, actual, ROOT_XPATH_PATTERN, EXTENSION_XPATH_PATTERN)
                    && (valueToInt(actual) == valueToInt(expected) + 1)) {
                result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
            }
            break;
        case DifferenceConstants.ATTR_NAME_NOT_FOUND_ID:
            // <project> tag has an additional "lastModifiedMillis" attribute in the new API
            if (equalsAndMatchesAnyXPath(expected, actual, ROOT_XPATH_PATTERN, EXTENSION_XPATH_PATTERN)
                    && equalsValueNull(expected) && "lastModifiedMillis".equals(actual.getValue())) {
                result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
            }
            ;
            break;
        case DifferenceConstants.CHILD_NODELIST_LENGTH_ID:
            // <project> tag has always an additional <descriptionFormnat> tag in the new API, but never in the old API;
            // <project> tag has always an additional <link rel=permalink> in the new API, but never in the old API;
            // <project> tag has always a <link rel=subprojects> in the new API,
            // but only if it has also a <subprojects> tag in the old API;
            // <project> tag has always a <members> tag in the new API (even if empty),
            // but only if it was non-empty in the old API;
            // <project> tag has always a <subprojects> tag in the new API (even if empty),
            // but only if it was non-empty in the old API;
            // therefore, we have at least 2 additional tags, but never more than 4
            if (equalsAndMatchesAnyXPath(expected, actual, ROOT_XPATH_PATTERN)
                    && (valueToInt(actual) >= valueToInt(expected) + 3)
                    && (valueToInt(actual) <= valueToInt(expected) + 5)) {
                result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
            }
            break;
        case DifferenceConstants.CHILD_NODELIST_SEQUENCE_ID:
            // some tags have changed their position in the new API compared to the old API, e.g.
            // all <link> tags are now grouped together, and the <phase>, <registered> and <description>
            // tags are now  rendered before the links.
            if (equalsAndMatchesAnyXPath(expected, actual, LINK_XPATH_PATTERN, PHASE_XPATH_PATTERN,
                    REGISTERED_XPATH_PATTERN, DESCRIPTION_XPATH_PATTERN, MEMBERS_XPATH_PATTERN,
                    SUBPROJECTS_XPATH_PATTERN, EXTENSIONS_XPATH_PATTERN)) {
                result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
            }
            break;
        case DifferenceConstants.ATTR_VALUE_ID:
            // The <link> tags have different ordering and position within the <project> tag
            if (equalsAndMatchesAnyXPath(expected, actual, LINK_HREF_XPATH_PATTERN, LINK_REL_XPATH_PATTERN)) {
                result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
            }
            break;
        case DifferenceConstants.CHILD_NODE_NOT_FOUND_ID:
            // in the new API we may have additional <descriptionFormat>, <link>, <subprojects> and <members> tags,
            // which may no be there in the old API
            if (equalsValueNull(expected) && (matchesAnyXPath(actual, DESCRIPTION_FORMAT_XPATH_PATTERN)
                    && "descriptionFormat".equals(actual.getValue())
                    || matchesAnyXPath(actual, LINK_XPATH_PATTERN) && "link".equals(actual.getValue())
                    || matchesAnyXPath(actual, SUBPROJECTS_XPATH_PATTERN) && "subprojects".equals(actual.getValue())
                    || matchesAnyXPath(actual, MEMBERS_XPATH_PATTERN) && "members".equals(actual.getValue()))) {
                result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
            }
            break;
        case DifferenceConstants.TEXT_VALUE_ID:
            // old and new API render different file endings: the new API renders a single \n #xA),
            // while the old API preferred \r\n (#xD #xA)
            if (equalsAndMatchesAnyXPath(expected, actual, DESCRIPTION_TEXT_XPATH_PATTERN)
                    && equalsValueIgnoreLineEndings(expected, actual)) {
                result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
            }
            ;
            break;
        default:
            result = RETURN_ACCEPT_DIFFERENCE;
        }
        return result;
    }

    @Override
    public void skippedComparison(Node expected, Node actual) {
        fail(MessageFormat.format(
                "comparison skipped because the node types are not comparable: {0} (type: {1}) - {2} (type: {3})",
                expected.getNodeName(), expected.getNodeType(), actual.getNodeName(), actual.getNodeType()));
    }

    protected String getRootPath() {
        return "/projects\\[1\\]";
    }

    protected Pattern getPattern(String relPath) {
        return Pattern.compile(MessageFormat.format("^{0}/project\\[\\d+\\]{1}$", getRootPath(), relPath));
    }

    protected Pattern getExtPattern(String relPath) {
        return Pattern.compile(
                MessageFormat.format("^{0}/project\\[\\d+\\]/extensions\\[1\\]{1}$", getRootPath(), relPath));
    }

    protected boolean equalsXPath(NodeDetail expected, NodeDetail actual) {
        return ComparatorUtils.equals(expected.getXpathLocation(), actual.getXpathLocation());
    }

    protected boolean matchesAnyXPath(NodeDetail node, Pattern... xPathPatterns) {
        for (Pattern xPathPattern : xPathPatterns) {
            if (xPathPattern.matcher(node.getXpathLocation()).matches()) {
                return true;
            }
        }
        return false;
    }

    protected boolean equalsAndMatchesAnyXPath(NodeDetail expected, NodeDetail actual, Pattern... xPathPatterns) {
        for (Pattern xPathPattern : xPathPatterns) {
            if (equalsXPath(expected, actual) && matchesAnyXPath(expected, xPathPattern)
                    && matchesAnyXPath(actual, xPathPattern)) {
                return true;
            }
        }
        return false;
    }

    protected boolean equalsValueInt(NodeDetail node, int value) {
        return isValueInt(node) && valueToInt(node) == value;
    }

    protected boolean isValueInt(NodeDetail node) {
        return NumberUtils.isNumber(node.getValue());
    }

    protected int valueToInt(NodeDetail node) {
        return NumberUtils.toInt(node.getValue());
    }

    protected boolean equalsValueNull(NodeDetail node) {
        return "null".equals(node.getValue());
    }

    protected boolean equalsValueIgnoreLineEndings(NodeDetail expected, NodeDetail actual) {
        return normalized(expected).equals(normalized(actual));
    }

    protected String normalized(NodeDetail node) {
        return StringUtils.replace(node.getValue(), "\r\n", "\n");
    }

    protected boolean hasEmptyChildNode(NodeDetail nodeDetails, String name) {
        Node node = nodeDetails.getNode().getFirstChild();
        while (node != null) {
            if (name.equals(node.getNodeName())) {
                return !node.hasChildNodes();
            }
            node = node.getNextSibling();
        }
        return false;
    }

    protected boolean hasBooleanChildNode(NodeDetail nodeDetails, String name) {
        Node node = nodeDetails.getNode().getFirstChild();
        while (node != null) {
            if (name.equals(node.getNodeName())) {
                return "true".equals(node.getTextContent()) || "false".equals(node.getTextContent());
            }
            node = node.getNextSibling();
        }
        return false;
    }

    protected boolean hasAnyChildNode(NodeDetail nodeDetails, Set<String> names) {
        Node node = nodeDetails.getNode().getFirstChild();
        while (node != null) {
            if (names.contains(node.getNodeName())) {
                return true;
            }
            node = node.getNextSibling();
        }
        return false;
    }
}