/*
Copyright (c) 2008 Arno Haase, Andr� Arnold.
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:
Arno Haase - initial API and implementation
Andr� Arnold
*/
package org.eclipse.xtend.backend.aop;
import static org.eclipse.xtend.backend.testhelpers.BackendTestHelper.createEmptyExecutionContext;
import static org.eclipse.xtend.backend.testhelpers.BackendTestHelper.createEmptyFdc;
import static org.eclipse.xtend.backend.testhelpers.BackendTestHelper.createFdc;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.eclipse.xtend.backend.common.AdviceContext;
import org.eclipse.xtend.backend.common.BackendType;
import org.eclipse.xtend.backend.common.ExecutionContext;
import org.eclipse.xtend.backend.common.ExpressionBase;
import org.eclipse.xtend.backend.common.NamedFunction;
import org.eclipse.xtend.backend.common.QualifiedName;
import org.eclipse.xtend.backend.expr.ConcatExpression;
import org.eclipse.xtend.backend.expr.InvocationOnObjectExpression;
import org.eclipse.xtend.backend.expr.LiteralExpression;
import org.eclipse.xtend.backend.functions.AbstractFunction;
import org.eclipse.xtend.backend.functions.FunctionDefContextInternal;
import org.eclipse.xtend.backend.testhelpers.CounterFunction;
import org.eclipse.xtend.backend.types.CompositeTypesystem;
import org.eclipse.xtend.backend.types.builtin.CollectionType;
import org.eclipse.xtend.backend.types.builtin.LongType;
import org.eclipse.xtend.backend.types.builtin.ObjectType;
import org.eclipse.xtend.backend.types.builtin.StringType;
import org.eclipse.xtend.backend.util.Pair;
import org.junit.Test;
/**
*
* @author Arno Haase (http://www.haase-consulting.com)
* @author Andr� Arnold
*/
public class AopTest {
@Test
public void testUncached () {
final ExecutionContext ctx = createEmptyExecutionContext();
final FunctionDefContextInternal fdc = createFdc (ctx.getTypesystem(), CounterFunction.class);
ctx.setFunctionDefContext (fdc);
@SuppressWarnings("unchecked")
final Pointcut pointCut = new ExecutionPointcut ("*", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
registerAdvice (ctx, "pre-1", "post-1", true, pointCut, true);
assertEquals ("pre-10post-1", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("nextCounterValue"), Collections.emptyList()).toString());
assertEquals ("pre-11post-1", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("nextCounterValue"), Collections.emptyList()).toString());
// test wrapped advice, particularly the application order
final AdviceContext advCtx1 = ctx.getAdviceContext();
registerAdvice (ctx, "pre-2", "post-2", true, pointCut, true);
assertEquals ("pre-1pre-22post-2post-1", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("nextCounterValue"), Collections.emptyList()).toString());
// test without calling proceed, and at the same time reverting to an earlier AdviceContext
ctx.setAdviceContext (advCtx1);
registerAdvice (ctx, "pre-3", "post-3", false, pointCut, true);
assertEquals ("pre-1pre-3post-3post-1", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("nextCounterValue"), Collections.emptyList()).toString());
assertEquals ("pre-1pre-3post-3post-1", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("nextCounterValue"), Collections.emptyList()).toString());
assertEquals ("pre-1pre-3post-3post-1", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("nextCounterValue"), Collections.emptyList()).toString());
// test that the previous invocations did not reach the actual function implementation
ctx.setAdviceContext (advCtx1);
assertEquals ("pre-13post-1", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("nextCounterValue"), Collections.emptyList()).toString());
}
private void registerAdvice (ExecutionContext ctx, String prefix, String postfix, boolean proceed, Pointcut pointCut, boolean cached) {
final AroundAdvice newAdvice = new AroundAdvice (ConcatAdviceFactory.createConcatExpression (prefix, postfix, proceed), pointCut, cached);
newAdvice.setFunctionDefContext (createEmptyFdc (new CompositeTypesystem ()));
ctx.setAdviceContext (ctx.getAdviceContext().copyWithAdvice (newAdvice));
}
private void registerAdviceWithParams (ExecutionContext ctx, List<ExpressionBase> params, Pointcut pointCut, boolean cached) {
final AroundAdvice newAdvice = new AroundAdvice (ConcatAdviceFactory.createParameterizedProceedExpression(params), pointCut, cached);
newAdvice.setFunctionDefContext (createEmptyFdc (new CompositeTypesystem ()));
ctx.setAdviceContext (ctx.getAdviceContext().copyWithAdvice (newAdvice));
}
@SuppressWarnings("unchecked")
@Test
public void testVarArgsParamTypeMatching () {
final ExecutionContext ctx = createEmptyExecutionContext();
final FunctionDefContextInternal fdc = createFdc (ctx.getTypesystem(), AopTestFunctions.class);
ctx.setFunctionDefContext (fdc);
final Pointcut pointCutObject = new ExecutionPointcut ("f", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, false));
final Pointcut pointCutObjectPlus = new ExecutionPointcut ("f", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutCollection = new ExecutionPointcut ("f", Collections.EMPTY_LIST, true, new AdviceParamType (CollectionType.INSTANCE, false));
final Pointcut pointCutCollectionPlus = new ExecutionPointcut ("f", Collections.EMPTY_LIST, true, new AdviceParamType (CollectionType.INSTANCE, true));
registerAdvice (ctx, "object ", "", true, pointCutObject, false);
registerAdvice (ctx, "object+ ", "", true, pointCutObjectPlus, false);
registerAdvice (ctx, "collection ", "", true, pointCutCollection, false);
registerAdvice (ctx, "collection+ ", "", true, pointCutCollectionPlus, false);
assertEquals ("object object+ f(Object)", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName ("f"), Arrays.asList (new Object ())).toString());
assertEquals ("object object+ f(Object)", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName ("f"), Arrays.asList ("")).toString());
assertEquals ("object+ collection collection+ f(Collection)", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName ("f"), Arrays.asList (new HashSet<Object> ())).toString());
assertEquals ("object+ collection+ f(List)", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName ("f"), Arrays.asList (new ArrayList<Object> ())).toString());
}
@SuppressWarnings("unchecked")
@Test
public void testExplicitParamTypeMatching () {
final ExecutionContext ctx = createEmptyExecutionContext();
final FunctionDefContextInternal fdc = createFdc (ctx.getTypesystem(), AopTestFunctions.class);
ctx.setFunctionDefContext (fdc);
final Pointcut pointCutObject = new ExecutionPointcut ("f", Arrays.asList (new Pair<String, AdviceParamType> ("o", new AdviceParamType (ObjectType.INSTANCE, false))), false, null);
final Pointcut pointCutObjectPlus = new ExecutionPointcut ("f", Arrays.asList (new Pair<String, AdviceParamType> ("o", new AdviceParamType (ObjectType.INSTANCE, true))), false, null);
final Pointcut pointCutCollection = new ExecutionPointcut ("f", Arrays.asList (new Pair<String, AdviceParamType> ("o", new AdviceParamType (CollectionType.INSTANCE, false))), false, null);
final Pointcut pointCutCollectionPlus = new ExecutionPointcut ("f", Arrays.asList (new Pair<String, AdviceParamType> ("o", new AdviceParamType (CollectionType.INSTANCE, true))), false, null);
registerAdvice (ctx, "object ", "", true, pointCutObject, false);
registerAdvice (ctx, "object+ ", "", true, pointCutObjectPlus, false);
registerAdvice (ctx, "collection ", "", true, pointCutCollection, false);
registerAdvice (ctx, "collection+ ", "", true, pointCutCollectionPlus, false);
assertEquals ("object object+ f(Object)", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName ("f"), Arrays.asList (new Object ())).toString());
assertEquals ("object object+ f(Object)", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName ("f"), Arrays.asList ("")).toString());
assertEquals ("object+ collection collection+ f(Collection)", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName ("f"), Arrays.asList (new HashSet<Object> ())).toString());
assertEquals ("object+ collection+ f(List)", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName ("f"), Arrays.asList (new ArrayList<Object> ())).toString());
}
@SuppressWarnings("unchecked")
@Test
public void testNameMatching () {
final ExecutionContext ctx = createEmptyExecutionContext();
final FunctionDefContextInternal fdc = createFdc (ctx.getTypesystem(), AopTestFunctions.class);
ctx.setFunctionDefContext (fdc);
final Pointcut pointCutFirstPre = new ExecutionPointcut ("first*", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutFirstPost = new ExecutionPointcut ("*tFunction", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutFirstIn = new ExecutionPointcut ("*tF*", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutFirstIn2 = new ExecutionPointcut ("fir*on", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutSecondPre = new ExecutionPointcut ("second*", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutSecondPost = new ExecutionPointcut ("*dFunction", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutSecondIn = new ExecutionPointcut ("*dF*", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutSecondIn2 = new ExecutionPointcut ("sec*on", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
final Pointcut pointCutBothIn = new ExecutionPointcut ("*Fun*", Collections.EMPTY_LIST, true, new AdviceParamType (ObjectType.INSTANCE, true));
registerAdvice (ctx, "firstPre ", "", true, pointCutFirstPre, false);
registerAdvice (ctx, "firstPost ", "", true, pointCutFirstPost, false);
registerAdvice (ctx, "firstIn ", "", true, pointCutFirstIn, false);
registerAdvice (ctx, "firstIn2 ", "", true, pointCutFirstIn2, false);
registerAdvice (ctx, "secondPre ", "", true, pointCutSecondPre, false);
registerAdvice (ctx, "secondPost ", "", true, pointCutSecondPost, false);
registerAdvice (ctx, "secondIn ", "", true, pointCutSecondIn, false);
registerAdvice (ctx, "secondIn2 ", "", true, pointCutSecondIn2, false);
registerAdvice (ctx, "bothIn ", "", true, pointCutBothIn, false);
assertEquals ("firstPre firstPost firstIn firstIn2 bothIn first", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("firstFunction"), Collections.emptyList()).toString());
assertEquals ("secondPre secondPost secondIn secondIn2 bothIn second", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("secondFunction"), Collections.emptyList()).toString());
}
@Test
public void testParamOverriding () {
final ExecutionContext ctx = createEmptyExecutionContext();
final FunctionDefContextInternal fdc = createFdc (ctx.getTypesystem(), AopTestFunctions.class);
ctx.setFunctionDefContext (fdc);
List<Pair<String, AdviceParamType>> advParamType = new ArrayList<Pair<String, AdviceParamType>>();
advParamType.add(new Pair<String, AdviceParamType>("s", new AdviceParamType(StringType.INSTANCE, true)));
final Pointcut pointCut = new ExecutionPointcut ("func", advParamType, false, null);
List<ExpressionBase> advParams = new ArrayList<ExpressionBase>();
advParams.add(new LiteralExpression("override", null));
registerAdviceWithParams(ctx, advParams, pointCut, false);
assertEquals("override", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName("func"), Arrays.asList("original")));
}
@Test
public void testPartialParamOverriding () {
final ExecutionContext ctx = createEmptyExecutionContext();
final FunctionDefContextInternal fdc = createFdc (ctx.getTypesystem(), AopTestFunctions.class);
ctx.setFunctionDefContext (fdc);
List<Pair<String, AdviceParamType>> advParamType = new ArrayList<Pair<String, AdviceParamType>>();
advParamType.add(new Pair<String, AdviceParamType>("s", new AdviceParamType(StringType.INSTANCE, true)));
final Pointcut pointCut = new ExecutionPointcut ("otherFunc", advParamType, true, new AdviceParamType (ObjectType.INSTANCE, true));
List<ExpressionBase> advParams = new ArrayList<ExpressionBase>();
advParams.add(new LiteralExpression("override", null));
registerAdviceWithParams(ctx, advParams, pointCut, false);
assertEquals("override original2", ctx.getFunctionDefContext().invoke(ctx, new QualifiedName("otherFunc"), Arrays.asList("original1", "original2")));
}
private long _counter = 0;
@SuppressWarnings("unchecked")
@Test
public void testCacheable () {
_counter = 0;
final ExecutionContext ctx = createEmptyExecutionContext();
final FunctionDefContextInternal fdc = createFdc (ctx.getTypesystem(), CounterFunction.class);
ctx.setFunctionDefContext (fdc);
fdc.register (new NamedFunction (new QualifiedName ("f"), new AbstractFunction (null, new ArrayList<BackendType> (), LongType.INSTANCE, true) {
public Object invoke (ExecutionContext localCtx, Object[] params) {
return _counter++;
}
}), true);
final Pointcut pointCut = new ExecutionPointcut ("f", Collections.EMPTY_LIST, false, null);
registerSideEffectAdvice (ctx, "first ", pointCut, true);
registerSideEffectAdvice (ctx, "second ", pointCut, false);
registerSideEffectAdvice (ctx, "third ", pointCut, true);
assertEquals ("first 0 second 1 third 2 0", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("f"), Collections.EMPTY_LIST).toString());
assertEquals ("first 3 second 4 third 2 0", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("f"), Collections.EMPTY_LIST).toString());
assertEquals ("first 5 second 6 third 2 0", ctx.getFunctionDefContext().invoke (ctx, new QualifiedName ("f"), Collections.EMPTY_LIST).toString());
}
@SuppressWarnings("unchecked")
private void registerSideEffectAdvice (ExecutionContext ctx, String marker, Pointcut pointCut, boolean cacheable) {
final List<ExpressionBase> toBeConcatenated = new ArrayList<ExpressionBase> ();
toBeConcatenated.add (new LiteralExpression (marker, null));
toBeConcatenated.add (new InvocationOnObjectExpression (new QualifiedName ("nextCounterValue"), Collections.EMPTY_LIST, true, null));
toBeConcatenated.add (new LiteralExpression (" ", null));
toBeConcatenated.add (ConcatAdviceFactory.createProceedExpression());
final AroundAdvice advice = new AroundAdvice (new ConcatExpression (toBeConcatenated, null), pointCut, cacheable);
advice.setFunctionDefContext (ctx.getFunctionDefContext());
ctx.setAdviceContext (ctx.getAdviceContext().copyWithAdvice (advice));
}
}