/*******************************************************************************
* Copyright (c) 2007, 2013 Borland Software Corporation 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:
* Borland Software Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.evaluator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.ASTBindingHelper;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.ASTSyntheticNode;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.ASTSyntheticNodeAccess;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.IModuleSourceInfo;
import org.eclipse.m2m.internal.qvt.oml.ast.env.InternalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.parser.QvtOperationalParserUtil;
import org.eclipse.m2m.internal.qvt.oml.emf.util.EmfUtil;
import org.eclipse.m2m.internal.qvt.oml.expressions.ImperativeOperation;
import org.eclipse.m2m.internal.qvt.oml.expressions.Module;
import org.eclipse.ocl.ecore.FeatureCallExp;
import org.eclipse.ocl.expressions.VariableExp;
import org.eclipse.ocl.utilities.ASTNode;
/**
* Helps to build QVT stack trace from a given state of QVT code execution.
*/
public class QvtStackTraceBuilder {
private static final String UNKNOWN_NAME = "<Unknown>"; //$NON-NLS-1$
private static final int UNKNOWN_LINE_NUM = -1;
private QvtOperationalEvaluationEnv fEvalEnv;
/**
* Constructs stack trace builder for the given evaluation environment.
*
* @param evalEnv
* the evaluation environment representing the top stack trace
*
* @param astNodeIPOffset explicit the AST node offset representing the current instruction
* pointer of execution in a QVT module
*/
public QvtStackTraceBuilder(QvtOperationalEvaluationEnv evalEnv) {
if(evalEnv == null) {
throw new IllegalArgumentException();
}
fEvalEnv = evalEnv;
}
/**
* Builds the stack trace corresponding to evaluation environments hierarchy
* associated with this builder.
*
* @return list of QVT stack elements
*/
public List<QVTStackTraceElement> buildStackTrace() {
LinkedList<QVTStackTraceElement> elements = new LinkedList<QVTStackTraceElement>();
for(QvtOperationalEvaluationEnv nextEnv = fEvalEnv; nextEnv != null; nextEnv = nextEnv.getParent()) {
// skip all the root execution environments as they
// are not bound to any module code locations
QvtOperationalEvaluationEnv parent = nextEnv.getParent();
if(parent != null) {
InternalEvaluationEnv internalEnv = nextEnv.getAdapter(InternalEvaluationEnv.class);
// skip all stack frames not running in a module,
// IOW possible non QVT transformation clients
if(internalEnv.getCurrentModule() != null) {
elements.addLast(createStackElement(nextEnv));
}
}
}
QvtOperationalEvaluationEnv rootEnv = fEvalEnv.getRoot();
QvtOperationalEvaluationEnv aggregatingEnv = EvaluationUtil.getAggregatingContext(rootEnv);
if(aggregatingEnv != null) {
List<QVTStackTraceElement> aggregatedStackTrace = new QvtStackTraceBuilder(aggregatingEnv).buildStackTrace();
List<QVTStackTraceElement> result = new ArrayList<QVTStackTraceElement>(elements.size() + aggregatedStackTrace.size());
result.addAll(elements);
result.addAll(aggregatedStackTrace);
return result;
}
return Collections.unmodifiableList(elements);
}
private QVTStackTraceElement createStackElement(QvtOperationalEvaluationEnv env) {
String unitName = null;
String moduleName = UNKNOWN_NAME;
String operName = UNKNOWN_NAME;
int lineNumber = UNKNOWN_LINE_NUM;
Module module = null;
ImperativeOperation operation = env.getOperation();
InternalEvaluationEnv internEvalEnv = env.getAdapter(InternalEvaluationEnv.class);
int resultOffset = getCurrentASTOffset(internEvalEnv);
ModuleInstance thisInstance = internEvalEnv.getCurrentModule();
if(thisInstance == null) {
throw new IllegalArgumentException("Currently executed model is not set in environment"); //$NON-NLS-1$
}
module = thisInstance.getModule();
assert module != null;
moduleName = module.getName();
if(operation == null) {
// we must be executing a module instance initialization - synthetic constructor
operName = moduleName;
if(internEvalEnv.getCurrentIP() == module || resultOffset < -1) {
ASTSyntheticNode astNode = ASTSyntheticNodeAccess.getASTNode(module);
if(astNode != null) {
resultOffset = astNode.getStartPosition();
}
}
} else {
operName = operation.getName();
EClassifier contextType = QvtOperationalParserUtil.getContextualType(operation);
if(contextType != null) {
operName = contextType.getName() + EmfUtil.PATH_SEPARATOR + operName;
}
}
IModuleSourceInfo sourceInfo = ASTBindingHelper.getModuleSourceBinding(module);
if(sourceInfo != null) {
URI uri = sourceInfo.getSourceURI();
unitName = uri.lastSegment();
if(resultOffset >= 0) {
lineNumber = sourceInfo.getLineNumberProvider().getLineNumber(resultOffset);
}
}
return new QVTStackTraceElement(moduleName, operName, unitName, lineNumber);
}
private static int getCurrentASTOffset(InternalEvaluationEnv evalEnv) {
// TODO - for cases that AST does not fill all offset
// traverse up to the enclosing operation scope, taking the closest
// offset which has been initialized
EObject currentIPObject = evalEnv.getCurrentIP();
if(currentIPObject instanceof ASTNode) {
ASTNode astNode = (ASTNode) currentIPObject;
if(astNode.getStartPosition() < 0 && astNode instanceof VariableExp<?, ?>) {
// Remark: special processing for implicit source variables represented as
// synthetic variable expression in AST. These do not have any CST representation
// but are rather synthetic nodes => point to the call AST
if(astNode.eContainer() instanceof FeatureCallExp) {
astNode = (FeatureCallExp) astNode.eContainer();
}
}
return astNode.getStartPosition();
}
ASTSyntheticNode astNode = ASTSyntheticNodeAccess.getASTNode(currentIPObject);
if(astNode != null) {
return astNode.getStartPosition();
}
return -1;
}
}