com.c4om.autoconf.ulysses.extra.svrlmultipathinterpreter.PartialNodeGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.c4om.autoconf.ulysses.extra.svrlmultipathinterpreter.PartialNodeGenerator.java

Source

/*
Copyright 2014 Universidad Politcnica de Madrid - Center for Open Middleware (http://www.centeropenmiddleware.com)
    
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    
http://www.apache.org/licenses/LICENSE-2.0
    
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.c4om.autoconf.ulysses.extra.svrlmultipathinterpreter;

import static com.c4om.utils.xmlutils.JDOMUtils.loadJDOMDocumentFromFile;
import static com.c4om.utils.xmlutils.XMLLibsShortcuts.performJAXENXPath;
import static com.c4om.utils.xmlutils.XPathUtils.divideDocAndPath;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.Text;
import org.jdom2.filter.Filters;

import com.c4om.utils.xmlutils.JDOMUtils;
import com.c4om.utils.xmlutils.constants.AutoconfXMLConstants;

/**
 * This class generates partial nodes, whose content MUST be present in the added/repaced node (but are or may be incomplete).
 * @author Pablo Alonso Rodriguez (Center for Open Middleware - UPM)
 *
 */
public class PartialNodeGenerator {

    /**
     * This regexp matches a single token on a simple XPath path to an element, as we will use to generate partial nodes.
     */
    private static final String REGEXP_PATH_TOKEN = "/(?<qname>(?:(?<pref>[a-zA-Z_][a-zA-Z0-9_\\.\\-]*):)?(?<name>[a-zA-Z_\\.][a-zA-Z0-9_\\.\\-]*))(?:\\[(?<filter>(?:[^\\[\\]]|count\\((?:\\./)?\\Q@*[\\E[^\\[\\]]+\\])+)\\])?";

    /**
     * Each XPath simple path to elements used at this generator must match this regular expression to be correct.
     */
    private static final String REGEXP_PATH = "^\\.?(" + REGEXP_PATH_TOKEN + ")+$";

    /**
     * This regexp checks that a filter content is a list of attribute conditions (some attributes are equal to certain values and, optionally, the count of them is equal to a certain number).
     */
    private static final String REGEXP_ATTRIBUTES_FILTER = "^(?:(?:\\./)?(?:(?:@(?:[a-zA-Z_][a-zA-Z0-9_\\.\\-]*:)?(?:[a-zA-Z_][a-zA-Z0-9_\\.\\-]*))|text\\(\\)) *= *(?<q>['\"]).*\\k<q> *and *)*(?:count\\((?:\\./)?\\Q@*\\E(?:\\[.+\\])?\\) *= *[0-9]+ *|(?:\\./)?(?:(?:@(?:[a-zA-Z_][a-zA-Z0-9_\\.\\-]*:)?(?:[a-zA-Z_][a-zA-Z0-9_\\.\\-]*))|text\\(\\)) *= *(?<r>['\"]).*\\k<r>)$";

    /**
     * This regex matches any token of the form ".[./text() = '<i>something</i>']" (being flexible with quotes and spaces).
     */
    private static final String REGEXP_TEXT_AT_CURRENT_NODE = "^/.\\[(?:\\./)?text\\(\\) *= *(?<q>['\"])(?<textValue>.*)\\k<q>\\]";

    /**
     * A {@link Set} of {@link Namespace} objects, that represent the prefix-namespace mappings used during all the XPath queries and other methods.
     */
    private Set<Namespace> xpathNamespaces;

    /**
     * The path to the runtime configuration
     */
    private String pathToConfiguration;

    /**
     * The path to the metamodel
     */
    private String pathToMetamodel;

    /**
     * Prefix for some attributes of the metamodel (typically, 'mct')
     */
    private String metamodelAttributesPrefix;

