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