org.eclipse.winery.common.Util.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.winery.common.Util.java

Source

/*******************************************************************************
 * Copyright (c) 2013 University of Stuttgart.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and the Apache License 2.0 which both accompany this distribution,
 * and are available at http://www.eclipse.org/legal/epl-v10.html
 * and http://www.apache.org/licenses/LICENSE-2.0
 *
 * Contributors:
 *     Oliver Kopp - initial API and implementation
 *******************************************************************************/
package org.eclipse.winery.common;

import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.namespace.QName;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang3.StringUtils;
import org.apache.taglibs.standard.functions.Functions;
import org.eclipse.winery.common.ids.GenericId;
import org.eclipse.winery.common.ids.definitions.ArtifactTemplateId;
import org.eclipse.winery.common.ids.definitions.EntityTemplateId;
import org.eclipse.winery.common.ids.definitions.EntityTypeId;
import org.eclipse.winery.common.ids.definitions.EntityTypeImplementationId;
import org.eclipse.winery.common.ids.definitions.PolicyTemplateId;
import org.eclipse.winery.common.ids.definitions.ServiceTemplateId;
import org.eclipse.winery.common.ids.definitions.TOSCAComponentId;
import org.eclipse.winery.common.ids.definitions.imports.GenericImportId;
import org.eclipse.winery.common.ids.definitions.imports.XSDImportId;
import org.eclipse.winery.common.ids.elements.TOSCAElementId;
import org.eclipse.winery.model.tosca.TEntityType;
import org.eclipse.winery.model.tosca.TExtensibleElements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

public class Util {

    private static final Logger logger = LoggerFactory.getLogger(Util.class);

    public static final String FORBIDDEN_CHARACTER_REPLACEMENT = "_";