    /**
     * This method checks that a path is valid and decomposes it into its tokens
     * @param path a valid simple path to elements
     * @return a list of path tokens
     * @throws IllegalArgumentException if <i>path</i> is not a valid path
     */
    private static List<String> decomposePathTokens(String path) {
        if (!path.matches(REGEXP_PATH)) {
            throw new IllegalArgumentException("path '" + path + "' is not valid");
        }
        List<String> result = new ArrayList<>();
        Pattern pattern = Pattern.compile(REGEXP_PATH_TOKEN);
        Matcher matcher = pattern.matcher(path);
        while (matcher.find()) {
            result.add(matcher.group());
        }
        return result;
    }

    /**
     * This method generates a XML element (as a JDOM2 element) from a XPath path token of the form
     * \/<i>qualified name</i>[<i>attributesFilter</i>]
     * @param pathToken the path token
     * @param namespaces a {@link Collection} of {@link Namespace} objects, describing the known prefix-namespace mappings
     * @return a {@link Element} object as described by the pathToken
     * @throws IllegalArgumentException if the path token is invalid and/or there are errors at the filter as described by {@link JDOMUtils#generateAttributeListFromFilter(String, Collection)}
     * @throws IndexOutOfBoundsException if the filter specifies an attribute count token and its number does not match the number of attributes, as described by {@link JDOMUtils#generateAttributeListFromFilter(String, Collection)}
     * @see JDOMUtils#generateAttributeListFromFilter(String, Collection)
     */
    private static Element generateElementFromPathToken(String pathToken, Collection<Namespace> namespaces) {
        Pattern pattern = Pattern.compile("^" + REGEXP_PATH_TOKEN + "$");
        Matcher matcher = pattern.matcher(pathToken);
        if (!matcher.matches()) {
            throw new IllegalArgumentException("Invalid path token: " + pathToken);
        }
        String name = matcher.group("name");
        String pref = matcher.group("pref");
        String filter = matcher.group("filter");

        if (filter != null && !filter.matches(REGEXP_ATTRIBUTES_FILTER)) {
            throw new IllegalArgumentException("Invalid path filter: " + filter);
        }
        Namespace namespace = JDOMUtils.solveNamespaceForPrefix(pref, namespaces);
        //Namespace not found
        if (namespace == null) {
            throw new IllegalArgumentException(
                    "Namespace prefix '" + pref + "' at path token is not defined at namespaces parameter.");
        }
        Element result = new Element(name, namespace);
        if (filter != null) {
            List<Attribute> attributeList = JDOMUtils.generateAttributeListFromFilter(filter, namespaces);
            result.setAttributes(attributeList);
        }

        return result;
    }

    /**
     * Gets a list of selectable values from a single select from path.
     * @param singleSelectFromPath a path of the form <code>doc('mydoc')/mypath</code>, that points to the selectable values.
     * @return a {@link List} with the selectable values
     * @throws IOException If there are I/O errors while loading documents to select values from.
     * @throws JDOMException If there are problems at XML parsing while loading documents to select values from.
     */
    private List<String> getSelectableValuesFromSinglePath(String singleSelectFromPath)
            throws JDOMException, IOException {
        String[] selectFromDocAndPath = divideDocAndPath(singleSelectFromPath);
        File documentFile = new File(selectFromDocAndPath[0]);
        if (!documentFile.isAbsolute()) {
            documentFile = new File(this.pathToConfiguration, selectFromDocAndPath[0]);
        }
        Document documentToSelectFrom = loadJDOMDocumentFromFile(documentFile);

        List<String> gatheredPossibleValues = new ArrayList<>();

        if (selectFromDocAndPath[1].matches(".+/@[^@/]+$")) {
            //It is an attribute path
            List<Attribute> selectableValuesQueryResult = performJAXENXPath(selectFromDocAndPath[1],
                    documentToSelectFrom, Filters.attribute(), xpathNamespaces);
            for (int i = 0; i < selectableValuesQueryResult.size(); i++) {
                gatheredPossibleValues.add(selectableValuesQueryResult.get(i).getValue());
            }
        } else {
            //It is a text path
            List<Text> selectableValuesQueryResult = performJAXENXPath(selectFromDocAndPath[1],
                    documentToSelectFrom, Filters.text(), xpathNamespaces);
            for (int i = 0; i < selectableValuesQueryResult.size(); i++) {
                gatheredPossibleValues.add(selectableValuesQueryResult.get(i).getTextNormalize());
            }
        }
        return gatheredPossibleValues;
    }

