All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.devonfw.cobigen.xmlplugin.matcher.XmlMatcher Maven / Gradle / Ivy

package com.devonfw.cobigen.xmlplugin.matcher;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.devonfw.cobigen.api.exception.CobiGenRuntimeException;
import com.devonfw.cobigen.api.exception.InvalidConfigurationException;
import com.devonfw.cobigen.api.extension.MatcherInterpreter;
import com.devonfw.cobigen.api.to.MatcherTo;
import com.devonfw.cobigen.api.to.VariableAssignmentTo;
import com.devonfw.cobigen.xmlplugin.inputreader.XmlInputReader;

/** {@link MatcherInterpreter} for XML matcher configurations. */
public class XmlMatcher implements MatcherInterpreter {

    /** Assigning logger to XmlClassMatcher */
    private static final Logger LOG = LoggerFactory.getLogger(XmlMatcher.class);

    /** XPath object to evaluate xpath expressions with */
    private static final XPath XPathObj = XPathFactory.newInstance().newXPath();

    /** Currently supported matcher types */
    private enum MatcherType {
        /** Document's root name */
        NODENAME,
        /** Should match if input is a Document */
        XPATH
    }

    /** Available variable types for the matcher */
    private enum VariableType {
        /** Constant variable assignment */
        CONSTANT,
        /** Xpath expression group assignment */
        XPATH
    }

    @Override
    public boolean matches(MatcherTo matcher) {
        try {
            MatcherType matcherType = Enum.valueOf(MatcherType.class, matcher.getType().toUpperCase());
            Object target = matcher.getTarget();
            switch (matcherType) {
            case NODENAME:
                if (target instanceof Document) {
                    String documentRootName = ((Document) target).getDocumentElement().getNodeName();
                    return documentRootName != null && !documentRootName.equals("")
                        && matcher.getValue().matches(documentRootName);
                }
                break;
            case XPATH:
                Node targetNode = getDocElem(target, 1);
                XPath xPath = createXpathObject(target);
                String xpathExpression = matcher.getValue();
                try {
                    return (boolean) xPath.evaluate(xpathExpression, targetNode, XPathConstants.BOOLEAN);
                } catch (XPathExpressionException e) {
                    if (checkXPathSyntax(xpathExpression)) {
                        return false;
                    }
                    throw new InvalidConfigurationException(xpathExpression, "Invalid XPath expression", e);
                }
            }
        } catch (IllegalArgumentException e) {
            throw new CobiGenRuntimeException("Matcher type " + matcher.getType() + " not registered!", e);
        }
        return false;
    }

    /**
     * Checks whether a given XPath syntax is correct or not.
     * @param xpathExpression
     *            the xPath expression
     * @return true if the syntax is correct
     */
    private boolean checkXPathSyntax(String xpathExpression) {
        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();
        try {
            xpath.compile(xpathExpression);
        } catch (XPathExpressionException e) {
            return false;
        }
        return true;
    }

    /**
     * Creates a new namespace aware XPath object for xpath evaluation
     * @param target
     *            the matcher target
     * @return the created {@link XPath} object
     */
    private XPath createXpathObject(Object target) {
        Node fullDoc = getDocElem(target, 0);
        XPathObj.setNamespaceContext(new NamespaceResolver(fullDoc));
        return XPathObj;
    }

    /**
     * Returns the document provided in the input object. Either the object is an {@link Document}, or an
     * {@link Document} array. In the latter case, the document at the preferred index is returned. As of
     * {@link XmlInputReader#getInputObjects(Object, java.nio.charset.Charset)}, the preferred index 0 points
     * at the full {@link Document} and the preferred index 1 points at the partial {@link Document} in case
     * of an array as input.
     * @param target
     *            generator input, which has to be a {@link Document} or {@link Document} array dependent on
     *            the return types of {@link XmlInputReader#getInputObjects(Object, java.nio.charset.Charset)}
     * @param preferredIndex
     *            index pointing at the document to be returned in case of {@link Document} array as target
     * @return the preferred {@link Document}
     */
    private Node getDocElem(Object target, int preferredIndex) {
        Node targetDoc;
        if (target instanceof Document) {
            targetDoc = ((Document) target).getDocumentElement();
        } else if (target instanceof Node[]) {
            return ((Node[]) target)[preferredIndex];
        } else {
            throw new IllegalArgumentException(
                "Unknown input object of type " + target.getClass() + " in matcher execution.");
        }
        return targetDoc;
    }

    @Override
    public Map resolveVariables(MatcherTo matcher, List variableAssignments)
        throws InvalidConfigurationException {

        Map resolvedVariables = new HashMap<>();
        try {
            MatcherType matcherType = Enum.valueOf(MatcherType.class, matcher.getType().toUpperCase());
            switch (matcherType) {
            case NODENAME:
                for (VariableAssignmentTo va : variableAssignments) {
                    VariableType variableType = Enum.valueOf(VariableType.class, va.getType().toUpperCase());
                    switch (variableType) {
                    case CONSTANT:
                        resolvedVariables.put(va.getVarName(), va.getValue());
                        break;
                    case XPATH:
                        resolvedVariables.put(va.getVarName(),
                            resolveVariablesXPath(getDocElem(matcher.getTarget(), 1), va.getValue()));
                        break;
                    }
                }
                return resolvedVariables;
            default:
                break;
            }
            // In case of being an XMI document
            for (VariableAssignmentTo va : variableAssignments) {
                VariableType variableType = Enum.valueOf(VariableType.class, va.getType().toUpperCase());
                switch (variableType) {
                case XPATH:
                    resolvedVariables.put(va.getVarName(),
                        resolveVariablesXPath(getDocElem(matcher.getTarget(), 1), va.getValue()));
                    break;
                case CONSTANT:
                    resolvedVariables.put(va.getVarName(), va.getValue());
                    break;
                }

            }
            return resolvedVariables;
        } catch (IllegalArgumentException e) {
            throw new CobiGenRuntimeException(
                "Matcher or VariableAssignment type " + matcher.getType() + " not registered!", e);
        }
    }

    /**
     * Resolves the given xpath expression on the given doc.
     * @param doc
     *            target document
     * @param xpathExpression
     *            xpath expression
     * @return the text content of the first node resulting from the xpath or the empty string if the xpath
     *         results in an empty list
     */
    private String resolveVariablesXPath(Node doc, String xpathExpression) {
        XPath xPath = XPathFactory.newInstance().newXPath();
        LOG.debug("Evaluating xpath {}", xpathExpression);
        try {
            NodeList list = (NodeList) xPath.evaluate(xpathExpression, doc, XPathConstants.NODESET);
            if (list.getLength() > 0) {
                // currently, we just allow strings as variable assignment values
                LOG.debug("... found {} nodes.", list.getLength());
                return list.item(0).getTextContent();
            } else {
                LOG.debug("... nothing found.", list.getLength());
                return ""; // we have to return empty string as of Variables restrictions of cobigen-core
            }
        } catch (XPathExpressionException e) {
            throw new CobiGenRuntimeException("Could not evaluate xpath expression " + xpathExpression, e);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy