com.c4om.utils.xmlutils.JDOMUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.c4om.utils.xmlutils.JDOMUtils.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.utils.xmlutils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
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.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.LineSeparator;
import org.jdom2.output.XMLOutputter;

/**
 * Static methods on this class help with some common tasks performed while working 
 * with JDOM.
 * @author Pablo Alonso Rodrguez (Center for Open Middleware - UPM)
 *
 */
public class JDOMUtils {

    /**
     * This regexp matches any attribute token, which sets a condition under its value
     */
    static final String REGEXP_ATTRIBUTES_FILTER_ATTRIBUTE_TOKEN = "^(?:\\./)?@(?<qname>(?:(?<pref>[a-zA-Z_][a-zA-Z0-9_\\.\\-]*):)?(?<name>[a-zA-Z_][a-zA-Z0-9_\\.\\-]*)) *= *(?<q>['\"])(?<value>.*)\\k<q>$";

    /**
     * This regexp matches any count token, which states how many attributes must there be.
     */
    static final String REGEXP_ATTRIBUTES_FILTER_COUNT_TOKEN = "^count\\((?:\\./)?\\Q@*\\E(?:\\[(?<countFilter>.+)\\])?\\) *= *(?<value>[0-9]+)$";

    /**
     * Method that checks whether two elements are the same in the following way:
     * <ul>
     * <li>Have the same name</li>
     * <li>Have the same namespace URI</li>
     * <li>Have the same attribute sets (names, namespaces and values)</li>
     * </ul>
     * @param element1 one element
     * @param element2 another element
     * @return the result of the comparison
     */
    public static boolean elementsEqualAtCNameAndAttributes(Element element1, Element element2) {
        boolean haveSameName = element1.getName().equals(element2.getName());
        boolean haveSameNamespaceURI = element1.getNamespaceURI().equals(element2.getNamespaceURI());
        Set<Attribute> attrSet1 = new HashSet<>(element1.getAttributes());
        Set<Attribute> attrSet2 = new HashSet<>(element2.getAttributes());
        boolean attrSetsEquals = attrSet1.equals(attrSet2);
        return haveSameName && haveSameNamespaceURI & attrSetsEquals;
    }

    /**
     * This method looks at a {@link Collection} of {@link Element} objects and returns the first one 
     * such that the method {@link JDOMUtils#elementsEqualAtCNameAndAttributes(Element, Element)} 
     * returns true for it and another given element
     * @param elementsToLookAt the collection where the element will be searched
     * @param otherElement the element to compare to
     * @return the searched element, if it exists. Otherwise, it returns null.
     */
    public static Element searchElementEqualAtCNameAndAttributes(Collection<Element> elementsToLookAt,
            Element otherElement) {
        for (Element element : elementsToLookAt) {
            if (elementsEqualAtCNameAndAttributes(element, otherElement)) {
                return element;
            }
        }
        return null;
    }

    /**
     * Loads a XML file as a JDOM Document
     * @param file the file to load
     * @return the loaded document
     * @throws IOException Input/Output errors.
     * @throws JDOMException Errors at XML parsing. 
     */
    public static Document loadJDOMDocumentFromFile(File file) throws JDOMException, IOException {
        if (!file.exists())
            throw new FileNotFoundException(file + " not found");
        InputStream is = new FileInputStream(file);
        return loadJDOMDocumentFromStream(is);
    }

    /**
     * Loads a XML file from a stream as a JDOM Document
     * @param is the input stream
     * @return the loaded document
     * @throws IOException Input/Output errors.
     * @throws JDOMException Errors at XML parsing. 
     */
    public static Document loadJDOMDocumentFromStream(InputStream is) throws JDOMException, IOException {
        SAXBuilder builder = new SAXBuilder();
        return builder.build(is);
    }

    /**
     * Loads a XML file from a string as a JDOM Document
     * @param s the input string
     * @return the loaded document
     * @throws IOException Input/Output errors.
     * @throws JDOMException Errors at XML parsing. 
     */
    public static Document loadJDOMDocumentFromString(String s) throws JDOMException, IOException {
        SAXBuilder builder = new SAXBuilder();
        return builder.build(new StringReader(s));
    }

