blob: bfd563d83c80b5f1b858274792c5bfab47fa866b [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2019, 2021 Robert Bosch GmbH and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.amalthea.converters.common.utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.XMLConstants;
import org.eclipse.app4mc.util.sessionlog.SessionLogger;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
public final class HelperUtil {
private static final String NO_NAME = "no-name";
private static final String AMXMI = "amxmi";
private HelperUtil() {
// empty default constructor
}
@SuppressWarnings("unchecked")
public static <T> List<T> getXpathResult(final Document document, final String xpath, final Class<T> expectedType,
final Namespace... nameSpaces) {
final XPathFactory xpfac = XPathFactory.instance();
if (Attribute.class.isAssignableFrom(expectedType)) {
final XPathExpression<Attribute> xp = xpfac.compile(xpath, Filters.attribute(), null, nameSpaces);
return ((List<T>) xp.evaluate(document));
}
else if (Element.class.isAssignableFrom(expectedType)) {
final XPathExpression<Element> xp = xpfac.compile(xpath, Filters.element(), null, nameSpaces);
return ((List<T>) xp.evaluate(document));
}
return null;
}
@SuppressWarnings("unchecked")
public static <T> List<T> getXpathResult(Element element, String xpath, Class<T> expectedType, Namespace... nameSpaces) {
final XPathFactory xpfac = XPathFactory.instance();
if (Attribute.class.isAssignableFrom(expectedType)) {
final XPathExpression<Attribute> xp = xpfac.compile(xpath, Filters.attribute(), null, nameSpaces);
return ((List<T>) xp.evaluate(element));
} else if (Element.class.isAssignableFrom(expectedType)) {
final XPathExpression<Element> xp = xpfac.compile(xpath, Filters.element(), null, nameSpaces);
return ((List<T>) xp.evaluate(element));
}
return null;
}
public static Element createAmaltheaElement(ModelVersion version) {
final Element amaltheaElement = new Element("Amalthea");
amaltheaElement.setNamespace(AmaltheaNamespaceRegistry.getNamespace(version, "am"));
amaltheaElement.addNamespaceDeclaration(AmaltheaNamespaceRegistry.getGenericNamespace("xsi"));
amaltheaElement.addNamespaceDeclaration(AmaltheaNamespaceRegistry.getGenericNamespace("xmi"));
final Attribute attrib = new Attribute("version", "2.0");
attrib.setNamespace(AmaltheaNamespaceRegistry.getGenericNamespace("xmi"));
amaltheaElement.setAttribute(attrib);
return amaltheaElement;
}
/**
* This method is used to update the namespace references from old to new.
*
* @param rootElement
*/
public static void updateRootElement_NameSpaces(Element rootElement, ModelVersion oldNsVersion, ModelVersion newNsVersion) {
final Namespace defaultNamespace = rootElement.getNamespace();
if (AmaltheaNamespaceRegistry.isNamespaceAvailable(oldNsVersion, defaultNamespace)) {
rootElement.setNamespace(null);
rootElement.removeNamespaceDeclaration(defaultNamespace);
rootElement.setNamespace(AmaltheaNamespaceRegistry.getNamespace(newNsVersion, "am"));
// updating additional namespaces to the ones from 0.9.5
final List<Namespace> additionalNamespaces = new ArrayList<>();
additionalNamespaces.addAll(rootElement.getAdditionalNamespaces());
for (int i = 0; i < additionalNamespaces.size(); i++) {
final Namespace ns = additionalNamespaces.get(i);
if (AmaltheaNamespaceRegistry.isNamespaceAvailable(oldNsVersion, ns)) {
rootElement.removeNamespaceDeclaration(ns);
}
}
rootElement.addNamespaceDeclaration(AmaltheaNamespaceRegistry.getGenericNamespace("xsi"));
rootElement.addNamespaceDeclaration(AmaltheaNamespaceRegistry.getGenericNamespace("xmi"));
}
}
public static Document loadFile(final String path) throws JDOMException, IOException {
return loadFile(path, null);
}
public static Document loadFile(final String path, SessionLogger logger) throws JDOMException, IOException {
final long start = System.currentTimeMillis();
final File file = new File(path);
final SAXBuilder sax = new SAXBuilder();
sax.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sax.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
Document doc;
try {
doc = sax.build(file);
}
catch (JDOMException e) {
throw new JDOMException("Error occured while parsing file : " + path, e);
}
final long end = System.currentTimeMillis();
if (logger != null) {
logger.info("Total time taken to load file : {0} : {1} ms", path, (end - start));
}
return doc;
}
public static void updateFileName(final Map<File, Document> fileName2documentMap) {
/*
* 1.File name should be updated
*
* 2.File location should be updated
*
*/
for (final Entry<File, Document> entry : fileName2documentMap.entrySet()) {
final Document document = entry.getValue();
final List<Attribute> hrefs = getXpathResult(document.getRootElement(), ".//@href", Attribute.class);
for (final Attribute attribute : hrefs) {
/*- example: href="f1/f2/sw.amxmi#_gK_hoMW5EeWBM6uFowTedA" */
final String attributeValue = attribute.getValue();
final int indexOfHash = attributeValue.lastIndexOf('#');
if (indexOfHash != -1 && (indexOfHash + 1) < attributeValue.length()) {
/* example: f1/f2/sw.amxmi */
final String refRelativePath = attributeValue.substring(0, indexOfHash);
final int lastIndex = refRelativePath.lastIndexOf('/');
String refFileName = "";
if (lastIndex == -1) {
/* Example: sw.amxmi i.e. there is no hierarchy to reach to the file from the selected file*/
refFileName = refRelativePath;
}
else if ((lastIndex != -1) && (lastIndex + 1) < refRelativePath.length()) {
/* example: sw.amxmi */
refFileName = refRelativePath.substring(lastIndex + 1);
}
if (refFileName.length() > 0) {
/* check for the file extension */
final int indexOfDot = refFileName.indexOf('.');
if (indexOfDot != -1) {
final String extension = refFileName.substring(indexOfDot + 1);
if (extension.startsWith(AMXMI) && !extension.equals(AMXMI)) {
/*
* - this is the case where extension of the model (e.g: amxmi-hw) is present instead of amxmi
* - adding amxmi extension, to make the model file compatible to 1.1.1 or higher
*/
refFileName = refFileName + ".amxmi";
}
}
/* example: href="f1/f2/sw.amxmi#_gK_hoMW5EeWBM6uFowTedA"
* In this case attributeValue_part2 is the second part of the above String i.e _gK_hoMW5EeWBM6uFowTedA
*/
final String attributeValue_part2 = attributeValue.substring(indexOfHash + 1);
/* setting the updated file name (and also making the references relative, as files will be moved inside same directory*/
attribute.setValue(refFileName + "#" + attributeValue_part2);
}
}
}
}
}
public static void saveFile(final Document doc, final String outputFilePath, final boolean prettyPrintXml)
throws IOException {
saveFile(doc, outputFilePath, prettyPrintXml, false);
}
public static void saveFile(final Document doc, final String outputFilePath, final boolean prettyPrintXml,
final boolean force) throws IOException {
XMLOutputter xout = prettyPrintXml ? new XMLOutputter(Format.getPrettyFormat()) : new XMLOutputter();
final File file = new File(outputFilePath);
if (force && !file.exists()) {
file.getParentFile().mkdirs();
}
try (FileOutputStream out = new FileOutputStream(file)) {
xout.output(doc, out);
}
}
public static void saveFile(final Document doc, final File outputFile, final boolean prettyPrintXml) throws IOException {
saveFile(doc, outputFile, prettyPrintXml, false);
}
public static void saveFile(final Document doc, final File outputFile, final boolean prettyPrintXml, final boolean force)
throws IOException {
XMLOutputter xout = prettyPrintXml ? new XMLOutputter(Format.getPrettyFormat()) : new XMLOutputter();
if (force && !outputFile.exists()) {
outputFile.getParentFile().mkdirs();
}
try (FileOutputStream out = new FileOutputStream(outputFile);) {
xout.output(doc, out);
}
}
/**
* This method returns name and type of the element
*
* @param attributeOrTagName
* @param element
* @return Entry<String, String> key here is name and value is Type
*/
public static Entry<String, String> getSingleElementsNameandTypeFromAttributeOrChildeElement(
final String attributeOrTagName, final Element element) {
final String attributeValue = element.getAttributeValue(attributeOrTagName);
if (attributeValue != null) {
final String name = getElementNameFromReference(attributeValue);
final String type = getElementTypeFromReference(attributeValue);
return new AbstractMap.SimpleEntry<>(name, type);
}
else {
final Element child = element.getChild(attributeOrTagName);
if (child != null) {
final String hrefValue = child.getAttributeValue("href");
if (hrefValue != null) {
final String name = getElementNameFromReference(hrefValue);
final String type = getElementTypeFromReference(hrefValue);
return new AbstractMap.SimpleEntry<>(name, type);
}
}
}
return null;
}
public static String getSingleElementNameFromAttributeOrChildeElement(final String attributeOrTagName,
final Element element) {
final String attributeValue = element.getAttributeValue(attributeOrTagName);
if (attributeValue != null) {
return getElementNameFromReference(attributeValue);
}
else {
final Element child = element.getChild(attributeOrTagName);
if (child != null) {
final String hrefValue = child.getAttributeValue("href");
if (hrefValue != null) {
return getElementNameFromReference(hrefValue);
}
}
}
return null;
}
public static String getElementTypeFromReference(final String reference) {
if (reference == null || reference.length() == 0) {
return "";
}
final int startIndex = reference.indexOf("?type=");
if (startIndex != -1) {
return reference.substring(startIndex + 6);
}
return reference;
}
public static String getElementNameFromReference(final String reference) {
if (reference == null || reference.length() == 0) {
return "";
}
final int startIndex = reference.indexOf("?type=");
if (startIndex != -1) {
String name = reference.substring(0, startIndex);
if (name.startsWith("amlt:/#")) {
name = name.replaceFirst("amlt\\:\\/\\#", "");
return name;
}
return name;
}
return reference;
}
public static Element getParentElementOfName(final Element currentElement, final String... parentNames) {
final Element parentElement = currentElement.getParentElement();
if (parentElement != null) {
final boolean contains = Arrays.stream(parentNames).anyMatch(parentElement.getName()::equals);
if (contains) {
return parentElement;
} else {
return getParentElementOfName(parentElement, parentNames);
}
}
return null;
}
public static String getValueFromChildElement(final Element element, final String childElementName, final String childElementAttributeName) {
final Element child = element.getChild(childElementName);
if (child != null) {
return child.getAttributeValue(childElementAttributeName);
}
return null;
}
/**
* This method is used to copy the root namespace and additional namespaces from the source element and set them to
* the target element
*
* @param source
* Element. Containing root namespace and additional namespaces
* @param target
* Element. This element should be populated with the root namespace and additional namespaces
*/
public static void copyAllNameSpaces(final Element source, final Element target) {
final Namespace namespace = source.getNamespace();
/*-setting target namespace */
target.setNamespace(namespace);
final List<Namespace> additionalNamespaces = source.getAdditionalNamespaces();
for (final Namespace additionalNS : additionalNamespaces) {
/*-setting additional namespace */
target.addNamespaceDeclaration(additionalNS);
}
}
/**
* This method is used to copy an element from the source node to the target node, irrespective of if the element is
* an attribute or node
*
* @param sourceElement
* @param targetElement
* @param childNodeOrAttributeName
* name of the child element. Method should detect if the child with this name exists as a
*/
public static void copyElement_Attribute_or_Element(final Element sourceElement, final Element targetElement, final String childNodeOrAttributeName) {
final Attribute attribute = sourceElement.getAttribute(childNodeOrAttributeName);
final Element childElement = sourceElement.getChild(childNodeOrAttributeName);
if (attribute != null) {
targetElement.setAttribute(attribute.clone());
} else if (childElement != null) {
targetElement.setContent(childElement.clone());
}
}
/**
* This method is used to remove default namespace and standard additional namespaces.. along with xmi:version
* attribute
*
* @param element
*/
public static void removeDefaultAttribs(final Element element) {
element.setNamespace(null);
element.removeNamespaceDeclaration(Namespace.getNamespace("http://www.omg.org/XMI"));
element.removeNamespaceDeclaration(Namespace.getNamespace("http://www.w3.org/2001/XMLSchema-instance"));
element.removeAttribute("version", Namespace.getNamespace("xmi", "http://www.omg.org/XMI"));
}
/**
* This method returns name and type of the element
*
* @param attributeOrTagName
* @param element
* @return Entry<String, String> key here is name and value is Type
*/
public static Map<String, String> getMultipleElementsNameandTypeFromAttributeOrChildeElement(
final String attributeOrTagName, final Element element) {
final String attributeValue = element.getAttributeValue(attributeOrTagName);
if (attributeValue != null) {
final Map<String, String> map = new HashMap<>();
final String[] references = attributeValue.split("\\s");
for (final String reference : references) {
final String name = getElementNameFromReference(reference);
final String type = getElementTypeFromReference(reference);
map.put(name, type);
}
return map;
}
else {
final List<Element> children = element.getChildren(attributeOrTagName);
if (!children.isEmpty()) {
final Map<String, String> map = new HashMap<>();
for (final Element child : children) {
final String hrefValue = child.getAttributeValue("href");
if (hrefValue != null) {
final String name = getElementNameFromReference(hrefValue);
final String type = getElementTypeFromReference(hrefValue);
map.put(name, type);
}
}
return map;
}
}
return new HashMap<>();
}
public static String encodeName(final String name) {
if (name == null || name.length() == 0) {
return NO_NAME;
}
String result;
try {
result = URLEncoder.encode(name, StandardCharsets.UTF_8.toString());
}
catch (final UnsupportedEncodingException e) {
result = name; // keep old name - we have no better option
}
return result;
}
public static String decodeName(final String name) {
if (name == null || name.length() == 0) {
return NO_NAME;
}
String result;
try {
result = URLDecoder.decode(name, StandardCharsets.UTF_8.toString());
}
catch (final UnsupportedEncodingException e) {
result = name; // keep old name - we have no better option
}
return result;
}
public static String encodeNameForReference(final String name) {
return encodeName(name);
}
}