    public static String URLdecode(String s) {
        try {
            return URLDecoder.decode(s, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException();
        }
    }

    public static String URLencode(String s) {
        try {
            return URLEncoder.encode(s, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException();
        }
    }

    public static String DoubleURLencode(String s) {
        return Util.URLencode(Util.URLencode(s));
    }

    /**
     * Encodes the namespace and the localname of the given qname, separated by
     * "/"
     * 
     * @return <double encoded namespace>"/"<double encoded localname>
     */
    public static String DoubleURLencode(QName qname) {
        String ns = Util.DoubleURLencode(qname.getNamespaceURI());
        String localName = Util.DoubleURLencode(qname.getLocalPart());
        return ns + "/" + localName;
    }

    public static boolean isRelativeURI(String uri) {
        URI u;
        try {
            u = new URI(uri);
        } catch (URISyntaxException e) {
            Util.logger.debug(e.getMessage(), e);
            // fallback
            return false;
        }
        return !u.isAbsolute();
    }

    /**
     * @param c the element directly nested below a definitions element in XML
     */
    public static String getURLpathFragmentForCollection(Class<? extends TExtensibleElements> c) {
        String res = c.getName().toLowerCase();
        int lastDot = res.lastIndexOf('.');
        // classname is something like <package>.T<type>. We are only interested
        // in "<type>". Therefore "+2" from the dot onwards
        res = res.substring(lastDot + 2);
        res = res + "s";
        return res;
    }

    public static String getEverythingBetweenTheLastDotAndBeforeId(Class<? extends GenericId> cls) {
        String res = cls.getName();
        // Everything between the last "." and before "Id" is the Type
        int dotIndex = res.lastIndexOf('.');
        assert (dotIndex >= 0);
        return res.substring(dotIndex + 1, res.length() - "Id".length());
    }

    public static String getTypeForElementId(Class<? extends TOSCAElementId> idClass) {
        return Util.getEverythingBetweenTheLastDotAndBeforeId(idClass);
    }

    /**
     * @return Singular type name for the given id. E.g., "ServiceTemplateId"
     *         gets "ServiceTemplate"
     */
    public static String getTypeForComponentId(Class<? extends TOSCAComponentId> idClass) {
        return Util.getEverythingBetweenTheLastDotAndBeforeId(idClass);
    }

    /**
     * Returns the root path fragment for the given
     * AbstractComponentIntanceResource
     * 
     * With trailing slash
     * 
     * @return [ComponentName]s/
     */
    public static String getRootPathFragment(Class<? extends TOSCAComponentId> idClass) {
        // quick handling of imports special case
        // in the package naming, all other component instances have a this intermediate location, but not in the URLs
        // The package handling is in {@link org.eclipse.winery.repository.Utils.getIntermediateLocationStringForType(String, String)}
        String res;
        if (GenericImportId.class.isAssignableFrom(idClass)) {
            // this fires if idClass is a sub class from ImportCollectionId
            // special treatment for imports
            res = "imports/";
            if (XSDImportId.class.isAssignableFrom(idClass)) {
                res = res + "http%3A%2F%2Fwww.w3.org%2F2001%2FXMLSchema/";
            } else {
                throw new IllegalStateException(
                        "Not possible to determine local storage for generic imports class");
            }
            // we have the complete root path fragment
            return res;
        } else {
            res = "";
        }
        res = res + Util.getTypeForComponentId(idClass);
        res = res.toLowerCase();
        res = res + "s";
        res = res + "/";
        return res;
    }

    /**
     * Just calls @link{qname2href}
     * 
     * Introduced because of JSP error
     * "The method qname2href(String, Class<? extends TExtensibleElements>, QName) in the type Util is not applicable for the arguments (String, Class<TNodeType>, QName, String)"
     */
    public static String qname2hrefWithName(String repositoryUrl, Class<? extends TExtensibleElements> element,
            QName qname, String name) {
        return Util.qname2href(repositoryUrl, element, qname, name);
    }

    /**
     * 
     * @param repositoryUrl the URL to the repository
     * @param element the element directly nested below a definitions element in
     *            XML
     * @param qname the QName of the element
     * @param name (optional) if not null, the name to display as text in the
     *            reference. Default: localName of the QName
     * @return an <code>a</code> HTML element pointing to the given id
     */
    public static String qname2href(String repositoryUrl, Class<? extends TExtensibleElements> element, QName qname,
            String name) {
        if (StringUtils.isEmpty(repositoryUrl)) {
            throw new IllegalArgumentException("Repository URL must not be empty.");
        }
        if (element == null) {
            throw new IllegalArgumentException("Element class must not be null.");
        }
        if (qname == null) {
            return "(none)";
        }

        String absoluteURL = repositoryUrl + "/" + Util.getURLpathFragmentForCollection(element) + "/"
                + Util.DoubleURLencode(qname.getNamespaceURI()) + "/" + Util.DoubleURLencode(qname.getLocalPart());

        if (name == null) {
            // fallback if no name is given
            name = qname.getLocalPart();
        }
        // sanitize name
        name = Functions.escapeXml(name);

        String res = "<a target=\"_blank\" data-qname=\"" + qname + "\" href=\"" + absoluteURL + "\">" + name
                + "</a>";
        return res;
    }

    /**
     * 
     * @param repositoryUrl the URL to the repository
     * @param element the element directly nested below a definitions element in
     *            XML
     * @param qname the QName of the element
     * @return an <code>a</code> HTML element pointing to the given id
     */
    public static String qname2href(String repositoryUrl, Class<? extends TExtensibleElements> element,
            QName qname) {
        return Util.qname2href(repositoryUrl, element, qname, null);
    }

    /**
     * Returns a visual rendering of minInstances
     * 
     * @param minInstances the value to render
     */
    public static String renderMinInstances(Integer minInstances) {
        if ((minInstances == null) || (minInstances == 1)) {
            // == null: default value: display nothing -- *never* happens:
            // the function *always* returns 1 even, if no explicit value is set. Therefore, we also display "" if the default value 1 is set
            return "";
        } else {
            return Integer.toString(minInstances);
        }
    }

    /**
     * Returns a visual rendering of maxInstances
     * 
     * @param maxInstances the value to render
     */
    public static String renderMaxInstances(String maxInstances) {
        if ((maxInstances == null) || (maxInstances.equals("1"))) {
            // default value display nothing
            // "1" is returned even if no explicit value has been set.
            return "";
        } else if (maxInstances.equals("unbounded")) {
            return "&infin;";
        } else {
            // maxInstance is a plain integer
            // return as is
            return maxInstances;
        }
    }

    /**
     * @return the local name of a Class representing a TOSCA element
     */
    private static String getLocalName(@SuppressWarnings("rawtypes") Class clazz) {
        String localName = clazz.getName();
        // a class defined within another class is written as superclass$class. E.g., EntityTemplate$Properties
        // We use the real class name
        int pos = localName.lastIndexOf('$');
        if (pos == -1) {
            pos = localName.lastIndexOf('.');
        }
        localName = localName.substring(pos + 1);
        if (localName.equals("TDocumentation")) {
            // special case for documentation: the local name starts with a lower case letter
            localName = "documentation";
        } else if (localName.startsWith("T")) {
            localName = localName.substring(1);
        }
        return localName;
    }

    public static <T extends Object> JAXBElement<T> getJAXBElement(Class<T> clazz, T obj) {
        String namespace = null;
        XmlRootElement xmlRootElement = clazz.getAnnotation(XmlRootElement.class);
        if (xmlRootElement != null) {
            namespace = xmlRootElement.namespace();
            if ("##default".equals(namespace)) {
                XmlSchema xmlSchema = clazz.getPackage().getAnnotation(XmlSchema.class);
                if (xmlSchema != null) {
                    namespace = xmlSchema.namespace();
                } else {
                    // trigger default handling
                    namespace = null;
                }
            }
        }
        if (namespace == null) {
            // fallback non-specified namespaces
            namespace = org.eclipse.winery.common.constants.Namespaces.TOSCA_NAMESPACE;
        }
        String localName = Util.getLocalName(clazz);
        QName qname = new QName(namespace, localName);
        JAXBElement<T> rootElement = new JAXBElement<T>(qname, clazz, obj);
        return rootElement;
    }

    /**
     * Method similar to {@link
     * org.eclipse.winery.repository.Utils.getXMLAsString(Class, Object)}.
     * 
     * Differences:
     * <ul>
     * <li>XML processing instruction is not included in the header</li>
     * <li>JAXBcontext is created at each call</li>
     * </ul>
     */
    public static <T extends Object> String getXMLAsString(Class<T> clazz, T obj) throws Exception {
        // copied from Utils java, but we create an own JAXBcontext here
        // JAXBSupport cannot be used as this relies on a MockElement, which we do not want to factor out to winery.common

        JAXBContext context;
        try {
            // For winery classes, eventually the package+jaxb.index method could be better. See http://stackoverflow.com/a/3628525/873282
            // @formatter:off
            context = JAXBContext.newInstance(TEntityType.class);
            // @formatter:on
        } catch (JAXBException e) {
            throw new IllegalStateException(e);
        }

        JAXBElement<T> rootElement = Util.getJAXBElement(clazz, obj);
        Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        m.setProperty(Marshaller.JAXB_FRAGMENT, true);
        // m.setProperty("com.sun.xml.bind.namespacePrefixMapper", JAXBSupport.prefixMapper);

        StringWriter w = new StringWriter();
        try {
            m.marshal(rootElement, w);
        } catch (JAXBException e) {
            throw new IllegalStateException(e);
        }
        String res = w.toString();
        return res;
    }

    public static String getXMLAsString(Element el) {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t;
        try {
            t = tf.newTransformer();
        } catch (TransformerConfigurationException e) {
            throw new IllegalStateException("Could not instantiate Transformer", e);
        }
        t.setOutputProperty(OutputKeys.INDENT, "yes");
        Source source = new DOMSource(el);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        Result target = new StreamResult(os);
        try {
            t.transform(source, target);
        } catch (TransformerException e) {
            Util.logger.debug(e.getMessage(), e);
            throw new IllegalStateException("Could not transform dom node to string", e);
        }
        return os.toString();
    }

    /**
     * Determines whether the instance belonging to the given id supports the
     * "name" attribute. This cannot be done using the super class as the TOSCA
     * specification treats that differently in the case of EntityTemplates
     * 
     * NOTE: The respective subclasses of AbstractComponentInstanceResource have
     * to implement {@link org.eclipse.winery.repository.resources.IHasName}
     * 
     * @param id the id to test
     * @return true if the TOSCA model class belonging to the given id supports
     *         the method "getName()" in addition to "getId()"
     */
    public static boolean instanceSupportsNameAttribute(Class<? extends TOSCAComponentId> idClass) {
        if (ServiceTemplateId.class.isAssignableFrom(idClass)) {
            return true;
        } else if ((EntityTypeId.class.isAssignableFrom(idClass))
                || (EntityTypeImplementationId.class.isAssignableFrom(idClass))) {
            // name is available, but no id attribute
            return false;
        } else if (GenericImportId.class.isAssignableFrom(idClass)) {
            return false;
        } else {
            assert (EntityTemplateId.class.isAssignableFrom(idClass));
            if (ArtifactTemplateId.class.isAssignableFrom(idClass)) {
                return true;
            } else if (PolicyTemplateId.class.isAssignableFrom(idClass)) {
                return true;
            } else {
                throw new IllegalStateException("Unimplemented branch to determine if getName() exists");
            }
        }
    }

    public static String getLastURIPart(String loc) {
        int posSlash = loc.lastIndexOf('/');
        String fileName = loc.substring(posSlash + 1);
        return fileName;
    }

    /**
     * Determines a color belonging to the given name
     */
    public static String getColor(String name) {
        int hash = name.hashCode();
        // trim to 3*8=24 bits
        hash = hash & 0xFFFFFF;
        // check if color is more than #F0F0F0, i.e., too light
        if (((hash & 0xF00000) >= 0xF00000)
                && (((hash & 0x00F000) >= 0x00F000) && ((hash & 0x0000F0) >= 0x0000F0))) {
            // set one high bit to zero for each channel. That makes the overall color darker
            hash = hash & 0xEFEFEF;
        }
        String colorStr = String.format("#%06x", hash);
        return colorStr;
    }

    /**
     * Determines the name of the CSS class used for relationshipTypes at
     * nodeTemplateRenderer.tag
     */
    public static String makeCSSName(String namespace, String localName) {
        // according to http://stackoverflow.com/a/79022/873282 everything is allowed
        // However, {namespace}id does NOT work
        String res = namespace + "_" + localName;
        res = res.replaceAll("[^\\w\\d_]", "_");
        return res;
    }

    /**
     * @see {@link org.eclipse.winery.common.Util.makeCSSName(String, String)}
     */
    public static String makeCSSName(QName qname) {
        return Util.makeCSSName(qname.getNamespaceURI(), qname.getLocalPart());
    }

    public static SortedMap<String, SortedSet<String>> convertQNameListToNamespaceToLocalNameList(
            List<QName> list) {
        SortedMap<String, SortedSet<String>> res = new TreeMap<>();
        for (QName qname : list) {
            SortedSet<String> localNameSet = res.get(qname.getNamespaceURI());
            if (localNameSet == null) {
                localNameSet = new TreeSet<>();
                res.put(qname.getNamespaceURI(), localNameSet);
            }
            localNameSet.add(qname.getLocalPart());
        }
        return res;
    }

    public static String namespaceToJavaPackage(String namespace) {
        URI uri;
        try {
            uri = new URI(namespace);
        } catch (URISyntaxException e) {
            Util.logger.debug(e.getMessage(), e);
            return "uri.invalid";
        }
        StringBuilder sb = new StringBuilder();

        String host = uri.getHost();
        if (host != null) {
            Util.addReversed(sb, host, "\\.");
        }

        String path = uri.getPath();
        if (!path.equals("")) {
            if (path.startsWith("/")) {
                // remove first slash
                path = path.substring(1);
            }

            // and then handle the string
            Util.addAsIs(sb, path, "/");
        }

        // remove the final dot
        sb.replace(sb.length() - 1, sb.length(), "");

        return Util.cleanName(sb.toString());
    }

    private static String cleanName(String s) {
        // TODO: Integrate with other name cleaning functions. "." should not be replaced as it is used as separator in the java package name
        // @formatter:off
        return s.replace(":", Util.FORBIDDEN_CHARACTER_REPLACEMENT)
                .replace("/", Util.FORBIDDEN_CHARACTER_REPLACEMENT)
                .replace(" ", Util.FORBIDDEN_CHARACTER_REPLACEMENT)
                .replace("-", Util.FORBIDDEN_CHARACTER_REPLACEMENT);
        // @formatter:on
    }

    /*
    * Valid chars: See
    * <ul>
    * <li>http://www.w3.org/TR/REC-xml-names/#NT-NCName</li>
    * <li>http://www.w3.org/TR/REC-xml/#NT-Name</li>
    * </ul>
    */
    // NameCharRange \u10000-\ueffff is not supported by Java
    private static final String NCNameStartChar_RegExp = "[A-Z_a-z\u00c0-\u00d6\u00d8\u00f6\u00f8\u02ff\u0370\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]";
    private static final String NCNameChar_RegExp = Util.NCNameStartChar_RegExp
            + "|[-\\.0-9\u00B7\u0300-\u036F\u203F-\u2040]";
    private static final Pattern NCNameStartChar_Pattern = Pattern.compile(Util.NCNameStartChar_RegExp);
    private static final Pattern NCNameChar_RegExp_Pattern = Pattern.compile(Util.NCNameChar_RegExp);

    /**
     * Removes all non-NCName characters from the given string and returns the
     * result
     * 
     * This function should be consistent with
     * org.eclipse.winery.common.Util.cleanName(String)
     * 
     * TODO: This method seems to be equal to {@link
     * org.eclipse.winery.repository.Utils.createXMLidAsString(String)}. These
     * methods should be merged.
     * 
     */
    public static String makeNCName(String text) {
        if (StringUtils.isEmpty(text)) {
            return text;
        }

        StringBuffer res = new StringBuffer();

        // handle start
        String start = text.substring(0, 1);
        Matcher m = Util.NCNameStartChar_Pattern.matcher(start);
        if (m.matches()) {
            res.append(start);
        } else {
            // not a valid character
            res.append("_");
        }

        // handle remaining characters;
        for (int i = 1; i < text.length(); i++) {
            String s = text.substring(i, i + 1);
            m = Util.NCNameChar_RegExp_Pattern.matcher(s);
            if (m.matches()) {
                res.append(s);
            } else {
                // not a valid character
                res.append("_");
            }
        }

        return res.toString();
    }

    private static void addAsIs(StringBuilder sb, String s, String separator) {
        if (s.isEmpty()) {
            return;
        }
        String[] split = s.split(separator);
        for (int i = 0; i < split.length; i++) {
            sb.append(split[i]);
            sb.append(".");
        }
    }

    private static void addReversed(StringBuilder sb, String s, String separator) {
        String[] split = s.split(separator);
        for (int i = split.length - 1; i >= 0; i--) {
            sb.append(split[i]);
            sb.append(".");
        }
    }

    /**
     * Bridge to client.getType(). Just calls client getType(), used by
     * functions.tld.
     * 
     * We suppress compiler warnings as JSP 2.0 do not offer support for
     * generics, but we're using JSP 2.0...
     * 
     * @param client the repository client to use
     * @param qname the QName to resolve
     * @param clazz the class the QName is describing
     * @return {@inheritDoc}
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static org.eclipse.winery.model.tosca.TEntityType getType(
            org.eclipse.winery.common.interfaces.IWineryRepository client, javax.xml.namespace.QName qname,
            java.lang.Class clazz) {
        return client.getType(qname, clazz);
    }
}