    /**
     * Method that converts a JDOM2 {@link Document} to String
     * @param document the document
     * @return the string
     */
    public static String convertJDOMDocumentToString(Document document) {
        Format xmlFormat = Format.getPrettyFormat();
        xmlFormat.setLineSeparator(LineSeparator.SYSTEM);
        XMLOutputter outputter = new XMLOutputter(xmlFormat);
        String result = outputter.outputString(document);
        return result;
    }

    /**
     * Given a {@link Collection} of {@link Namespace} objects, it returns the first one (as returned by its iterator) 
     * whose prefix is equal to a given one 
     * @param prefix the prefix whose namespace we are looking for. It may be null, to look for the null namespace.
     * @param namespaces the collection of namespace
     * @return the corresponding {@link Namespace} object if found, the null namespace (i.e. {@link Namespace#NO_NAMESPACE}) if prefix is null or "" (regardless of it belongs to the collection or not), null otherwise.
     */
    public static Namespace solveNamespaceForPrefix(String prefix, Collection<Namespace> namespaces) {
        Namespace namespace = null;
        if (prefix == null) {
            namespace = Namespace.NO_NAMESPACE;
            return namespace;
        } else {
            for (Namespace knownNS : namespaces) {
                if (knownNS.getPrefix().equals(prefix)) {
                    namespace = knownNS;
                    break;
                }
            }
        }
        if (prefix.equals("") && namespace == null) {
            namespace = Namespace.NO_NAMESPACE;
        }
        return namespace;
    }

    /**
     * This method converts a XPath attribute filter into an {@link Attribute} {@link List}. 
     * Count tokens are optional. However, if a count token is present, the number of attributes must match its count value.
     * @param filter the filter to convert (without initial '[' and final ']')
     * @param namespaces the {@link Namespace} objects with the uri-prefix mappings used at attributes
     * @return the list of attributes, as described
     * @throws IllegalArgumentException if one of the "and"-separated tokens is neither a count token nor an attribute token
     * @throws IndexOutOfBoundsException if the count token is present and does not match the number of attributes
     */
    public static List<Attribute> generateAttributeListFromFilter(String filter, Collection<Namespace> namespaces) {
        String filterTrimmed = filter.trim();
        String[] attributeTokens = filterTrimmed.split(" +and +");
        List<Attribute> attributeList = new ArrayList<Attribute>(attributeTokens.length);
        Pattern attributeTokenPattern = Pattern.compile(REGEXP_ATTRIBUTES_FILTER_ATTRIBUTE_TOKEN);
        Pattern countTokenPattern = Pattern.compile(REGEXP_ATTRIBUTES_FILTER_COUNT_TOKEN);
        int knownCount = -1; //-1 means unknown
        for (int i = 0; i < attributeTokens.length; i++) {
            String currentToken = attributeTokens[i].trim();
            Matcher countMatcher = countTokenPattern.matcher(currentToken);
            if (countMatcher.matches()) {
                //It is a count token
                knownCount = Integer.parseInt(countMatcher.group("value"));
            } else {
                Matcher attributeMatcher = attributeTokenPattern.matcher(currentToken);
                if (!attributeMatcher.matches()) {
                    throw new IllegalArgumentException("Invalid filter '" + filterTrimmed + "' since token '"
                            + currentToken + "' is neither a count token nor an attribute token.");
                }
                String attrName = attributeMatcher.group("name");
                String attrPref = attributeMatcher.group("pref");
                String attrValue = attributeMatcher.group("value");
                Namespace attrNamespace = solveNamespaceForPrefix(attrPref, namespaces);
                //Namespace not found
                if (attrNamespace == null) {
                    throw new IllegalArgumentException("Namespace prefix '" + attrPref
                            + "' at path token is not defined at namespaces parameter.");
                }

                Attribute currentAttribute = new Attribute(attrName, attrValue, attrNamespace);
                attributeList.add(currentAttribute);
            }
        }
        if (knownCount >= 0 && attributeList.size() != knownCount) {
            throw new IndexOutOfBoundsException("Count value '" + knownCount
                    + "' does not match attribute list size '" + attributeList.size() + "'");
        }
        return attributeList;
    }

    private JDOMUtils() {
    }

}