    /**
     * Given a selectFromPath, it queries the runtime configuration to get the selectable values pointed by that path
     * and generates the met:possibleValues element that describes it.
     * @param selectFromPath the path to select from (including a document function, relative to the runtime configuration)
     * @param metamodelDoc the metamodel document, to extract probabilities from.
     * @param originalPath the path of the original element at the document
     * @return the desired met:possibleValues
     * @throws IOException If there are I/O errors while loading documents to select values from.
     * @throws JDOMException If there are problems at XML parsing while loading documents to select values from.
     */
    private Element getPossibleValuesElement(String selectFromPath, Document metamodelDoc, String originalPath)
            throws JDOMException, IOException {
        Element possibleValuesElement = new Element("possibleValues",
                AutoconfXMLConstants.NAMESPACE_AUTOCONF_METADATA);

        List<String> gatheredPossibleValues = new ArrayList<>();

        if (selectFromPath.contains("|")) {
            String[] singlePaths = selectFromPath.split("\\|");
            for (int i = 0; i < singlePaths.length; i++) {
                String singlePath = singlePaths[i];
                gatheredPossibleValues.addAll(getSelectableValuesFromSinglePath(singlePath));
            }
        } else {
            gatheredPossibleValues = getSelectableValuesFromSinglePath(selectFromPath);
        }

        for (String possibleValue : gatheredPossibleValues) {
            Element currentPosibleValueElement = new Element("possibleValue",
                    AutoconfXMLConstants.NAMESPACE_AUTOCONF_METADATA);
            String queryFrequencyStr = "sum(" + originalPath + "/" + metamodelAttributesPrefix + "Value[./@"
                    + metamodelAttributesPrefix + "ValueAttr='" + possibleValue + "']/@" + metamodelAttributesPrefix
                    + "Frequency)";
            String queryParentFrequencyStr = "sum(" + originalPath + "/@" + metamodelAttributesPrefix
                    + "Frequency)";
            List<Double> queryFrequencyResults;
            List<Double> queryParentFrequencyResults;
            if (metamodelDoc != null) {
                queryFrequencyResults = performJAXENXPath(queryFrequencyStr, metamodelDoc, Filters.fdouble());
                queryParentFrequencyResults = performJAXENXPath(queryParentFrequencyStr, metamodelDoc,
                        Filters.fdouble());

            } else {
                queryFrequencyResults = Collections.emptyList();
                queryParentFrequencyResults = Collections.emptyList();
            }

            if (queryParentFrequencyResults.size() > 0 && queryFrequencyResults.size() > 0) {
                double myFrequency = queryFrequencyResults.get(0);
                double myParentFrequency = queryParentFrequencyResults.get(0);
                double probability = myFrequency / myParentFrequency;
                String formattedProbability = String.format(Locale.US, "%.2f", probability);
                if (!formattedProbability.equalsIgnoreCase("nan")) {
                    Attribute probabilityAttribute = new Attribute("probability", formattedProbability);
                    currentPosibleValueElement.setAttribute(probabilityAttribute);
                }
            }

            currentPosibleValueElement.setText(possibleValue);
            possibleValuesElement.addContent(currentPosibleValueElement);
        }

        return possibleValuesElement;
    }

    /**
     * Constructor
     * @param xpathNamespaces the namespace-prefix mappings, as a {@link Collection} of {@link Namespace} objects
     * @param pathToConfiguration the path to the runtime configuration
     * @param pathToMetamodel the path to the metamodel
     * @param metamodelAttributesPrefix prefix for some metamodel-specific attributes
     */
    public PartialNodeGenerator(Collection<Namespace> xpathNamespaces, String pathToConfiguration,
            String pathToMetamodel, String metamodelAttributesPrefix) {
        this.xpathNamespaces = new HashSet<>(xpathNamespaces);
        this.pathToConfiguration = pathToConfiguration;
        this.pathToMetamodel = pathToMetamodel;
    }

