/*******************************************************************************
* Copyright (c) 2015 ARM Ltd. and others
* 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:
* ARM Ltd and ARM Germany GmbH - Initial API and implementation
*******************************************************************************/
package com.arm.cmsis.pack.rte.dependencies;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import com.arm.cmsis.pack.common.CmsisConstants;
import com.arm.cmsis.pack.data.CpConditionContext;
import com.arm.cmsis.pack.data.ICpComponent;
import com.arm.cmsis.pack.data.ICpExpression;
import com.arm.cmsis.pack.data.ICpItem;
import com.arm.cmsis.pack.data.ICpPack;
import com.arm.cmsis.pack.enums.EEvaluationResult;
import com.arm.cmsis.pack.generic.IAttributes;
import com.arm.cmsis.pack.info.ICpComponentInfo;
import com.arm.cmsis.pack.info.ICpDeviceInfo;
import com.arm.cmsis.pack.rte.IRteModel;
import com.arm.cmsis.pack.rte.components.IRteComponent;
import com.arm.cmsis.pack.rte.components.IRteComponentGroup;
import com.arm.cmsis.pack.rte.components.IRteComponentItem;
import com.arm.cmsis.pack.utils.AlnumComparator;
/**
* Class responsible for evaluating component dependencies and resolving them
*/
public class RteDependencySolver extends CpConditionContext implements IRteDependencySolver {
protected IRteModel rteModel = null;
//results of evaluating expressions
protected Map<ICpExpression, IRteDependency> fDependencies = null;
protected Map<ICpExpression, IRteDependency> fDenyDependencies = null;
// collected results for selected components
protected Map<IRteComponentItem, IRteDependencyItem> fDependencyItems = null;
// temporary collection of selected components
protected Collection<IRteComponent> tSelectedComponents = null;
protected Map<IRteComponentItem, EEvaluationResult> fEvaluationResults = null;
/**
* Helper class to compare component by evaluation result (descending) and component name (acceding)
*/
class ComponentResultComparator implements Comparator<IRteComponent> {
@Override
public int compare(IRteComponent c0, IRteComponent c1) {
int res0 = getEvaluationResult(c0).ordinal();
int res1 = getEvaluationResult(c1).ordinal();
int res = res0 - res1;
if(res != 0) {
return res;
}
String name0 = c0.getActiveCpItem().getName();
String name1 = c1.getActiveCpItem().getName();
return AlnumComparator.alnumCompare(name0, name1);
}
};
/**
* Default constructor
*/
public RteDependencySolver(IRteModel model) {
rteModel = model;
}
@Override
public void resetResult() {
super.resetResult();
fDependencies = null;
fDenyDependencies = null;
fEvaluationResults = null;
fDependencyItems = null;
tSelectedComponents = null;
}
protected Collection<IRteComponent> getSelectedComponents(){
if(tSelectedComponents == null) {
if(rteModel != null) {
tSelectedComponents = rteModel.getSelectedComponents();
}
}
return tSelectedComponents;
}
protected Collection<IRteComponent> getUsedComponents(){
if(rteModel != null) {
return rteModel.getUsedComponents();
}
return null;
}
protected Map<String, ICpPack> getGeneratedPacks(){
if(rteModel != null) {
return rteModel.getGeneratedPacks();
}
return null;
}
@Override
protected boolean isEvaluate(EEvaluationResult res) {
if(super.isEvaluate(res)) {
return true;
}
if(res == EEvaluationResult.ERROR) {
return false;
}
// do not re-evaluate fulfilled and ignored conditions,
// for other results do trigger calls to evaluateDependency(), it has its own cache
return !res.isFulfilled();
}
protected void collectDependencies(IRteDependencyResult depRes, ICpItem condition, EEvaluationResult overallResult) {
// first check require and deny expressions
Collection<? extends ICpItem> children = condition.getChildren();
for(ICpItem child : children) {
if(!(child instanceof ICpExpression)) {
continue;
}
ICpExpression expr = (ICpExpression)child;
EEvaluationResult res = getCachedResult(expr);
if(res == EEvaluationResult.IGNORED || res == EEvaluationResult.UNDEFINED || res == EEvaluationResult.FULFILLED) {
continue;
}
if(expr.getExpressionType() == ICpExpression.ACCEPT_EXPRESSION) {
if(res.ordinal() < overallResult.ordinal()){
continue; // ignored
}
} else {
if(res.ordinal() > overallResult.ordinal()){
continue;
}
}
boolean bDeny = tbDeny; // save deny context
if(expr.getExpressionType() == ICpExpression.DENY_EXPRESSION)
{
tbDeny = !tbDeny; // invert the deny context
}
if(expr.getExpressionDomain() == ICpExpression.REFERENCE_EXPRESSION) {
collectDependencies(depRes, expr.getCondition(), overallResult);
} else if(expr.getExpressionDomain() == ICpExpression.COMPONENT_EXPRESSION) {
IRteDependency dep = getDependency(expr);
if(dep != null) {
depRes.addDependency(dep);
}
}
tbDeny = bDeny; // restore deny context
}
}
@Override
public EEvaluationResult evaluateExpression(ICpExpression expression) {
if(expression == null) {
return EEvaluationResult.IGNORED;
}
switch(expression.getExpressionDomain()) {
case ICpExpression.COMPONENT_EXPRESSION:
return evaluateDependency(expression);
case ICpExpression.REFERENCE_EXPRESSION:
return evaluate(expression.getCondition());
case ICpExpression.DEVICE_EXPRESSION:
case ICpExpression.TOOLCHAIN_EXPRESSION:
return EEvaluationResult.IGNORED;
default:
break;
}
return EEvaluationResult.ERROR;
}
protected EEvaluationResult evaluateDependency( ICpExpression expression) {
if(rteModel == null)
{
return EEvaluationResult.IGNORED; // nothing to do
}
IRteDependency dep = getDependency(expression);
if(dep == null){
dep = new RteDependency(expression, tbDeny);
if(tbDeny) {
EEvaluationResult res = evaluateDenyDependency(dep);
dep.setEvaluationResult(res);
} else {
IRteComponentItem components = rteModel.getComponents();
if(components != null){
components.findComponents(dep);
}
}
putDependency(expression, dep);
}
EEvaluationResult result = dep.getEvaluationResult();
return result;
}
protected EEvaluationResult evaluateDenyDependency(IRteDependency dep) {
EEvaluationResult res = EEvaluationResult.FULFILLED;
Collection<IRteComponent> selectedComponents = getSelectedComponents();
if(selectedComponents == null || selectedComponents.isEmpty()) {
return res;
}
IAttributes attr = dep.getCpItem().attributes();
for(IRteComponent rteComponent : selectedComponents) {
ICpComponent c = rteComponent.getActiveCpComponent();
if(c == null)
{
continue; // should not happen
}
if(attr.matchAttributes(c.attributes())) {
res = EEvaluationResult.INCOMPATIBLE;
dep.addComponent(rteComponent, res);
}
}
return res;
}
protected IRteDependency getDependency(ICpExpression expression) {
if(tbDeny) { // cache deny results separately
if(fDenyDependencies != null) {
return fDenyDependencies.get(expression);
}
} else if(fDependencies != null) {
return fDependencies.get(expression);
}
return null;
}
protected void putDependency(ICpExpression expression, IRteDependency dep ) {
if(tbDeny) { // cache deny results separately
if(fDenyDependencies == null) {
fDenyDependencies = new HashMap<ICpExpression, IRteDependency>();
}
fDenyDependencies.put(expression, dep);
} else {
if(fDependencies == null) {
fDependencies = new HashMap<ICpExpression, IRteDependency>();
}
fDependencies.put(expression, dep);
}
}
@Override
public IRteDependencyItem getDependencyItem(IRteComponentItem componentItem) {
if(fDependencyItems != null) {
return fDependencyItems.get(componentItem);
}
return null;
}
@Override
public EEvaluationResult evaluateDependencies() {
resetResult();
if(rteModel == null)
{
return EEvaluationResult.IGNORED; // nothing to do
}
fDependencyItems = new LinkedHashMap<IRteComponentItem, IRteDependencyItem>();
IRteComponentItem devClass = getSelectedDeviceClass();
// first check if the selected device is available
ICpDeviceInfo di = rteModel.getDeviceInfo();
if(devClass != null && di != null) {
if(di.getDevice() == null){
fResult = EEvaluationResult.FAILED;
IRteDependencyResult depRes = new RteMissingDeviceResult(devClass, di);
fDependencyItems.put(devClass, depRes);
cacheConditionResult(devClass, fResult);
return fResult; // missing device => no use to evaluate something else
}
cacheConditionResult(devClass, EEvaluationResult.FULFILLED);
}
// report missing components and gpdsc files
Collection<IRteComponent> usedComponents = getUsedComponents();
if(usedComponents != null && !usedComponents.isEmpty()) {
for(IRteComponent component : usedComponents){
if(!component.isSelected()) {
continue;
}
ICpComponentInfo ci = component.getActiveCpComponentInfo();
if(ci == null) {
continue;
}
EEvaluationResult r = EEvaluationResult.IGNORED;
IRteDependencyResult depRes = null;
if(ci.getComponent() != null) {
if(ci.isGenerated() || !ci.isSaved()) {
continue;
}
String gpdsc = ci.getGpdsc(true);
if(gpdsc == null) {
continue;
}
ICpPack pack = rteModel.getGeneratedPack(gpdsc);
if(pack != null) {
continue;
}
r = EEvaluationResult.MISSING_GPDSC;
depRes = new RteMissingGpdscResult(component, gpdsc);
} else {
r = ci.getEvaluationResult();
depRes = new RteMissingComponentResult(component);
updateEvaluationResult(EEvaluationResult.FAILED);
}
depRes.setEvaluationResult(r);
fDependencyItems.put(component, depRes);
cacheConditionResult(component, r);
cacheConditionResult(component.getParentClass(), r);
cacheConditionResult(component.getParentGroup(), r);
updateEvaluationResult(r);
}
}
Collection<IRteComponent> selectedComponents = getSelectedComponents();
if(selectedComponents == null || selectedComponents.isEmpty()) {
return getEvaluationResult();
}
// sorted map : MISSING comes earlier than SELECTABLE
Map<IRteComponent, IRteDependencyResult> componentResults = new TreeMap<IRteComponent, IRteDependencyResult>(new ComponentResultComparator());
Map<IRteComponentGroup, IRteDependency> apiConflicts = new HashMap<IRteComponentGroup, IRteDependency>();
for(IRteComponent component : selectedComponents){
ICpComponent c = component.getActiveCpComponent();
if(c == null || c instanceof ICpComponentInfo) {
continue;
}
EEvaluationResult r = evaluate(c);
updateEvaluationResult(r);
cacheConditionResult(component, r);
cacheConditionResult(component.getParentClass(), r);
if(r.ordinal() < EEvaluationResult.FULFILLED.ordinal()) {
IRteDependencyResult depRes = new RteDependencyResult(component);
ICpItem condition = c.getCondition();
if(r != EEvaluationResult.ERROR) {
collectDependencies(depRes, condition, r);
}
depRes.setEvaluationResult(r);
componentResults.put(component, depRes);
}
IRteComponentGroup g = component.getParentGroup();
cacheConditionResult(g, r);
// check for missing APIs and API conflicts
ICpComponent api = g.getApi();
if(api != null) {
if(api instanceof ICpComponentInfo ) {
ICpComponentInfo apiInfo = (ICpComponentInfo)api;
if(apiInfo.getComponent() == null) {
r = EEvaluationResult.MISSING_API;
IRteDependencyResult depRes = new RteMissingComponentResult(g);
depRes.setEvaluationResult(r);
fDependencyItems.put(g, depRes);
cacheConditionResult(g, r);
cacheConditionResult(g.getParentClass(), r);
fResult = EEvaluationResult.FAILED;
}
} else if(api.isExclusive()) {
IRteDependency d = apiConflicts.get(g);
if(d == null) {
d = new RteDependency(api, true);
apiConflicts.put(g, d);
}
d.addComponent(component, r);
}
}
}
if(!fDependencyItems.isEmpty())
{
return getEvaluationResult(); // no need to evaluate further if components or APIs are missing
}
// add API evaluation results
for(Entry<IRteComponentGroup, IRteDependency> e : apiConflicts.entrySet()) {
IRteDependency d = e.getValue();
if(d.getChildCount() > 1) {
d.setEvaluationResult(EEvaluationResult.CONFLICT);
IRteComponentGroup g = e.getKey();
fDependencyItems.put(g, d);
cacheConditionResult(g, EEvaluationResult.CONFLICT);
cacheConditionResult(g.getParentClass(), EEvaluationResult.CONFLICT);
if(fResult.ordinal() > EEvaluationResult.CONFLICT.ordinal() ) {
fResult = EEvaluationResult.CONFLICT;
}
}
}
// finally add sorted dependency results
for(Entry<IRteComponent, IRteDependencyResult> e : componentResults.entrySet()) {
IRteComponent c = e.getKey();
IRteDependencyResult r = e.getValue();
fDependencyItems.put(c, r);
}
purgeResults();
return getEvaluationResult();
}
// remove all items that are higher than overall result
protected void purgeResults() {
Iterator<IRteDependencyItem> iterator = fDependencyItems.values().iterator();
while(iterator.hasNext()) {
IRteDependencyItem d = iterator.next();
EEvaluationResult res = d.getEvaluationResult();
if(res.ordinal() > fResult.ordinal()) {
iterator.remove();
}
}
}
protected IRteComponentItem getSelectedDeviceClass(){
return rteModel.getComponents().getFirstChild(CmsisConstants.EMPTY_STRING); // always first
}
protected void cacheConditionResult(IRteComponentItem item, EEvaluationResult res) {
if(item == null) {
return;
}
if(getEvaluationResult(item).ordinal() <= res.ordinal()) {
return;
}
if(fEvaluationResults == null) {
fEvaluationResults = new HashMap<IRteComponentItem, EEvaluationResult>();
}
fEvaluationResults.put(item, res);
}
@Override
public EEvaluationResult getEvaluationResult(IRteComponentItem item) {
if(fEvaluationResults != null) {
EEvaluationResult res = fEvaluationResults.get(item);
if(res != null) {
return res;
}
}
return EEvaluationResult.IGNORED;
}
@Override
public EEvaluationResult resolveDependencies() {
// try to run resolve iteration until all dependencies are resolved or no resolution is available
while(fDependencyItems != null && getEvaluationResult().ordinal() < EEvaluationResult.FULFILLED.ordinal())
{
if(resolveIteration() == false) {
break;
}
}
return getEvaluationResult();
}
/**
* Tries to resolve SELECTABLE dependencies
* @return true if one of dependencies gets resolved => the state changes
*/
protected boolean resolveIteration(){
for(IRteDependencyItem depItem : fDependencyItems.values()) {
if(resolveDependency(depItem)) {
return true;
}
}
return false;
}
protected boolean resolveDependency(IRteDependencyItem depItem){
if(depItem.getEvaluationResult() != EEvaluationResult.SELECTABLE) {
return false;
}
if(depItem instanceof IRteDependencyResult) {
IRteDependencyResult depRes = (IRteDependencyResult)depItem;
Collection<IRteDependency> deps = depRes.getDependencies();
if(deps == null) {
return false;
}
for(IRteDependency d : deps) {
if(resolveDependency(d)) {
return true;
}
}
}
return false;
}
protected boolean resolveDependency(IRteDependency dependency){
if(dependency.getEvaluationResult() != EEvaluationResult.SELECTABLE) {
return false;
}
IRteComponent c = dependency.getBestMatch();
if(c != null) {
rteModel.selectComponent(c, 1);
rteModel.evaluateComponentDependencies(); // re-evaluate dependencies to remove resolved ones
return true;
}
return false;
}
@Override
public Collection<? extends IRteDependencyItem> getDependencyItems() {
if(fDependencyItems != null) {
return fDependencyItems.values();
}
return null;
}
}