/*
* <copyright>
*
* Copyright (c) 2005-2008 Sven Efftinge 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:
* Sven Efftinge - Initial API and implementation
* Alexander Shatalin (Borland)
*
* </copyright>
*/
package org.eclipse.gmf.internal.xpand.expression.ast;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.gmf.internal.xpand.BuiltinMetaModel;
import org.eclipse.gmf.internal.xpand.expression.AnalysationIssue;
import org.eclipse.gmf.internal.xpand.expression.EvaluationException;
import org.eclipse.gmf.internal.xpand.expression.ExecutionContext;
import org.eclipse.gmf.internal.xpand.expression.SyntaxConstants;
import org.eclipse.gmf.internal.xpand.expression.Variable;
import org.eclipse.gmf.internal.xpand.migration.FeatureCallTrace;
import org.eclipse.gmf.internal.xpand.migration.FeatureCallTrace.Type;
/**
* @author Sven Efftinge
* @author Arno Haase
*/
public class FeatureCall extends Expression {
private Expression target;
private final Identifier name;
public FeatureCall(final int start, final int end, final int line, final int startOffset, final int endOffset, final Identifier name, final Expression target) {
super(start, end, line, startOffset, endOffset);
this.target = target;
this.name = name;
}
public Expression getTarget() {
return target;
}
public void setTarget(final Expression target) {
this.target = target;
}
public Identifier getName() {
return name;
}
public EEnumLiteral getEnumLiteral(final ExecutionContext ctx) {
if (name.getValue().indexOf(SyntaxConstants.NS_DELIM) != -1) {
String typeName = name.getValue();
typeName = typeName.substring(0, typeName.lastIndexOf(SyntaxConstants.NS_DELIM));
final EClassifier type = ctx.getTypeForName(typeName);
if (type != null) {
if (false == (type instanceof EEnum)) {
return null;
}
final String litName = name.getValue().substring(
name.getValue().lastIndexOf(SyntaxConstants.NS_DELIM) + SyntaxConstants.NS_DELIM.length());
return ((EEnum) type).getEEnumLiteral(litName);
}
}
return null;
}
/**
* evaluates in the following order if (target==null) 1) enumeration literal
* 2) variable 3) implicite var (e.g. this) 4) type literal
*
* otherwise it's a property
*/
@Override
public Object evaluateInternal(final ExecutionContext ctx) {
Object targetObj = null;
if (target == null) {
final EEnumLiteral staticProp = getEnumLiteral(ctx);
if (staticProp != null) {
// XXX write tests to make sure
// switch (enumField) case EnumName::LITERAL1
// works
return staticProp.getInstance();
}
Variable var = ctx.getVariable(getName().getValue());
if (var != null) {
return var.getValue();
}
var = ctx.getVariable(ExecutionContext.IMPLICIT_VARIABLE);
if (var != null) {
targetObj = var.getValue();
if (targetObj == null) {
return null;
}
}
} else {
targetObj = getTarget().evaluate(ctx);
if (targetObj == null) {
return null;
}
}
if (targetObj != null) {
EClassifier targetObjType = BuiltinMetaModel.getType(targetObj);
final EStructuralFeature p = BuiltinMetaModel.getAttribute(targetObjType, getName().getValue());
if ((p == null) && (targetObj instanceof Collection)) {
return handleCollection(ctx, (Collection) targetObj);
}
if (p != null) {
return BuiltinMetaModel.getValue(p, targetObj);
}
}
if (target == null) {
// type literal ?
final EClassifier type = ctx.getTypeForName(getName().getValue());
if (type != null) {
return type;
}
}
if (getName().getValue().indexOf(SyntaxConstants.NS_DELIM) != -1) {
throw new EvaluationException("Couldn't find enum literal or type '" + getName().getValue() + "'", this);
}
if (target == null) {
throw new EvaluationException("Couldn't find type or property '" + getName().getValue() + "'", this);
} else {
throw new EvaluationException("Couldn't find property '" + getName().getValue() + "' for type "
+ findEClassifier(targetObj, ctx).getName(), this);
}
}
private Object handleCollection(final ExecutionContext ctx, final Collection col) {
final List<Object> result = new ArrayList<Object>();
for (final Iterator iter = col.iterator(); iter.hasNext();) {
final Object element = iter.next();
final EClassifier type = BuiltinMetaModel.getType(element);
final EStructuralFeature prop = BuiltinMetaModel.getAttribute(type, getName().getValue());
if (prop == null) {
throw new EvaluationException("Couldn't find property '" + getName().getValue()
+ "' for inner type " + type + "'", this);
} else {
final Object r = BuiltinMetaModel.getValue(prop, element);
if (r instanceof Collection) {
result.addAll((Collection<?>) r);
} else {
result.add(r);
}
}
}
return result;
}
/**
* analyzes in the following order if (target==null) 1) enumeration literal
* 2) variable 3) implicite var (e.g. this) 4) type literal
*
* otherwise it's a property
*/
public EClassifier analyze(final ExecutionContext ctx, final Set<AnalysationIssue> issues) {
EClassifier targetType = null;
// XXX hmm, all visible invocations of createFeatureCall always pass null
// as target. Perhaps, some subclasses pass non-null, though needs some investigation
if (target == null) {
// enum literal
final EEnumLiteral staticProp = getEnumLiteral(ctx);
if (staticProp != null) {
return createAnalyzeTrace(ctx, new FeatureCallTrace(BuiltinMetaModel.getReturnType(staticProp), staticProp));
}
// variable
Variable var = ctx.getVariable(getName().getValue());
if (var != null) {
assert var.getValue() == null || var.getValue() instanceof EClassifier : "variable should hold EClassifier";
return createAnalyzeTrace(ctx, new FeatureCallTrace((EClassifier) var.getValue(), Type.ENV_VAR_REF));
}
// implicite variable 'this'
var = ctx.getVariable(ExecutionContext.IMPLICIT_VARIABLE);
if (var != null) {
targetType = (EClassifier) var.getValue();
}
} else {
targetType = analyzeTarget(ctx, issues);
if (targetType == null) {
return createAnalyzeTrace(ctx, new FeatureCallTrace(null, Type.UNDESOLVED_TARGET_TYPE));
}
}
String additionalMsg = "";
// simple property
if (targetType != null) {
EStructuralFeature p = BuiltinMetaModel.getAttribute(targetType, getName().getValue());
if (p != null) {
return createAnalyzeTrace(ctx, new FeatureCallTrace(BuiltinMetaModel.getTypedElementType(p), p, targetType));
}
if ((p == null) && BuiltinMetaModel.isParameterizedType(targetType)) {
final EClassifier innerEClassifier = BuiltinMetaModel.getInnerType(targetType);
p = BuiltinMetaModel.getAttribute(innerEClassifier, getName().getValue());
if (p != null) {
EClassifier rt = BuiltinMetaModel.getTypedElementType(p);
if (BuiltinMetaModel.isParameterizedType(rt)) {
rt = BuiltinMetaModel.getInnerType(rt);
}
return createAnalyzeTrace(ctx, new FeatureCallTrace(BuiltinMetaModel.getListType(rt), targetType));
}
additionalMsg = " or inner type '" + innerEClassifier + "'";
}
}
if (target == null) {
final EClassifier type = ctx.getTypeForName(getName().getValue());
if (type != null) {
createAnalyzeTrace(ctx, new FeatureCallTrace(null, Type.UNSUPPORTED_CLASSIFIER_REF));
return EcorePackage.eINSTANCE.getEClass();
}
}
if (target == null) {
String txt = "";
if (targetType != null) {
txt = targetType.getName() + " property, ";
}
issues.add(new AnalysationIssue(AnalysationIssue.Type.FEATURE_NOT_FOUND, "Unknown " + txt
+ "variable, type or enumeration literal '" + getName().getValue() + "'", this));
return null;
}
issues.add(new AnalysationIssue(AnalysationIssue.Type.FEATURE_NOT_FOUND, "Couldn't find property '"
+ getName().getValue() + "' for type '" + targetType.getName() + "'" + additionalMsg, this));
return null;
}
protected EClassifier analyzeTarget(final ExecutionContext ctx, final Set<AnalysationIssue> issues) {
return getTarget().analyze(ctx, issues);
}
protected EClassifier findEClassifier(final Object value, final ExecutionContext ctx) {
final EClassifier t = BuiltinMetaModel.getType(value);
if (t == null) {
throw new EvaluationException("Unkown object type : " + value.getClass().getName(), this);
}
return t;
}
@Override
public String toString() {
return (getTarget() != null ? getTarget().toString() + "." : "") + name.getValue();
}
@Override
public int getStartOffset() {
if (getTarget() != null && super.getStartOffset() > getTarget().getStartOffset()) {
return getTarget().getStartOffset();
}
return super.getStartOffset();
}
}