    /**
     * Method that, given a {@link PathGroup} object (representing a group of paths), generates a partial element (as an {@link Element} object) with all the information present at it.
     * @param pathGroup the {@link PathGroup} object
     * @return the partial element 
     * @throws IOException If there are I/O errors while loading documents to select values from.
     * @throws JDOMException If there are problems at XML parsing while loading documents to select values from.
     * @throws IllegalArgumentException if the basePath or any subpath is invalid
     * @throws IndexOutOfBoundsException if a path token filter contains a count token and the number of attributes does not match.
     */
    public Element generatePartialNode(PathGroup pathGroup) throws JDOMException, IOException {
        File metamodelDocFile = new File(pathGroup.getDocumentPath());
        if (!metamodelDocFile.isAbsolute()) {
            metamodelDocFile = new File(this.pathToMetamodel, pathGroup.getDocumentPath());
        }
        Document metamodelDoc;
        try {
            metamodelDoc = loadJDOMDocumentFromFile(metamodelDocFile);
        } catch (IOException e) {
            metamodelDoc = null;
        }

        List<String> basePathTokens = decomposePathTokens(pathGroup.getBasePath());
        String basePathToken = basePathTokens.get(basePathTokens.size() - 1);
        Pattern textAtCurrentNodePattern = Pattern.compile(REGEXP_TEXT_AT_CURRENT_NODE);
        Matcher textAtCurrentBasePathMatcher = textAtCurrentNodePattern.matcher(basePathToken);
        Element partialNode;
        if (textAtCurrentBasePathMatcher.matches()) {
            String text = textAtCurrentBasePathMatcher.group("textValue");
            basePathToken = basePathTokens.get(basePathTokens.size() - 2);
            partialNode = generateElementFromPathToken(basePathToken, xpathNamespaces);
            partialNode.setText(text);
        } else {
            partialNode = generateElementFromPathToken(basePathToken, xpathNamespaces);
        }
        String basePathSelectFrom = pathGroup.getBasePathSelectFrom();
        if (basePathSelectFrom != null && !(basePathSelectFrom.equals(""))) {
            partialNode.setText("");
            Element possibleValuesElement = getPossibleValuesElement(basePathSelectFrom, metamodelDoc,
                    pathGroup.getBasePath());
            partialNode.addContent(possibleValuesElement);
        }
        for (String subpath : pathGroup.getSubPaths()) {
            List<String> subpathTokens = decomposePathTokens(subpath);
            Element currentElement = partialNode;
            for (int i = 0; i < subpathTokens.size(); i++) {
                String currentSubpathToken = subpathTokens.get(i);
                Matcher textAtCurrentNodeMatcher = textAtCurrentNodePattern.matcher(currentSubpathToken);
                if (textAtCurrentNodeMatcher.matches()) {
                    String text = textAtCurrentNodeMatcher.group("textValue");
                    currentElement.setText(text);
                    continue;
                }
                List<Element> newChildList = performJAXENXPath("./" + currentSubpathToken, currentElement);
                if (newChildList.size() > 1) {
                    throw new IllegalArgumentException(
                            "Subpath token '" + currentSubpathToken + "' yields more than one child");
                }
                Element newChild;
                if (newChildList.size() == 1) {
                    newChild = newChildList.get(0);
                } else {
                    newChild = generateElementFromPathToken(currentSubpathToken, xpathNamespaces);
                    currentElement.addContent(newChild);
                }
                currentElement = newChild;
            }
            String subpathSelectFrom = pathGroup.getSubPathSelectFrom(subpath);
            if (subpathSelectFrom != null && !(subpathSelectFrom.equals(""))) {
                currentElement.setText("");
                Element possibleValuesElement = getPossibleValuesElement(subpathSelectFrom, metamodelDoc,
                        pathGroup.getBasePath() + "/" + subpath);
                currentElement.addContent(possibleValuesElement);
            }
        }
        return partialNode;
    }

}