/** | |
******************************************************************************** | |
* 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); | |
} | |
} |