/*******************************************************************************
* Copyright (c) 2016 BREDEX GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.client.core.businessprocess;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jubula.client.core.businessprocess.db.TestSuiteBP;
import org.eclipse.jubula.client.core.businessprocess.problems.ProblemFactory;
import org.eclipse.jubula.client.core.businessprocess.problems.ProblemType;
import org.eclipse.jubula.client.core.model.IAUTMainPO;
import org.eclipse.jubula.client.core.model.ICapPO;
import org.eclipse.jubula.client.core.model.ICategoryPO;
import org.eclipse.jubula.client.core.model.ICompNamesPairPO;
import org.eclipse.jubula.client.core.model.IComponentNamePO;
import org.eclipse.jubula.client.core.model.IExecTestCasePO;
import org.eclipse.jubula.client.core.model.INodePO;
import org.eclipse.jubula.client.core.model.IObjectMappingAssoziationPO;
import org.eclipse.jubula.client.core.model.IProjectPO;
import org.eclipse.jubula.client.core.model.ISpecTestCasePO;
import org.eclipse.jubula.client.core.model.ITestSuitePO;
import org.eclipse.jubula.client.core.persistence.GeneralStorage;
import org.eclipse.jubula.client.core.persistence.ISpecPersistable;
import org.eclipse.jubula.toolkit.common.xml.businessprocess.ComponentBuilder;
import org.eclipse.jubula.tools.internal.i18n.CompSystemI18n;
import org.eclipse.jubula.tools.internal.objects.IComponentIdentifier;
import org.eclipse.jubula.tools.internal.xml.businessmodell.CompSystem;
import org.eclipse.jubula.tools.internal.xml.businessmodell.Component;
/**
* Calculates the types for all CNs
* Used when loading a project, changing the usage of a Component Name or saving an Editor
* Collects a lot of information, it is up to the caller to decide what is to be done with it
*
* There are two main entry points: calculateTypes() and calculateLocalTypes()
* The formal is global and if m_writeTypes is set, it writes the calculated types
* to the main session's CNs and CNPairs
* The second one only writes to the traversed CNPairs
*
* @author BREDEX GmbH
*
*/
public class CalcTypes {
/** The guid of the object which is to be replaced by an in-Editor local version */
private String m_guidToSwap;
/** The local version of the edited node */
private INodePO m_localNode;
/** The (NodeID = (Guid => type)) map of local usage types */
private Map<Long, Map<String, String>> m_localType = new HashMap<>();
/** The Guid => calculated usage type map */
private Map<String, String> m_usageType = new HashMap<>();
/**
* The Guid => calculated global type map
* the global type is the usage type or the last most concrete map type
*/
private Map<String, String> m_globalType = new HashMap<>();
/** Found problems */
private Map<String, ProblemType> m_allProblems = new HashMap<>();
/** Info for the problems */
private Map<String, List<String>> m_problemInfo = new HashMap<>();
/** The GUID => last type change map */
private Map<String, String> m_lastTypeChange = new HashMap<>();
/** Most abstract component type */
private String m_mostAbstract = CompNameTypeManager.getMostAbstractType();
/** The Component Name Cache used */
private IComponentNameCache m_cache;
/** Whether to write the types to CNs and CNPairs */
private boolean m_writeTypes = false;
/**
* The constructor
* @param cache the used component name cache
* @param node the node to replace the master version or null
*/
public CalcTypes(IComponentNameCache cache, INodePO node) {
m_cache = cache;
m_guidToSwap = node == null ? null : node.getGuid();
m_localNode = node;
}
/**
* Setting whether to write the types to the CNs and CNPairs
* @param write whether to write
*/
public void setWriteTypes(boolean write) {
m_writeTypes = write;
}
/** Calculating the types */
public void calculateTypes() {
List<IProjectPO> reusedAndSelf = new ArrayList<>(
GeneralStorage.getInstance().getReusedProjects().values());
reusedAndSelf.add(GeneralStorage.getInstance().getProject());
for (IProjectPO proj : reusedAndSelf) {
// SpecTestCasePOs
for (ISpecPersistable node : proj.getSpecObjCont()
.getSpecObjList()) {
if (!m_localType.containsKey(node.getId())) {
traverse(node);
}
}
// TestSuitePOs
for (ITestSuitePO ts : TestSuiteBP.getListOfTestSuites(proj)) {
traverse(ts);
}
}
collectUsageProblems();
handleAssociations();
if (m_writeTypes) {
writeCompNameTypesAndProblems();
}
}
/**
* Calculating the local type for a single Component Name
* @param node the starting node - should be SpecTC or TestSuite
* @param guid the guid - can be null if irrelevant
* @return the type - can be null if the CN does not appear anywhere
* below the given node
*/
public String calculateLocalType(INodePO node, String guid) {
if (!(node instanceof ISpecTestCasePO
|| node instanceof ITestSuitePO)) {
throw new IllegalArgumentException(
"Node can only be a Spec Test Case or a Test Suite"); //$NON-NLS-1$
}
traverse(node);
return m_localType.get(node.getId()).get(guid);
}
/** Collects usage incompatibility problems */
private void collectUsageProblems() {
for (String guid : m_usageType.keySet()) {
if (m_usageType.get(guid).equals(
ComponentNamesBP.UNKNOWN_COMPONENT_TYPE)) {
m_allProblems.put(guid,
ProblemType.REASON_INCOMPATIBLE_USAGE_TYPE);
}
}
}
/**
* Handling the associations
* All mapped types should realize the most concrete usage type
* The most concrete of usages and the last map will be the new type
*/
private void handleAssociations() {
// The Guid => most concrete mapped type
Map<String, String> maps = new HashMap<>();
CompSystem compSystem = ComponentBuilder.getInstance().getCompSystem();
String resGuid;
for (IAUTMainPO aut : GeneralStorage.getInstance()
.getProject().getAutMainList()) {
for (IObjectMappingAssoziationPO assoc : aut.getObjMap()
.getMappings()) {
IComponentIdentifier technicalName = assoc.getTechnicalName();
if (technicalName == null) {
continue;
}
List<Component> availableComponents = compSystem
.getComponents(aut.getToolkit(), true);
String type = CompSystem.getComponentType(technicalName
.getSupportedClassName(), availableComponents);
for (String guid : assoc.getLogicalNames()) {
resGuid = CompNameManager.getInstance().resolveGuid(guid);
if (!maps.containsKey(resGuid) || CompNameTypeManager
.doesFirstTypeRealizeSecond(type,
maps.get(resGuid))) {
maps.put(resGuid, type);
}
String globType = m_usageType.get(resGuid);
if (globType == null) {
m_usageType.put(resGuid, m_mostAbstract);
globType = m_mostAbstract;
}
if (globType != null && !globType.equals(
ComponentNamesBP.UNKNOWN_COMPONENT_TYPE)
&& !CompNameTypeManager.doesFirstTypeRealizeSecond(
type, globType)) {
// incompatible usage and mapping
m_allProblems.put(resGuid,
ProblemType.REASON_INCOMPATIBLE_MAP_TYPE);
List<String> info = new ArrayList<>(2);
info.add(CompSystemI18n.getString(type));
info.add(CompSystemI18n.getString(globType));
info.add(m_lastTypeChange.get(guid));
info.add(aut.getGuid());
m_problemInfo.put(resGuid, info);
}
}
}
}
// determining the final type: most concrete of usages and maps
for (String guid : m_usageType.keySet()) {
if (!maps.containsKey(guid)
|| ProblemType.REASON_INCOMPATIBLE_USAGE_TYPE.equals(
m_allProblems.get(guid))
|| ProblemType.REASON_INCOMPATIBLE_MAP_TYPE.equals(
m_allProblems.get(guid))) {
// if there is no map or
// if there is a map problem then the displayed type is the usage type
m_globalType.put(guid, m_usageType.get(guid));
} else {
// otherwise the type is the most concrete visible type of the most concrete mapped type
m_globalType.put(guid, CompNameTypeManager.
getMostConcreteVisibleAncestorType(maps.get(guid)));
}
}
}
/**
* Dealing with a single node
* @param nodeOld the node
*/
public void traverse(INodePO nodeOld) {
String guid;
ICompNamesPairPO pair;
Map<String, String> mapLocal;
// the type of the CN at this node
HashMap<String, String> localType = new HashMap<String, String>();
INodePO node = nodeOld;
if (m_guidToSwap != null
&& m_guidToSwap.equals(node.getGuid())) {
node = m_localNode;
}
// first we handle a child...
for (Iterator<INodePO> it = node.getAllNodeIter(); it.hasNext(); ) {
INodePO child = it.next();
// this part is for categories
if (child instanceof ICategoryPO
|| child instanceof ISpecTestCasePO) {
traverse(child);
} else if (child instanceof ICapPO) {
// ...for CAPs, we simply add the usage type to the CN
guid = ((ICapPO) child).getComponentName();
if (guid == null) {
continue;
}
guid = CompNameManager.getInstance().resolveGuid(guid);
String type = ((ICapPO) child).getComponentType();
updateType(guid, type, localType, child);
} else if (child instanceof IExecTestCasePO) {
// for ExecTestCasePOs we first handle the SpecTestCasePO
ISpecTestCasePO spec = ((IExecTestCasePO) child)
.getSpecTestCase();
if (spec == null) {
continue;
}
if (!m_localType.containsKey(spec.getId())) {
traverse(spec);
}
// ...and then we add the usages to the 'new' CNs
// indicated by the CompNamePairPOs
mapLocal = m_localType.get(spec.getId());
for (String gui : mapLocal.keySet()) {
pair = ((IExecTestCasePO) child).getCompNamesPair(gui);
if (pair == null) {
guid = gui;
} else {
guid = pair.getSecondName();
guid = CompNameManager.getInstance().resolveGuid(guid);
}
updateType(guid, mapLocal.get(gui), localType, child);
}
setCompNamePairTypes((IExecTestCasePO) child, mapLocal);
}
}
m_localType.put(node.getId(), localType);
}
/**
* Puts the type into the local and global type maps
* @param guid the guid
* @param type the type
* @param localType the local type map
* @param node the node
*/
private void updateType(String guid, String type,
Map<String, String> localType, INodePO node) {
// Calculating the local type for the CN
if (localType.containsKey(guid)) {
localType.put(guid, CompNameTypeManager.calcUsageType(
localType.get(guid), type));
} else {
localType.put(guid, type);
}
String currentType = m_usageType.get(guid);
String currentTypeDisp = CompSystemI18n.getString(currentType);
String typeDisp = CompSystemI18n.getString(type);
if (currentType == null) {
m_usageType.put(guid, m_mostAbstract);
currentType = m_mostAbstract;
}
if (currentType.equals(ComponentNamesBP.UNKNOWN_COMPONENT_TYPE)) {
return;
}
// Calculating the global type for the CN
String newType = CompNameTypeManager.calcUsageType(
currentType, type);
if (newType.equals(ComponentNamesBP.UNKNOWN_COMPONENT_TYPE)) {
List<String> info = new ArrayList<String>(2);
info.add(typeDisp);
info.add(currentTypeDisp);
info.add(m_lastTypeChange.get(guid));
info.add(node.getGuid());
m_problemInfo.put(guid, info);
} else if (!StringUtils.equals(currentTypeDisp,
CompSystemI18n.getString(newType))) {
m_lastTypeChange.put(guid, node.getGuid());
}
m_usageType.put(guid, newType);
}
/**
* Sets the Component Name Pair types
* @param exec the exec TC
* @param specMap the specTC's local types map
*/
private void setCompNamePairTypes(IExecTestCasePO exec,
Map<String, String> specMap) {
String resGuid;
for (ICompNamesPairPO pair : exec.getCompNamesPairs()) {
if (m_writeTypes) {
resGuid = CompNameManager.getInstance().resolveGuid(
pair.getFirstName());
pair.setType(specMap.get(resGuid));
}
}
}
/**
* Writes the usage types into the main session's Component Names
*/
public void writeCompNameTypesAndProblems() {
String guid;
for (IComponentNamePO cN : CompNameManager.getInstance().
getAllCompNamePOs()) {
guid = CompNameManager.getInstance().resolveGuid(cN.getGuid());
if (m_usageType.containsKey(guid)) {
cN.setUsageType(m_usageType.get(guid));
cN.setComponentType(m_globalType.get(guid));
} else {
cN.setUsageType(m_mostAbstract);
cN.setComponentType(m_mostAbstract);
}
if (m_allProblems.containsKey(guid)) {
cN.setTypeProblem(ProblemFactory.createIncompatibleTypeProblem(
cN, m_allProblems.get(guid)));
}
}
}
/**
* Writes CN types to the local cache
*/
public void writeLocalTypes() {
for (IComponentNamePO cN : m_cache.getLocalChanges().values()) {
if (m_globalType.containsKey(cN.getGuid())) {
cN.setComponentType(m_globalType.get(cN.getGuid()));
}
}
}
/**
* Calculates new problems
* @return the (Guid => Problem type) map
*/
public Map<String, ProblemType> getNewProblems() {
Map<String, ProblemType> newProblems = new HashMap<>();
for (String guid : m_allProblems.keySet()) {
IComponentNamePO cN = m_cache.getResCompNamePOByGuid(guid);
if (cN != null
&& (cN.getTypeProblem() == null || cN.getTypeProblem()
.getProblemType().equals(m_allProblems.get(guid)))) {
newProblems.put(guid, m_allProblems.get(guid));
}
}
return newProblems;
}
/**
* Returns all problems
* @return the Guid => ProblemType map of all type problems
*/
public Map<String, ProblemType> getAllProblems() {
return m_allProblems;
}
/**
* @param guid the CN guid
* @return additional info on the problem of the CN Type
*/
public List<String> getProblemInfo(String guid) {
return m_problemInfo.get(guid);
}
/**
* Recalculates the CompNamesPairPOs' type starting from the node spec
* @param cache the cache to use
* @param spec the SpecTestCasePO
*/
public static void recalculateCompNamePairs(
IComponentNameCache cache, INodePO spec) {
CalcTypes calc = new CalcTypes(cache, spec);
calc.setWriteTypes(true);
calc.calculateLocalType(spec, null);
}
/**
* Returns the local types at a given node
* @param node the node
* @return the (CN Guid) => (local type) map
*/
public Map<String, String> getLocalTypes(INodePO node) {
return m_localType.get(node.getId());
}
}