blob: 8f46a775f9379eed40e805023ac3cb74559930dc [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2015-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.converters071.impl;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.app4mc.amalthea.converters.common.ServiceConstants;
import org.eclipse.app4mc.amalthea.converters.common.base.ICache;
import org.eclipse.app4mc.amalthea.converters.common.base.IConverter;
import org.eclipse.app4mc.amalthea.converters.common.converter.AbstractConverter;
import org.eclipse.app4mc.amalthea.converters.common.utils.AmaltheaNamespaceRegistry;
import org.eclipse.app4mc.amalthea.converters.common.utils.HelperUtil;
import org.eclipse.app4mc.amalthea.converters.common.utils.ModelVersion;
import org.eclipse.app4mc.amalthea.converters071.utils.SectionRunnableLabelCacheBuilder;
import org.eclipse.app4mc.amalthea.converters071.utils.SectionRunnableLabelCacheEnum;
import org.eclipse.app4mc.util.sessionlog.SessionLogger;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
property = {
ServiceConstants.INPUT_MODEL_VERSION_PROPERTY + "=0.7.0",
ServiceConstants.OUTPUT_MODEL_VERSION_PROPERTY + "=0.7.1"},
service = IConverter.class)
public class SwConverter extends AbstractConverter {
private static final String AM = "am";
private static final String XMI = "xmi";
private static final String XSI = "xsi";
private static final String TYPE = "type";
private static final String VALUE = "value";
private static final String AMLT_PREFIX = "amlt:/#";
@Reference
SessionLogger logger;
protected SectionRunnableLabelCacheBuilder cache;
private Map<File, Document> fileDocumentMapping;
private File targetFile;
@Override
@Activate
protected void activate(Map<String, Object> properties) {
super.activate(properties);
}
@Override
public void convert(File targetFile, Map<File, Document> fileDocumentMapping, List<ICache> caches) {
logger.info(
"Migration from 0.7.0 to 0.7.1 : Executing Sw converter for model file : {0}", targetFile.getName());
this.cache = getSectionRunnableLabelCacheBuilder(caches);
if (this.cache == null) {
throw new IllegalStateException("SectionRunnableLabelCache is not built and Object of it is not available in Converters");
}
this.fileDocumentMapping = fileDocumentMapping;
this.targetFile = targetFile;
final Document root = fileDocumentMapping.get(this.targetFile);
if (root == null) {
return;
}
final Element rootElement = root.getRootElement();
/*- Migration of DataSize attributes */
updateDataSize(rootElement);
/*- association of MemoryElements (i.e Label/Runnable elements) with the corresponding Section as per 0.7.1 changes*/
updateMemoryElementsWithSectionInfo(rootElement);
/*-updation of Section as per 0.7.1 i.e. labels, runEntities, size will be removed */
updateSection(rootElement);
/*- Removing SectionMapping and SectionMappingConstraint elements */
removeSectionMappingAndSectionMappingConstraint(rootElement);
/* Update ModeSwitch */
updateModeSwitch(rootElement);
}
/**
* Based on model changes of 0.7.1 :
*
*
* @param rootElement
*/
private void updateModeSwitch(final Element rootElement) {
final StringBuilder xpathBuffer = new StringBuilder();
// xpathBuffer.append(".swModel/tasks/callGraph/graphEntries[@xsi:type=\"am:ModeSwitch\"]/entries");
xpathBuffer.append("./swModel/tasks/callGraph//graphEntries");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/isrs/callGraph//graphEntries");
// xpathBuffer.append("|");
// xpathBuffer.append(".swModel/isrs/callGraph/graphEntries[@xsi:type=\"am:ModeSwitch\"]/entries");
final List<Element> graphEntryBaseElements = HelperUtil.getXpathResult(
rootElement,
xpathBuffer.toString(),
Element.class,
AmaltheaNamespaceRegistry.getGenericNamespace(XSI),
AmaltheaNamespaceRegistry.getNamespace(ModelVersion._071, AM));
/*-
* <callGraph>
<graphEntries xsi:type="am:CallSequence"/>
<graphEntries xsi:type="am:ModeSwitch"/>
<graphEntries xsi:type="am:ProbabiltitySwitch"/>
</callGraph>
*/
for (final Element graphEntryBaseElement : graphEntryBaseElements) {
/*-
* Elements whicha are part of : "graphEntries" are to be verified if the parent is "CallGraph".
* For the elements whose parent is other than "CallGraph" should be renamed to "items"
*/
if (!graphEntryBaseElement.getParentElement().getName().equals("callGraph")) {
graphEntryBaseElement.setName("items");
}
final String graphEntryBaseObjectType = graphEntryBaseElement.getAttributeValue(TYPE,
AmaltheaNamespaceRegistry.getGenericNamespace(XSI));
if (graphEntryBaseObjectType != null && graphEntryBaseObjectType.equals("am:ModeSwitch")) {
final List<Element> entriesElements = graphEntryBaseElement.getChildren("entries");
/*-
* <callGraph>
<graphEntries xsi:type="am:ModeSwitch">
<entries value="md12/a?type=ModeLiteral" default="true">
<graphEntries xsi:type="am:CallSequence" name="cs2"/>
</entries>
</graphEntries>
</callGraph>
*/
/*-
* As per change in 0.7.1: Inside a ModeSwitch -> only one ModeSwitchEntry can be default.
* In previous versions of APP4MC -> it was possible to have several ModeSwitchEntry objects as default (which can not happen practically)
*
* Based on the change in 0.7.1 :
* - first encountered ModeSwitchEntry with default property as "true" is transformed as "defaultEntry"
* - the other ModeSwitchEntry objects are transformed as "non default" -> eventough they have default as "true"
*/
boolean isDefaultFound = false;
for (final Element entriesElement : entriesElements) {
final Attribute defaultAttribute = entriesElement.getAttribute("default");
if (defaultAttribute != null) {
if (!isDefaultFound) {
isDefaultFound = true;
/*- First ModeSwitchEntry with default value as "true" -> should have a tag name as "defaultEntry" */
entriesElement.setName("defaultEntry");
entriesElement.removeAttribute(VALUE);
entriesElement.removeChild(VALUE);
}
/*- "default" attribute is removed from ModeSwitchEntry object */
entriesElement.removeAttribute("default");
}
/*- renaming "value" attribute to "values" */
final Attribute valueAttribute = entriesElement.getAttribute(VALUE);
if (valueAttribute != null) {
valueAttribute.setName("values");
} else {
final Element valueElement = entriesElement.getChild(VALUE);
if (valueElement != null) {
valueElement.setName("values");
}
}
}
}
}
}
/**
* Based on Bug 500856 : It is no longer required to have SectionMapping and SectionMappingConstraint definitions in
* AMALTHEA model.
*
* Based on this change, corresponding elements of SectionMappingConstraint and SectionMapping are removed from the
* AMALTHEA model
*
* @param rootElement
*/
private void removeSectionMappingAndSectionMappingConstraint(final Element rootElement) {
final StringBuilder xpathBuffer = new StringBuilder();
xpathBuffer.append("./mappingModel/mapping[@xsi:type=\"am:SectionMapping\"]");
xpathBuffer.append("|");
xpathBuffer.append("./propertyConstraintsModel/mappingConstraints[@xsi:type=\"am:SectionMappingConstraint\"]");
final List<Element> elements = HelperUtil.getXpathResult(
rootElement,
xpathBuffer.toString(),
Element.class,
AmaltheaNamespaceRegistry.getGenericNamespace(XSI),
AmaltheaNamespaceRegistry.getNamespace(ModelVersion._071, AM));
final Iterator<Element> iterator = elements.iterator();
while (iterator.hasNext()) {
final Element sectionMappingElement = iterator.next();
sectionMappingElement.getParent().removeContent(sectionMappingElement);
}
}
/**
* This method is used to update the content of the Section element as per the changes introduced in 0.7.1 (Bug
* 500856 )<br>
*
* Section element will not directly contain dataSize, lists of Labels, Runnables <br>
*
* &nbsp;&nbsp;&nbsp;- Instead of the above relation, Section reference is directly available at the MemoryElements
*
* @param rootElement
*/
private void updateSection(final Element rootElement) {
final StringBuilder xpathBuffer = new StringBuilder();
xpathBuffer.append("./swModel/sections");
final List<Element> sectionElements = HelperUtil.getXpathResult(
rootElement,
xpathBuffer.toString(),
Element.class,
AmaltheaNamespaceRegistry.getNamespace(ModelVersion._071, AM));
for (final Element element : sectionElements) {
element.removeChildren("size");
element.removeChildren("labels");
element.removeChildren("runEntities");
element.removeAttribute("labels");
element.removeAttribute("runEntities");
}
}
/**
* This method is used to associate the Section reference directly at the Memory Element (i.e. Label/Runnable) based
* on the changes introduced in 0.7.1 (Bug 500856 )
*
* @param rootElement
* Xml Element of amalthea model file
*/
private void updateMemoryElementsWithSectionInfo(final Element rootElement) {
final StringBuilder xpathBuffer = new StringBuilder();
xpathBuffer.append("./swModel/runnables");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/labels");
final List<Element> memoryElements = HelperUtil.getXpathResult(
rootElement,
xpathBuffer.toString(),
Element.class,
AmaltheaNamespaceRegistry.getNamespace(ModelVersion._071, AM));
for (final Element memoryElement : memoryElements) {
final String memoryElementName = memoryElement.getAttributeValue("name");
final String memoryElementID = memoryElement.getAttributeValue("id", AmaltheaNamespaceRegistry.getGenericNamespace(XMI));
List<String> sections = new ArrayList<>();
if (memoryElement.getName().equals("labels")) {
sections = getAssociatedSectionFromCache(SectionRunnableLabelCacheEnum.Label_Sections,
memoryElementName);
/*- fetching the elements based on the UUID */
sections.addAll(getAssociatedSectionFromCache(SectionRunnableLabelCacheEnum.Label_UUID_Sections,
memoryElementID));
} else if (memoryElement.getName().equals("runnables")) {
sections = getAssociatedSectionFromCache(SectionRunnableLabelCacheEnum.Runnable_Sections,
memoryElementName);
/*- fetching the elements based on the UUID */
sections.addAll(getAssociatedSectionFromCache(SectionRunnableLabelCacheEnum.Runnable_UUID_Sections,
memoryElementID));
}
/*- Associating section to a memoryelement */
if (!sections.isEmpty()) {
final String sectionName = sections.get(0); // name without encoding
if (isSectionDefinedInFile(sectionName, this.targetFile)) {
memoryElement
.setAttribute(new Attribute("section", encodeSectionName(sectionName) + "?type=Section"));
} else {
final Element sectionRef = new Element("section");
sectionRef.setAttribute("href", AMLT_PREFIX + encodeSectionName(sectionName) + "?type=Section");
memoryElement.addContent(sectionRef);
}
if (sections.size() > 1) {
logger.warn("MemoryElement : {0} is associated to multiple sections: {1}. \n"
+ "As per AMALTHEA 0.7.1 it is allowed to be part of one Section only.", memoryElementName, sections.toArray());
}
}
}
}
/**
* This method is used to associate Section to the corresponding MemoryElemnt. <br>
*
* Required input data across the models is fetched from the ICache(SectionRunnableLabelCacheBuilder)<br>
*
* <b>Note</b>: In SectionRunnableLabelCacheBuilder Cache is stored in the below format:<br>
* <ul>
* <li>Map<File, Map<String, Object>> : CacheMap having key as File (amalthea model file) and value as Map<String,
* Object><br>
* </li>
* <li>In Map<String, Object> : Key is the "SectionRunnableLabelCacheEnum type" and value is the <b> Map<String,
* List<String>> </b></li>
* <li>In Map Map<String, List<String>> : Key is the "Runnable/Label name" and value is the list of Section names to
* which MemoryElement (Label/Runnable) is associated to</li>
*
* </ul>
*
* @param type
* SectionRunnableLabelCacheEnum name. This parameter is supplied to identify the MemoryElement type i.e.
* Runnable/Label and accordingly fetch the cached content
* @param memoryElementName_or_UUID
* String. This parameter is the name/UUID of the MemoryElement for which accordingly the corresponding
* Sections are fetched
*
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<String> getAssociatedSectionFromCache(SectionRunnableLabelCacheEnum type, String memoryElementName_or_UUID) {
Map<File, Map<String, Object>> cacheMap = this.cache.getCacheMap();
Set<File> fileSet = this.fileDocumentMapping.keySet();
for (File file : fileSet) {
Map<String, Object> map = cacheMap.get(file);
Object object = map.get(type.name());
if (object instanceof Map && ((Map) object).containsKey(memoryElementName_or_UUID)) {
return (List<String>) ((Map) object).get(memoryElementName_or_UUID);
}
}
return new ArrayList<>();
}
/**
* This method is used to verify if the Section definition is present in the supplied input file
*
* @param sectionName
* Name of the input Section (without encoding)
* @param inputFile
* Input File which is being migrated to version 0.7.1
* @return boolean true if the Section definition is found in the supplied input model file
*/
@SuppressWarnings("unchecked")
private boolean isSectionDefinedInFile(String sectionName, File inputFile) {
Map<File, Map<String, Object>> cacheMap = this.cache.getCacheMap();
Map<String, Object> map = cacheMap.get(inputFile);
if (map != null) {
Object object = map.get(SectionRunnableLabelCacheEnum.Section_Names.name());
if (object instanceof List && ((List<String>) object).contains(sectionName)) {
return true;
}
}
return false;
}
/**
* This method is used to get the corresponding SectionRunnableLabelCacheBuilder object from various ICache objects
*
* @param caches The list of caches to find the {@link SectionRunnableLabelCacheBuilder} out of.
* @return SectionRunnableLabelCacheBuilder Object of ICache defined using extension point :
* org.eclipse.app4mc.amalthea.model.converters.cachebuilders
*/
private SectionRunnableLabelCacheBuilder getSectionRunnableLabelCacheBuilder(List<ICache> caches) {
if (caches != null) {
for (ICache c : caches) {
if (c instanceof SectionRunnableLabelCacheBuilder) {
return (SectionRunnableLabelCacheBuilder) c;
}
}
}
return null;
}
/**
* This method is used to migrate the contents of "DataSize" element which can be contained inside following
* AMALTHEA elements: <br>
* BaseTypeDefinition <br>
* Section <br>
* Task <br>
* ISR <br>
* ProcessPrototype <br>
* ModeLabel <br>
* Label <br>
* Runnable<br>
* CustomEntity<br>
*
* Change : in previous version of model (0.7.0) -> DataSize element was consisting of "numberBits". In model
* (0.7.1) it is changed to "value" and an additional attribute "unit" is introduced [As a part of migration value
* set for "unit" is "bit"]
*
* @param rootElement
* root XML Element
*/
private void updateDataSize(final Element rootElement) {
final StringBuilder xpathBuffer = new StringBuilder();
/*-
* As per the change in 0.7.1, Section will not have labels, runEntities, size elements
*
* xpathBuffer.append("./swModel/sections/size");
* xpathBuffer.append("|");
*/
xpathBuffer.append("./swModel/typeDefinitions/size");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/tasks/size");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/isrs/size");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/processPrototypes/size");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/modeLabels/size");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/labels/size");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/runnables/size");
xpathBuffer.append("|");
xpathBuffer.append("./swModel/customEntities/size");
final List<Element> dataSizes = HelperUtil.getXpathResult(
rootElement,
xpathBuffer.toString(),
Element.class,
AmaltheaNamespaceRegistry.getNamespace(ModelVersion._071, AM));
for (final Element dataSizeElement : dataSizes) {
final Attribute numberBits = dataSizeElement.getAttribute("numberBits");
if (numberBits != null) {
numberBits.setName(VALUE);
final Attribute unit = new Attribute("unit", "bit");
dataSizeElement.setAttribute(unit);
}
}
}
private String encodeSectionName(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;
}
}