/*******************************************************************************
* Copyright (c) 2012 BMW Car IT 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
*******************************************************************************/
package org.jnario.compiler;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Sets.newHashSet;
import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.getNode;
import static org.eclipse.xtext.util.Strings.convertToJavaString;
import static org.jnario.jvmmodel.DoubleArrowSupport.isDoubleArrow;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtend.core.compiler.XtendCompiler;
import org.eclipse.xtext.common.types.JvmGenericType;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.serializer.ISerializer;
import org.eclipse.xtext.xbase.XAbstractFeatureCall;
import org.eclipse.xtext.xbase.XBinaryOperation;
import org.eclipse.xtext.xbase.XClosure;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.XFeatureCall;
import org.eclipse.xtext.xbase.XNullLiteral;
import org.eclipse.xtext.xbase.XSwitchExpression;
import org.eclipse.xtext.xbase.XbaseFactory;
import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable;
import org.jnario.Assertion;
import org.jnario.MockLiteral;
import org.jnario.Should;
import org.jnario.ShouldThrow;
import org.jnario.lib.Assert;
import org.jnario.util.MockingSupport;
import org.jnario.util.SourceAdapter;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
/**
* @author Sebastian Benz - Initial contribution and API
*/
public class JnarioCompiler extends XtendCompiler {
@Inject
private JnarioExpressionHelper expressionHelper;
@Inject ISerializer serializer;
@Override
public void internalToConvertedExpression(XExpression obj,
ITreeAppendable appendable) {
if (obj instanceof Assertion) {
_toJavaExpression((Assertion) obj, appendable);
} else if (obj instanceof Should) {
_toJavaExpression((Should) obj, appendable);
} else if (obj instanceof ShouldThrow) {
_toJavaExpression((ShouldThrow) obj, appendable);
} else if (obj instanceof MockLiteral) {
_toJavaExpression((MockLiteral) obj, appendable);
} else {
super.internalToConvertedExpression(obj, appendable);
}
}
@Override
public void doInternalToJavaStatement(XExpression obj,
ITreeAppendable appendable, boolean isReferenced) {
if (obj instanceof Assertion) {
_toJavaStatement((Assertion) obj, appendable, isReferenced);
} else if (obj instanceof Should) {
_toJavaStatement((Should) obj, appendable, isReferenced);
} else if (obj instanceof ShouldThrow) {
_toJavaStatement((ShouldThrow) obj, appendable, isReferenced);
} else if (obj instanceof MockLiteral) {
_toJavaStatement((MockLiteral) obj, appendable, isReferenced);
} else
super.doInternalToJavaStatement(obj, appendable, isReferenced);
}
public void _toJavaStatement(ShouldThrow should, ITreeAppendable b, boolean isReferenced) {
if (should.getType() == null || should.getType().getType() == null) {
return;
}
String expectedException = b.declareSyntheticVariable(should, "expectedException");
b.newLine().append("boolean ").append(expectedException).append(" = false;").newLine();
String message = b.declareSyntheticVariable(should, "message");
b.append("String ").append(message).append(" = \"\";");
b.newLine().append("try{").increaseIndentation();
toJavaStatement(should.getExpression(), b, false);
b.newLine().append(message).append(" = \"Expected \" + ")
.append(should.getType().getType())
.append(".class.getName() + \" for ")
.append(javaStringNewLine())
.append(" ")
.append(serialize(should.getExpression()).replace("\n", "\n ")).append(javaStringNewLine())
.append(" with:\"");
appendValues(should.getExpression(), b, new HashSet<String>());
b.append(";");
b.decreaseIndentation().newLine().append("}catch(").increaseIndentation()
.append(should.getType().getType()).append(" e){").newLine()
.append(expectedException).append(" = true;")
.decreaseIndentation().newLine().append("}");
b.newLine()
.append(assertType(should))
.append(".assertTrue(").append(message).append(", ")
.append(expectedException).append(");");
}
public void _toJavaStatement(Should should, ITreeAppendable b,
boolean isReferenced) {
_toShouldExpression(should, b, should.isNot());
}
private void _toShouldExpression(XBinaryOperation should,
ITreeAppendable b, boolean isNot) {
if(should.getRightOperand() instanceof XNullLiteral){
_toShouldBeNullExpression(should, b, isNot);
}else{
toShouldBeExpression(should, b, isNot);
}
}
private void toShouldBeExpression(XBinaryOperation should,
ITreeAppendable b, boolean isNot) {
super._toJavaStatement(should, b, true);
b.newLine().append(assertType(should));
if (isNot) {
b.append(".assertFalse(");
} else {
b.append(".assertTrue(");
}
generateMessageFor(should, b);
b.append(" + \"" + javaStringNewLine() + "\", ");
super._toJavaExpression(should, b);
b.append(");").newLine();
}
private void _toShouldBeNullExpression(XBinaryOperation should,
ITreeAppendable b, boolean isNot) {
super.toJavaStatement(should.getLeftOperand(), b, true);
b.newLine().append(assertType(should));
if (isNot) {
b.append(".assertNotNull(");
} else {
b.append(".assertNull(");
}
generateNullMessageFor(should, b);
b.append(" + \"" + javaStringNewLine() + "\", ");
super.toJavaExpression(should.getLeftOperand(), b);
b.append(");").newLine();
}
protected XFeatureCall createFeatureCall(
JvmIdentifiableElement nullValueMatcher) {
XFeatureCall featureCall = XbaseFactory.eINSTANCE.createXFeatureCall();
featureCall.setFeature(nullValueMatcher);
return featureCall;
}
protected JvmIdentifiableElement getNullValueMatcher(XBinaryOperation should) {
return getMethod(should, org.jnario.lib.Should.class.getName(), "nullValue");
}
protected JvmIdentifiableElement getMethod(XBinaryOperation should, String type, String methodName, String...argumentTypes) {
JvmGenericType coreMatchersType = (JvmGenericType) jvmType(type, should);
if(coreMatchersType == null){
return null;
}
Iterable<JvmOperation> operations = Iterables.filter(coreMatchersType.getMembers(), JvmOperation.class);
for (JvmOperation jvmOperation : operations) {
if(methodName.equals(jvmOperation.getSimpleName()) && hasArguments(jvmOperation, argumentTypes)){
return jvmOperation;
}
}
return null;
}
private boolean hasArguments(JvmOperation jvmOperation,
String[] argumentTypes) {
if(jvmOperation.getParameters().size() != argumentTypes.length){
return false;
}
for (int i = 0; i < argumentTypes.length; i++) {
String argumentType = argumentTypes[i];
JvmTypeReference actual = getTypeComputationServices().getTypeReferences().getTypeForName(argumentType, jvmOperation);
JvmTypeReference expected = jvmOperation.getParameters().get(i).getParameterType();
// System.out.println(expected.getQualifiedName() + "=>" + actual.getQualifiedName());
if(!expected.getQualifiedName().equals(actual.getQualifiedName())){
return false;
}
}
return true;
}
private String javaStringNewLine() {
return convertToJavaString("\n");
}
public void _toJavaExpression(MockLiteral expr, ITreeAppendable b) {
JvmType mockito = getTypeComputationServices().getTypeReferences().findDeclaredType(MockingSupport.CLASS_NAME, expr);
b.append(mockito).append(".mock(");
b.append(expr.getType()).append(".class");
b.append(")");
}
public void _toJavaStatement(MockLiteral expr, ITreeAppendable b, boolean isReferenced) {
generateComment(expr, b, isReferenced);
}
public void _toJavaExpression(Should should, ITreeAppendable b) {
b.append("true");
}
public void _toJavaExpression(ShouldThrow should, ITreeAppendable b) {
b.append("true");
}
public void _toJavaStatement(Assertion assertion, ITreeAppendable b,
boolean isReferenced) {
if (assertion.getExpression() == null) {
return;
}
generateSingleAssertion(assertion.getExpression(), b);
}
public void _toJavaExpression(Assertion assertion, ITreeAppendable b) {
b.append("true");
}
private void generateSingleAssertion(XExpression expr, ITreeAppendable b) {
toJavaStatement(expr, b, true);
b.newLine();
b.append(assertType(expr));
b.append(".assertTrue(");
generateMessageFor(expr, b);
b.append(" + \"" + javaStringNewLine() + "\", ");
toJavaExpression(expr, b);
b.append(");");
b.newLine();
}
private JvmType assertType(XExpression expr) {
return jvmType(Assert.class, expr);
}
private boolean isVoid(XExpression expr) {
JvmTypeReference type = getType(expr);
return getTypeComputationServices().getTypeReferences().is(type, Void.TYPE);
}
private JvmType jvmType(Class<?> type, EObject context) {
return jvmType(type.getName(), context);
}
private JvmType jvmType(String type, EObject context) {
JvmTypeReference jvmTypeReference = getTypeComputationServices().getTypeReferences().getTypeForName(type, context);
if(jvmTypeReference == null){
return null;
}
return jvmTypeReference.getType();
}
public void generateMessageFor(Should should, ITreeAppendable b) {
b.append("\"\\nExpected ");
b.append(serialize(should));
b.append(" but\"");
Set<String> valueExpressions = newHashSet();
XExpression left = should.getLeftOperand();
toLiteralValue(left, b, valueExpressions);
appendValues(left, b, valueExpressions);
XExpression right = should.getRightOperand();
toLiteralValue(right, b, valueExpressions);
appendValues(right, b, valueExpressions);
if (valueExpressions.isEmpty()) {
b.append(" + \" did not.\"");
}
}
private void generateNullMessageFor(XBinaryOperation should, ITreeAppendable b) {
b.append("\"\\nExpected ");
b.append(serialize(should));
b.append("\\n but is \"");
toValue(should.getLeftOperand(), b);
}
private void generateMessageFor(XExpression expression, ITreeAppendable b) {
b.append("\"\\nExpected ");
b.append(serialize(expression));
b.append(" but\"");
Set<String> valueExpressions = newHashSet();
appendValues(expression, b, valueExpressions);
if (valueExpressions.isEmpty()) {
b.append(" + \" did not.\"");
}
}
private void appendValues(XExpression expression, ITreeAppendable b,
Set<String> valueExpressions) {
Iterator<XExpression> subExpressions = allSubExpressions(expression);
if (subExpressions.hasNext()) {
while (subExpressions.hasNext()) {
XExpression subExpression = subExpressions.next();
appendActualValues(subExpression, b, valueExpressions);
}
} else {
toLiteralValue(expression, b, valueExpressions);
}
}
protected String serialize(XExpression expression) {
INode node = findNode(expression);
if(node == null){
return "";
}
String result = node.getText();
result = result.trim();
result = removeSurroundingParentheses(result);
return convertToJavaString(result);
}
private INode findNode(XExpression expression) {
INode node = getNode(expression);
if(node != null) {
return node;
}
EObject source = SourceAdapter.find(expression);
while(node == null && isExpressions(source)){
node = getNode(source);
source = source.eContainer();
}
return node;
}
private boolean isExpressions(EObject source) {
return source != null && source instanceof XExpression;
}
protected String removeSurroundingParentheses(String result) {
if (result.startsWith("(") && result.endsWith(")")) {
result = result.substring(1, result.length() - 1);
}
return result.trim();
}
protected void appendActualValues(XExpression expression,
ITreeAppendable b, Set<String> valueExpressions) {
toLiteralValue(expression, b, valueExpressions);
Iterator<XExpression> subExpressions = allSubExpressions(expression);
while (subExpressions.hasNext()) {
XExpression subExpression = subExpressions.next();
appendActualValues(subExpression, b, valueExpressions);
}
}
protected Iterator<XExpression> allSubExpressions(XExpression expression) {
Predicate<XExpression> noSwitchCases = new Predicate<XExpression>() {
public boolean apply(XExpression e) {
return !(e.eContainer() instanceof XSwitchExpression);
}
};
Predicate<XExpression> noLiteralExpressions = new Predicate<XExpression>() {
public boolean apply(XExpression expr) {
return !expressionHelper.isLiteral(expr);
}
};
Iterable<XExpression> subExpressions = filter(expression.eContents(), XExpression.class);
subExpressions = filter(subExpressions, noLiteralExpressions);
subExpressions = filter(subExpressions, noSwitchCases);
return subExpressions.iterator();
}
protected void toLiteralValue(XExpression expression, ITreeAppendable b,
Set<String> valueMappings) {
if (expressionHelper.isLiteral(expression)) {
return;
}
if (isVoid(expression)) {
return;
}
if(isClosure(expression)){
return;
}
toValue(expression, b, valueMappings);
}
private void toValue(XExpression expression, ITreeAppendable b,
Set<String> valueMappings) {
String expr = serialize(expression);
if (expr.isEmpty() || valueMappings.contains(expr)) {
return;
}
valueMappings.add(expr);
b.append("\n + \"\\n ");
b.append(expr);
b.append(" is \"");
toValue(expression, b);
}
private void toValue(XExpression expression, ITreeAppendable b) {
b.append(" + new ");
b.append("org.hamcrest.StringDescription");
b.append("().appendValue(");
toJavaExpression(expression, b);
b.append(").toString()");
}
private boolean isClosure(XExpression expression) {
JvmTypeReference type = getType(expression);
return type.getQualifiedName().startsWith("org.eclipse.xtext.xbase.lib.Functions");
}
@Override
protected boolean isVariableDeclarationRequired(XExpression expr,
ITreeAppendable b) {
if (expr instanceof Assertion) {
return false;
}
return super.isVariableDeclarationRequired(expr, b);
}
@Override
protected void _toJavaStatement(XAbstractFeatureCall expr,
ITreeAppendable b, boolean isReferenced) {
if(!isDoubleArrow(expr)){
super._toJavaStatement(expr, b, isReferenced);
return;
}
XBinaryOperation doubleArrow = (XBinaryOperation) expr;
if(doubleArrow.getRightOperand() instanceof XClosure){
super._toJavaStatement(expr, b, isReferenced);
return;
}else{
_toShouldExpression((XBinaryOperation) expr, b, false);
}
}
}