blob: 6b777a84d9e97867f25bc25e404c356b88ac1288 [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2015-2020 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.postprocessor;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.app4mc.amalthea.converters.common.base.IPostProcessor;
import org.eclipse.app4mc.amalthea.converters.common.utils.AmaltheaNamespaceRegistry;
import org.eclipse.app4mc.amalthea.converters.common.utils.HelperUtil;
import org.jdom2.Document;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This PostProcessor is used to migrate CustomProperties which are having value as ReferenceObject
*
* @author MEZ2RNG
*
*/
public class CustomPropsPostProcessor implements IPostProcessor {
private static final String HREF = "href";
private static final String VALUE = "value";
private static final Logger LOGGER = LoggerFactory.getLogger(CustomPropsPostProcessor.class);
@Override
public void process(final Map<File, Document> fileName2documentMap) {
/*-Map holding File name as a Key and value as a Map:
* containing key as : uriFragment or xmi ID of AMALTHEA element
* Value as : List of CustomProperties Value object which is of type common:ReferenceObject
* */
final Map<File, Map<String, List<Element>>> map_values_to_be_investigated = new HashMap<>();
boolean areXMIIDsUsed = false;
Map<File, Map<String, List<Element>>> idElementsCacheMap = new HashMap<>();
for (final Entry<File, Document> entry : fileName2documentMap.entrySet()) {
final File targetFile = entry.getKey();
final Document root = entry.getValue();
if (root == null) {
return;
}
final Element rootElement = root.getRootElement();
/*-
* Contains key as : uriFragment or xmi ID of AMALTHEA element
* Value : List of CustomProperties Value object's having
*/
final Map<String, List<Element>> refCustomProps2ObjectsMap = new HashMap<>();
/*-
* CustomProperties with Reference to ReferableBaseObject can exists in the following way:
*
*Case 1: Referring elements inside the same model
* <customProperties xmi:id="_p-7r4OnfEeW9t73E3V9yAw" key="key">
* <value xsi:type="common:ReferenceObject" xmi:id="_qsGT4OnfEeW9t73E3V9yAw" value="_oi1hUOnfEeW9t73E3V9yAw"/>
* </customProperties>
*
*Case 2: Referring elements across the model files
*
*<customProperties xmi:id="__0JRIOqOEeW9t73E3V9yAw">
* <value xsi:type="common:ReferenceObject" xmi:id="_AmqTMOqPEeW9t73E3V9yAw">
* <value href="default.amxmi#_UZWXYOqLEeW9t73E3V9yAw"/>
* </value>
* </customProperties>
*
*Case 3: List of Ref objects
*
*<customProperties xmi:id="_rnhEIBaeEeasLIggkXJ5XQ" key="group_labels">
* <value xsi:type="common:ListObject" xmi:id="_sJ_fEBaeEeasLIggkXJ5XQ">
* <values xsi:type="common:ReferenceObject" xmi:id="_t-0SsBaeEeasLIggkXJ5XQ" value="_hk2sYBaeEeasLIggkXJ5XQ"/>
* <values xsi:type="common:ReferenceObject" xmi:id="_vzxCIBaeEeasLIggkXJ5XQ" value="_k5wysBaeEeasLIggkXJ5XQ"/>
* <values xsi:type="common:ReferenceObject" xmi:id="_yQN2cBaeEeasLIggkXJ5XQ" value="_lDRUABaeEeasLIggkXJ5XQ"/>
* <values xsi:type="common:ReferenceObject" xmi:id="_9YBRUBaeEeasLIggkXJ5XQ">
* <value href="slave_default.amxmi#_hk2sYBaeEeasLIggkXJ5XQ"/>
* </values>
* </value>
* </customProperties>
*/
final StringBuilder xpathBuffer = new StringBuilder();
xpathBuffer.append(".//customProperties/value[@xsi:type=\"common:ReferenceObject\"]");
xpathBuffer.append("|");
xpathBuffer.append(".//customProperties/value/values[@xsi:type=\"common:ReferenceObject\"]");
final List<Element> value_custProps_refObjects = HelperUtil.getXpathResult(rootElement, xpathBuffer.toString(),
Element.class, AmaltheaNamespaceRegistry.getGenericNamespace("xsi"));
for (final Element element : value_custProps_refObjects) {
String attributeValue = element.getAttributeValue(VALUE);
if (attributeValue == null) {
/*-
* Case where elements across the model files are referred:
* <value xsi:type="common:ReferenceObject" xmi:id="_AmqTMOqPEeW9t73E3V9yAw">
* <value href="default.amxmi#_UZWXYOqLEeW9t73E3V9yAw"/>
* </value>
*/
attributeValue = element.getChild(VALUE) != null
? element.getChild(VALUE).getAttributeValue(HREF) : null;
}
if (attributeValue != null) {
if (!refCustomProps2ObjectsMap.containsKey(attributeValue)) {
refCustomProps2ObjectsMap.put(attributeValue, new ArrayList<>());
}
refCustomProps2ObjectsMap.get(attributeValue).add(element);
if ((!areXMIIDsUsed) && !isURIFragment(attributeValue)) {
areXMIIDsUsed = true;
}
}
}
map_values_to_be_investigated.put(targetFile, refCustomProps2ObjectsMap);
}
/*- In the case where XMI ID's are used, fetching the elements through Xpath will be of performance hit.
* Instead of Xpath, memory map is built for all the model elements with key as XMI ID
*/
if (areXMIIDsUsed) {
idElementsCacheMap = buildCache(fileName2documentMap);
}
/*
* verifying if the referred elements are implementing ReferableBaseObject Interface. In the case elements are
* not implementing ReferableBaseObject, then such CustomProperty value elements should be removed from the
* model
*/
final HashMap<String, File> refFileInfoMap = new HashMap<>();
final Map<String, List<XSDElement>> tags2refbaseObjectsMap = populateInfoOfAmaltheModelTags_111_Content();
for (final Entry<File, Map<String, List<Element>>> entry : map_values_to_be_investigated.entrySet()) {
final File modelFile = entry.getKey();
final Map<String, List<Element>> map = entry.getValue();
if (map != null) {
for (final Entry<String, List<Element>> entry2 : map.entrySet()) {
/*- refString can be either an ID or the URIFragment */
final String refString = entry2.getKey();
final List<Element> values =entry2.getValue();
boolean isRefBaseObjectType = true;
if (!isURIFragment(refString)) {
/*- case of referring elements across models*/
final int indexOfHash = refString.indexOf('#');
if (indexOfHash != -1) {
final String fileName = refString.substring(0, indexOfHash);
final String id = refString.substring(indexOfHash + 1);
final File refFile = getRefFile(modelFile, refFileInfoMap, fileName);
/*- check is performed here to verify if Element associated to id is implementing ReferableBaseObject*/
final Entry<Boolean, ElementRef> result = isElementOfRefBaseObject(refFile, id,
idElementsCacheMap, tags2refbaseObjectsMap);
isRefBaseObjectType = result.getKey();
final ElementRef elementRef = result.getValue();
/*- in case where elements are referred from other model, corresponding types of it should be updated at the place where they are referred*/
updateTypeForAllElements(values, elementRef != null ? elementRef.getType() : null);
} else {
/*- case of referring elements within the same model*/
final Entry<Boolean, ElementRef> result = isElementOfRefBaseObject(modelFile, refString,
idElementsCacheMap, tags2refbaseObjectsMap);
isRefBaseObjectType = result.getKey();
}
} else {
/*- This is the case where legacy URI fragments are specified example: //@stimuliModel/@stimuli.0*/
final ElementRef elementRef = elementRefBuilder(refString);
isRefBaseObjectType = isElementOfRefBaseObject(elementRef, tags2refbaseObjectsMap);
}
if (!isRefBaseObjectType) {
detachElements(values);
}
}
}
}
}
/**
* This method is used to check if the supplied tag (through ElementRef) is extending RefBaseObject
*
* @param elementRef
* @param tags2refbaseObjectsMap
* @return
*/
private boolean isElementOfRefBaseObject(final ElementRef elementRef,
final Map<String, List<XSDElement>> tags2refbaseObjectsMap) {
final List<XSDElement> list = tags2refbaseObjectsMap.get(elementRef.getName());
if (list != null) {
final ElementRef parentRef = elementRef.getParentRef();
final String parentName = parentRef.getName();
final String parentType = parentRef.getType();
boolean isParentSame = false;
mainLoop: for (final XSDElement elementRef2 : list) {
final Set<XSDElement> parentRefs = elementRef2.getParentRefs();
for (final XSDElement xsdElementRef : parentRefs) {
if (parentName != null) {
if (xsdElementRef.getName().equals(parentName)) {
/*- setting the type from the XSD element .. in a case where ElementRef Type is not available from XMI */
if (elementRef.getType() == null) {
elementRef.setType(elementRef2.getType());
}
isParentSame = true;
break mainLoop;
}
}
else if (parentType != null) {
if (xsdElementRef.getType().equals(parentType)) {
isParentSame = true;
break mainLoop;
}
}
}
}
return isParentSame;
}
return false;
}
/**
* This method is used to check if the element having a specific XMI ID is belonging to Java class extending
* ReferableBaseObject
*
* @param refFile
* File to which XML element is belonging to
* @param id
* XMI ID of the XML element
*
* @param idElementsCacheMap
* map containing cache of XML elements
* @param tags2refbaseObjectsMap
* map containing the metadata information of Ecore (1.1.1)
* @return
*/
private Entry<Boolean, ElementRef> isElementOfRefBaseObject(final File refFile, final String id,
final Map<File, Map<String, List<Element>>> idElementsCacheMap,
final Map<String, List<XSDElement>> tags2refbaseObjectsMap) {
final Map<String, List<Element>> map = idElementsCacheMap.get(refFile);
if (map != null) {
final List<Element> elements = map.get(id);
if (elements != null) {
for (final Element element2 : elements) {
/*- ElementRef is a temporary class used to store the information of XML element */
final ElementRef elementRef = elementRefBuilder(element2);
final boolean result = isElementOfRefBaseObject(elementRef, tags2refbaseObjectsMap);
if (result) {
return new AbstractMap.SimpleEntry<>(true, elementRef);
}
}
}
}
return new AbstractMap.SimpleEntry<>(false, null);
}
/**
* This method is used to remove the element from the XML Document
*
* @param values
*
* Note: List<Element> consists of either<br>
* 1. value element which is the first sub-element of CustomProperties<br>
* 2. values element which is the second sub-element of CustomProperties (incase of List of RefObjects)
* <br>
*
*
* Case 1:
*
* <customProperties xmi:id="_A0YGsBhQEea3AoorCOXNXA" key="custKey_abstractProcess">
* <value xsi:type="common:ReferenceObject" xmi:id="_A0YGsRhQEea3AoorCOXNXA"> ------------------------->
* This element is part of List <value href="default1.amxmi#_nDgKUNotEeWXsaNW2kxe8A" /> </value>
* </customProperties>
*
* Case 2:
*
* <customProperties xmi:id="_NaacUBhfEea3AoorCOXNXA" key="custKey_group_refobjs">
* <value xsi:type="common:ListObject" xmi:id="_8b8TkBhfEea3AoorCOXNXA">
* <values xsi:type="common:ReferenceObject" xmi:id="__kW2ABhfEea3AoorCOXNXA"> ------------------------->
* This element is part of List <value href="default1.amxmi#_nDgKUNotEeWXsaNW2kxe8A" /> </values>
* <values xsi:type="common:ReferenceObject" xmi:id="__0BpsBhfEea3AoorCOXNXA">
* <value href="default1.amxmi#_6PbocBhOEea3AoorCOXNXA" /> </values> </value> </customProperties>
*
*
*
*/
private void detachElements(final List<Element> values) {
for (final Element element : values) {
final String tag_name = element.getName();
String key = "";
if (tag_name.equals(VALUE)) {
key = element.getParentElement() != null ? element.getParentElement().getAttributeValue("key") : null;
}
else if (tag_name.equals("values")) {
key = element.getParentElement() != null ? element.getParentElement().getParentElement() != null
? element.getParentElement().getParentElement().getAttributeValue("key") : null : null;
}
if (key != null) {
LOGGER.warn("Value of CustomProperty : {} is removed (as it is not inheriting RefereableBaseObject)", key);
}
element.detach();
}
}
/**
* This method is used to remove the element from the XML Document
*
* @param values
* List<Element>
* @param type
* String (e.g: hw:Memory)
*
* Note: List<Element> consists of either<br>
* 1. value element which is the first sub-element of CustomProperties<br>
* 2. values element which is the second sub-element of CustomProperties (incase of List of RefObjects)
* <br>
*
*
* Case 1:
*
* <customProperties xmi:id="_A0YGsBhQEea3AoorCOXNXA" key="custKey_abstractProcess">
* <value xsi:type="common:ReferenceObject" xmi:id="_A0YGsRhQEea3AoorCOXNXA"> ------------------------->
* This element is part of List <value href="default1.amxmi#_nDgKUNotEeWXsaNW2kxe8A" /> </value>
* </customProperties>
*
* Case 2:
*
* <customProperties xmi:id="_NaacUBhfEea3AoorCOXNXA" key="custKey_group_refobjs">
* <value xsi:type="common:ListObject" xmi:id="_8b8TkBhfEea3AoorCOXNXA">
* <values xsi:type="common:ReferenceObject" xmi:id="__kW2ABhfEea3AoorCOXNXA"> ------------------------->
* This element is part of List <value href="default1.amxmi#_nDgKUNotEeWXsaNW2kxe8A" /> </values>
* <values xsi:type="common:ReferenceObject" xmi:id="__0BpsBhfEea3AoorCOXNXA">
* <value href="default1.amxmi#_6PbocBhOEea3AoorCOXNXA" /> </values> </value> </customProperties>
*
*
*
*/
private void updateTypeForAllElements(final List<Element> values, final String type) {
if (type == null) {
return;
}
for (final Element element : values) {
final Element child = element.getChild(VALUE);
if (child != null) {
final String attributeValue = child.getAttributeValue("type", AmaltheaNamespaceRegistry.getGenericNamespace("xsi"));
if (attributeValue == null) {
child.setAttribute("type", type, AmaltheaNamespaceRegistry.getGenericNamespace("xsi"));
}
}
}
}
/**
* This method is used to parse the text content (which has meta model info of elements extending
* ReferableBaseObjects and its parents)
*
* @return
*/
// ignore false-positive that (parentElement != null) always evaluates to false
@SuppressWarnings("java:S2583")
private Map<String, List<XSDElement>> populateInfoOfAmaltheModelTags_111_Content() {
final Map<String, List<XSDElement>> map = new HashMap<>();
/*-
* Text file contains the content in the following format (which replicates the containment relationship):
*
* swModel,sw:SWModel,labels,sw:Label,,
*
* Elements on the left side are the parents
*/
try (final InputStream inputStream = getInputStream();
final BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
final StringTokenizer tokenizer = new StringTokenizer(inputLine, ",");
XSDElement parentElement = null;
XSDElement processingElement = null;
boolean isTagToProcess = true;
boolean isTagTypeToProcess = false;
while (tokenizer.hasMoreTokens()) {
final String nextToken = tokenizer.nextToken();
if (isTagToProcess) {
processingElement = new XSDElement();
processingElement.setName(nextToken);
isTagToProcess = false;
isTagTypeToProcess = true;
}
else if (isTagTypeToProcess && processingElement != null) {
processingElement.setType(nextToken);
if (!map.containsKey(processingElement.getName())) {
map.put(processingElement.getName(), new ArrayList<>());
map.get(processingElement.getName()).add(processingElement);
}
else {
final List<XSDElement> existingElements = map.get(processingElement.getName());
boolean isElementWithSamePropsExisting = false;
for (final XSDElement xsdElementRef : existingElements) {
if ((processingElement.getName().equals(xsdElementRef.getName()))
&& (processingElement.getType().equals(xsdElementRef.getType()))) {
processingElement = xsdElementRef;
isElementWithSamePropsExisting = true;
break;
}
}
if (!isElementWithSamePropsExisting) {
map.get(processingElement.getName()).add(processingElement);
}
}
if (parentElement != null) {
processingElement.getParentRefs().add(parentElement);
}
parentElement = processingElement;
isTagTypeToProcess = false;
isTagToProcess = true;
}
}
}
}
catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return map;
}
private InputStream getInputStream() throws IOException {
if (System.getProperty("junit:tags_Elements_ReferableBaseObject") != null) {
return new FileInputStream(
new File(System.getProperty("junit:tags_Elements_ReferableBaseObject")));
}
final URL url = new URL(
"platform:/plugin/org.eclipse.app4mc.amalthea.converters.common/resources/tags_Elements_ReferableBaseObject.txt");
return url.openConnection().getInputStream();
}
/**
* This method is used to take jdom2 Element node and return ElementRef object and its corresponding parent
* ElementRef's <br>
* Input string example: <StimuliModel><stimuli></stimuli></StimuliModel> <br>
* Result of this input : ElementRef (with name : stimuli) --> Parent ElementRef (with name : StimuliModel)
*
* @param node
* @return
*/
private ElementRef elementRefBuilder(final Element node) {
final String tagName = node.getName();
final ElementRef elementRef = new ElementRef();
elementRef.setName(tagName);
/*- element type from the XMI is fetched */
elementRef.setType(node.getAttributeValue("type", AmaltheaNamespaceRegistry.getGenericNamespace("xsi")));
final ElementRef parentElementRef = addParentElementRef(node, elementRef);
if (parentElementRef != null) {
elementRef.setParentRef(parentElementRef);
}
return elementRef;
}
/**
* This method is used to create Parent ElementRef object for the supplied JDom node
*
* @param node
* JDOM node element
* @param elementRef
* ElementRef object for which parent should be created
* @return
*/
private static ElementRef addParentElementRef(final Element node, final ElementRef elementRef) {
final Element parentElement = node.getParentElement();
if (parentElement != null) {
final String parentTagName = parentElement.getName()
+ (parentElement.getNamespacePrefix() != null ? parentElement.getNamespacePrefix() : "");
final ElementRef parentElementRef = new ElementRef();
if (parentTagName.contains(":")) {
parentElementRef.setType(parentTagName);
}
else {
parentElementRef.setName(parentTagName);
}
return parentElementRef;
}
return null;
}
/**
* This method takes legacy URI fragment as an input and builds ElementRef object and its corresponding Parent
* ElementRef's
*
* Input string example: //@stimuliModel/@stimuli.0
*
* Result of this input : ElementRef (with name : stimuli) --> Parent ElementRef (with name : StimuliModel)
*
* @param refString
* @return
*/
private static ElementRef elementRefBuilder(final String refString) {
ElementRef elementRef = null;
ElementRef parentElementRef = null;
final char[] charArray = refString.toCharArray();
boolean isBuildingElement = false;
final StringBuilder nameBuffer = new StringBuilder();
for (int i = 0; i < charArray.length; i++) {
if (charArray[i] == '/') {
if (nameBuffer.length() > 0 && elementRef != null) {
elementRef.setName(nameBuffer.toString());
if (parentElementRef != null) {
elementRef.setParentRef(parentElementRef);
}
parentElementRef = elementRef;
nameBuffer.setLength(0);
}
isBuildingElement = false;
// lookup for /@
int lookUp = i + 1;
if ((lookUp < charArray.length) && (charArray[lookUp] == '/')) {
i++;
lookUp = lookUp + 1;
if ((lookUp < charArray.length) && (charArray[lookUp] == '@')) {
i++;
isBuildingElement = true;
elementRef = new ElementRef();
}
}
else if (charArray[lookUp] == '@') {
i++;
isBuildingElement = true;
elementRef = new ElementRef();
}
}
else if (charArray[i] == '.') {
isBuildingElement = false;
}
else {
if (isBuildingElement) {
nameBuffer.append(charArray[i]);
}
}
}
if (nameBuffer.length() > 0 && elementRef != null) {
elementRef.setName(nameBuffer.toString());
if (parentElementRef != null) {
elementRef.setParentRef(parentElementRef);
}
nameBuffer.setLength(0);
}
return elementRef;
}
/**
* This method is used to build the cache Map for the supplied XML Document objects
*
* @param fileName2documentMap
* @param helper
* @return
*/
private Map<File, Map<String, List<Element>>> buildCache(final Map<File, Document> fileName2documentMap) {
final Map<File, Map<String, List<Element>>> cacheMap = new HashMap<>();
for (final Entry<File, Document> entry : fileName2documentMap.entrySet()) {
final File targetFile = entry.getKey();
final Document root = entry.getValue();
final List<Element> elements = HelperUtil.getXpathResult(root, ".//*[@xmi:id]", Element.class,
AmaltheaNamespaceRegistry.getGenericNamespace("xmi"));
for (final Element element : elements) {
final String id = element.getAttributeValue("id", AmaltheaNamespaceRegistry.getGenericNamespace("xmi"));
if (!cacheMap.containsKey(targetFile)) {
cacheMap.put(targetFile, new HashMap<>());
}
if (!cacheMap.get(targetFile).containsKey(id)) {
cacheMap.get(targetFile).put(id, new ArrayList<>());
}
cacheMap.get(targetFile).get(id).add(element);
}
}
return cacheMap;
}
/*
* If the input string is supplied as : "default.amxmi#_UZWXYOqLEeW9t73E3V9yAw"
*/
private boolean isURIFragment(final String string) {
if (string.contains("#")) {
return string.substring(string.indexOf('#') + 1).contains("/");
}
return string.contains("/");
}
/**
* This method is used to return File object based on the supplied content
*
* @param targetFile
* @param refFileInfoMap
* @param fileNameInfo
* @return
*/
protected File getRefFile(final File targetFile, final HashMap<String, File> refFileInfoMap,
final String fileNameInfo) {
File referredFile = null;
if (!refFileInfoMap.containsKey(fileNameInfo)) {
referredFile = new File(fileNameInfo);
if (!referredFile.exists()) {
referredFile = new File(targetFile.getParent(), fileNameInfo);
if (!referredFile.exists()) {
referredFile = null;
LOGGER.error("Unable to find the referred file : {}", fileNameInfo);
}
}
refFileInfoMap.put(fileNameInfo, referredFile);
} else {
referredFile = refFileInfoMap.get(fileNameInfo);
}
return referredFile;
}
}