diff options
13 files changed, 384 insertions, 26 deletions
diff --git a/plugins/org.eclipse.xtend.backend/META-INF/MANIFEST.MF b/plugins/org.eclipse.xtend.backend/META-INF/MANIFEST.MF index 5700d847..c56797d2 100644 --- a/plugins/org.eclipse.xtend.backend/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.xtend.backend/META-INF/MANIFEST.MF @@ -4,6 +4,8 @@ Bundle-Name: Backend Plug-in (Incubation) Bundle-SymbolicName: org.eclipse.xtend.backend Bundle-Version: 0.7.0 Export-Package: org.eclipse.xtend.backend;uses:="org.eclipse.xtend.backend.common", + org.eclipse.xtend.backend.aop, + org.eclipse.xtend.backend.aop.internal;x-internal:=true, org.eclipse.xtend.backend.common, org.eclipse.xtend.backend.expr;uses:="org.eclipse.xtend.backend.common", org.eclipse.xtend.backend.functions;uses:="org.eclipse.xtend.backend.common", diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceParamType.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AdviceParamType.java index 9ca0282b..30c444d1 100644 --- a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceParamType.java +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AdviceParamType.java @@ -8,7 +8,7 @@ http://www.eclipse.org/legal/epl-v10.html Contributors: Arno Haase - initial API and implementation */ -package org.eclipse.xtend.backend.aop.internal; +package org.eclipse.xtend.backend.aop; import org.eclipse.xtend.backend.common.BackendType; @@ -26,8 +26,6 @@ public final class AdviceParamType { _includingSubtypes = includingSubtypes; } - //TODO testen! - public boolean matches (BackendType type) { if (_includingSubtypes) return _type.isAssignableFrom (type); diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AdvisedFunction.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AdvisedFunction.java index ddab16fb..732a58d0 100644 --- a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AdvisedFunction.java +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AdvisedFunction.java @@ -10,9 +10,9 @@ Contributors: */ package org.eclipse.xtend.backend.aop; -import java.util.Arrays; import java.util.List; +import org.eclipse.xtend.backend.aop.internal.AdviceScopeCounter; import org.eclipse.xtend.backend.common.ExecutionContext; import org.eclipse.xtend.backend.common.Function; import org.eclipse.xtend.backend.util.ObjectWrapper; @@ -35,13 +35,15 @@ public final class AdvisedFunction { private final Function _function; private final List<AroundAdvice> _advice; private final int _firstCacheableIndex; + private final AdviceScopeCounter _scopeCounter; private final ThisJoinPointStaticPart _thisJoinPointStaticPart; - public AdvisedFunction (String functionName, Function function, List<AroundAdvice> advice) { + public AdvisedFunction (String functionName, Function function, List<AroundAdvice> advice, AdviceScopeCounter scopeCounter) { _function = function; _advice = advice; + _scopeCounter = scopeCounter; _thisJoinPointStaticPart = new ThisJoinPointStaticPart (functionName, _function); @@ -56,7 +58,7 @@ public final class AdvisedFunction { _firstCacheableIndex = advice.size(); } - public Object evaluate (ExecutionContext ctx, Object[] params) { + public Object evaluate (ExecutionContext ctx, List<?> params) { // the evaluation of the advice is performed in three stages: // 1. all advice that is "outside" the outermost non-cacheable advice // is always actually evaluated @@ -68,28 +70,28 @@ public final class AdvisedFunction { return proceedInternal (ctx, 0, params); } - private Object proceedInternal (final ExecutionContext ctx, final int indNextAdvice, final Object[] params) { + private Object proceedInternal (final ExecutionContext ctx, final int indNextAdvice, final List<?> params) { if (indNextAdvice >= _advice.size()) - return ctx.getFunctionInvoker().invoke (ctx, _function, Arrays.asList (params)); + return ctx.getFunctionInvoker().invoke (ctx, _function, params); if (indNextAdvice >= _firstCacheableIndex) { - final ObjectWrapper ow = ctx.getAdviceContext().getResultCache().get (new Triplet<Function, AroundAdvice, List<?>> (_function, _advice.get (indNextAdvice), Arrays.asList(params))); + final ObjectWrapper ow = ctx.getAdviceContext().getResultCache().get (new Triplet<Function, AroundAdvice, List<?>> (_function, _advice.get (indNextAdvice), params)); if (ow != null) return ow._content; } final ThisJoinPoint thisJoinPoint = new ThisJoinPoint (ctx.getStacktrace(), params) { @Override - public Object proceed (Object[] localParams) { + public Object proceed (List<?> localParams) { return proceedInternal (ctx, indNextAdvice+1, localParams); } }; final AroundAdvice advice = _advice.get (indNextAdvice); - final Object result = advice.evaluate (ctx, thisJoinPoint, _thisJoinPointStaticPart); + final Object result = advice.evaluate (ctx, _scopeCounter, thisJoinPoint, _thisJoinPointStaticPart); if (indNextAdvice >= _firstCacheableIndex) { - final Triplet<Function, AroundAdvice, List<?>> key = new Triplet<Function, AroundAdvice, List<?>> (_function, _advice.get (indNextAdvice), Arrays.asList(params)); + final Triplet<Function, AroundAdvice, List<?>> key = new Triplet<Function, AroundAdvice, List<?>> (_function, _advice.get (indNextAdvice), params); ctx.getAdviceContext().getResultCache().put (key, new ObjectWrapper (result)); } diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AroundAdvice.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AroundAdvice.java index 29c9c59d..8e5b6dbe 100644 --- a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AroundAdvice.java +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/AroundAdvice.java @@ -10,6 +10,7 @@ Contributors: */ package org.eclipse.xtend.backend.aop; +import org.eclipse.xtend.backend.aop.internal.AdviceScopeCounter; import org.eclipse.xtend.backend.common.ExecutionContext; import org.eclipse.xtend.backend.common.ExpressionBase; import org.eclipse.xtend.backend.common.SyntaxConstants; @@ -42,7 +43,8 @@ public final class AroundAdvice { * actually evaluates the advice, regardless of caching - that is context sensitive and must * be taken care of by callers */ - public Object evaluate (ExecutionContext ctx, ThisJoinPoint thisJoinPoint, ThisJoinPointStaticPart thisJoinPointStaticPart) { + public Object evaluate (ExecutionContext ctx, AdviceScopeCounter scopeCounter, ThisJoinPoint thisJoinPoint, ThisJoinPointStaticPart thisJoinPointStaticPart) { + scopeCounter.enterAdvice(); ctx.getLocalVarContext().getLocalVars().put (SyntaxConstants.THIS_JOINPOINT, thisJoinPoint); ctx.getLocalVarContext().getLocalVars().put (SyntaxConstants.THIS_JOINPOINT_STATICPART, thisJoinPointStaticPart); @@ -52,6 +54,7 @@ public final class AroundAdvice { finally { ctx.getLocalVarContext ().getLocalVars ().remove (SyntaxConstants.THIS_JOINPOINT); ctx.getLocalVarContext ().getLocalVars ().remove (SyntaxConstants.THIS_JOINPOINT_STATICPART); + scopeCounter.leaveAdvice(); } } diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/ExecutionPointcut.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/ExecutionPointcut.java index 995e39eb..6c0f75ec 100644 --- a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/ExecutionPointcut.java +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/ExecutionPointcut.java @@ -13,7 +13,6 @@ package org.eclipse.xtend.backend.aop; import java.util.List; import java.util.regex.Pattern; -import org.eclipse.xtend.backend.aop.internal.AdviceParamType; import org.eclipse.xtend.backend.common.BackendType; import org.eclipse.xtend.backend.common.Function; import org.eclipse.xtend.backend.util.Pair; @@ -62,8 +61,6 @@ public final class ExecutionPointcut implements Pointcut { _varArgsType = varArgsType; } - //TODO testen!!! - public boolean matches (String name, Function function) { if (! matchesName (name)) return false; diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/ThisJoinPoint.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/ThisJoinPoint.java index 1d36a311..0af2959f 100644 --- a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/ThisJoinPoint.java +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/ThisJoinPoint.java @@ -22,9 +22,9 @@ import org.eclipse.xtend.backend.common.StacktraceEntry; */ public abstract class ThisJoinPoint { private final List<StacktraceEntry> _stackTrace; - private final Object[] _params; + private final List<?> _params; - public ThisJoinPoint (List<StacktraceEntry> stackTrace, Object[] params) { + public ThisJoinPoint (List<StacktraceEntry> stackTrace, List<?> params) { _stackTrace = stackTrace; _params = params; } @@ -33,7 +33,7 @@ public abstract class ThisJoinPoint { return _stackTrace; } - public Object[] getParameters () { + public List<?> getParameters () { return _params; } @@ -41,5 +41,5 @@ public abstract class ThisJoinPoint { return proceed (_params); } - public abstract Object proceed (Object[] params); + public abstract Object proceed (List<?> params); } diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceContextImpl.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceContextImpl.java index b1698263..321902c4 100644 --- a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceContextImpl.java +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceContextImpl.java @@ -11,6 +11,7 @@ Contributors: package org.eclipse.xtend.backend.aop.internal; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -38,6 +39,22 @@ import org.eclipse.xtend.backend.util.Triplet; */ public final class AdviceContextImpl implements AdviceContext { private final List<AroundAdvice> _advice = new ArrayList<AroundAdvice> (); + private int _numAdviceInScope = 0; + + private final AdviceScopeCounter _scopeCounter = new AdviceScopeCounter () { + public void enterAdvice () { + _numAdviceInScope++; + } + + public void leaveAdvice () { + _numAdviceInScope--; + } + + public boolean isWithinAdvice () { + return _numAdviceInScope > 0; + } + }; + private final DoubleKeyCache <String, Function, AdvisedFunction> _advisedFunctionCache = new DoubleKeyCache<String, Function, AdvisedFunction> () { @Override @@ -48,7 +65,7 @@ public final class AdviceContextImpl implements AdviceContext { if (advice.getPointcut().matches (functionName, f)) applicableAdvice.add (advice); - return new AdvisedFunction (functionName, f, applicableAdvice); + return new AdvisedFunction (functionName, f, applicableAdvice, _scopeCounter); } }; @@ -79,17 +96,28 @@ public final class AdviceContextImpl implements AdviceContext { return result; } - //TODO test this (including the order in which advice is applied)!!! - /** * returns the advice to be applied to this function, starting with the outermost * advice, i.e. the advice that is to wrapped around all other advice applicable * to a given function. */ public AdvisedFunction getAdvice (String functionName, Function f) { - return _advisedFunctionCache.get (functionName, f); + // this distinction adds an implicit " && ! within <any advice>" to every pointcut. That is + // done to avoid endless recursion when a function is called from within advice that is + // applicable to this function + + if (_scopeCounter.isWithinAdvice()) { + return unadvisedFunction (functionName, f); + } + else + return _advisedFunctionCache.get (functionName, f); } + @SuppressWarnings("unchecked") + private AdvisedFunction unadvisedFunction (String functionName, Function f) { + return new AdvisedFunction (functionName, f, Collections.EMPTY_LIST, _scopeCounter); + } + public Map<Triplet<Function, AroundAdvice, List<?>>, ObjectWrapper> getResultCache () { return _resultCache; } diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceScopeCounter.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceScopeCounter.java new file mode 100644 index 00000000..56903f6e --- /dev/null +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/aop/internal/AdviceScopeCounter.java @@ -0,0 +1,24 @@ +/* +Copyright (c) 2008 Arno Haase. +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 +*/ +package org.eclipse.xtend.backend.aop.internal; + + +/** + * This abstraction serves to keep track of whether execution currently is within advice or not. + * + * @author Arno Haase (http://www.haase-consulting.com) + */ +public interface AdviceScopeCounter { + void enterAdvice (); + void leaveAdvice (); + + boolean isWithinAdvice (); +} diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/expr/InvocationOnObjectExpression.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/expr/InvocationOnObjectExpression.java index 97b51338..91314db6 100644 --- a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/expr/InvocationOnObjectExpression.java +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/expr/InvocationOnObjectExpression.java @@ -45,7 +45,7 @@ public final class InvocationOnObjectExpression extends ExpressionBase { // shortcut the evaluation and return null // // CAUTION - without this shortcut, the polymorphic resolution may be ambiguous in unexpected ways - if (_nullIfFirstParamIsNull && params.get(0) == null) { + if (_nullIfFirstParamIsNull && params.size() > 0 && params.get(0) == null) { ctx.logNullDeRef (getPos()); return null; } diff --git a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/functions/internal/FunctionDefContextImpl.java b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/functions/internal/FunctionDefContextImpl.java index 3716a49d..f41ce6a7 100644 --- a/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/functions/internal/FunctionDefContextImpl.java +++ b/plugins/org.eclipse.xtend.backend/src/org/eclipse/xtend/backend/functions/internal/FunctionDefContextImpl.java @@ -113,7 +113,8 @@ public final class FunctionDefContextImpl implements FunctionDefContextInternal ErrorHandler.handle ("could not resolve function '" + functionName + "' for parameter types " + StringHelper.getTypesAsString (params) + " - candidates were " + candidates, exc); } - return ctx.getFunctionInvoker().invoke (ctx, f, params); + return ctx.getAdviceContext().getAdvice (functionName, f).evaluate(ctx, params); +// return ctx.getFunctionInvoker().invoke (ctx, f, params); } /** diff --git a/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/AopTest.java b/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/AopTest.java new file mode 100644 index 00000000..41d3147f --- /dev/null +++ b/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/AopTest.java @@ -0,0 +1,233 @@ +/* +Copyright (c) 2008 Arno Haase. +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 + */ +package org.eclipse.xtend.backend.aop; + +import static org.eclipse.xtend.backend.testhelpers.BackendTestHelper.createEmptyExecutionContext; +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.Function; +import org.eclipse.xtend.backend.common.NamedFunction; +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.FunctionDefContextFactory; +import org.eclipse.xtend.backend.functions.FunctionDefContextInternal; +import org.eclipse.xtend.backend.functions.java.JavaDefinedFunction; +import org.eclipse.xtend.backend.testhelpers.CounterFunction; +import org.eclipse.xtend.backend.types.builtin.CollectionType; +import org.eclipse.xtend.backend.types.builtin.ObjectType; +import org.eclipse.xtend.backend.util.Pair; +import org.junit.Test; + + +/** + * + * @author Arno Haase (http://www.haase-consulting.com) + */ +public class AopTest { + @Test + public void testUncached () { + final ExecutionContext ctx = createEmptyExecutionContext(); + + final FunctionDefContextInternal fdc = new FunctionDefContextFactory (ctx.getTypesystem()).create(); + ctx.setFunctionDefContext (fdc); + + for (JavaDefinedFunction f: JavaDefinedFunction.createForEntireClass (CounterFunction.class, ctx.getTypesystem())) + fdc.register (new NamedFunction (f.getName(), f)); + + @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, "nextCounterValue", Collections.emptyList()).toString()); + assertEquals ("pre-11post-1", ctx.getFunctionDefContext().invoke (ctx, "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, "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, "nextCounterValue", Collections.emptyList()).toString()); + assertEquals ("pre-1pre-3post-3post-1", ctx.getFunctionDefContext().invoke (ctx, "nextCounterValue", Collections.emptyList()).toString()); + assertEquals ("pre-1pre-3post-3post-1", ctx.getFunctionDefContext().invoke (ctx, "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, "nextCounterValue", Collections.emptyList()).toString()); + } + + private void registerAdvice (ExecutionContext ctx, String prefix, String postfix, boolean proceed, Pointcut pointCut, boolean cached) { + ctx.setAdviceContext (ctx.getAdviceContext().copyWithAdvice (new AroundAdvice (ConcatAdviceFactory.createConcatExpression (prefix, postfix, proceed), pointCut, cached))); + } + + @SuppressWarnings("unchecked") + @Test + public void testVarArgsParamTypeMatching () { + final ExecutionContext ctx = createEmptyExecutionContext(); + + final FunctionDefContextInternal fdc = new FunctionDefContextFactory (ctx.getTypesystem()).create(); + ctx.setFunctionDefContext (fdc); + + for (JavaDefinedFunction f: JavaDefinedFunction.createForEntireClass (AopTestFunctions.class, ctx.getTypesystem())) + fdc.register (new NamedFunction (f.getName(), f)); + + 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, "f", Arrays.asList (new Object ())).toString()); + assertEquals ("object object+ f(Object)", ctx.getFunctionDefContext().invoke(ctx, "f", Arrays.asList ("")).toString()); + assertEquals ("object+ collection collection+ f(Collection)", ctx.getFunctionDefContext().invoke(ctx, "f", Arrays.asList (new HashSet<Object> ())).toString()); + assertEquals ("object+ collection+ f(List)", ctx.getFunctionDefContext().invoke(ctx, "f", Arrays.asList (new ArrayList<Object> ())).toString()); + } + + @SuppressWarnings("unchecked") + @Test + public void testExplicitParamTypeMatching () { + final ExecutionContext ctx = createEmptyExecutionContext(); + + final FunctionDefContextInternal fdc = new FunctionDefContextFactory (ctx.getTypesystem()).create(); + ctx.setFunctionDefContext (fdc); + + for (JavaDefinedFunction f: JavaDefinedFunction.createForEntireClass (AopTestFunctions.class, ctx.getTypesystem())) + fdc.register (new NamedFunction (f.getName(), f)); + + 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, "f", Arrays.asList (new Object ())).toString()); + assertEquals ("object object+ f(Object)", ctx.getFunctionDefContext().invoke(ctx, "f", Arrays.asList ("")).toString()); + assertEquals ("object+ collection collection+ f(Collection)", ctx.getFunctionDefContext().invoke(ctx, "f", Arrays.asList (new HashSet<Object> ())).toString()); + assertEquals ("object+ collection+ f(List)", ctx.getFunctionDefContext().invoke(ctx, "f", Arrays.asList (new ArrayList<Object> ())).toString()); + } + + @SuppressWarnings("unchecked") + @Test + public void testNameMatching () { + final ExecutionContext ctx = createEmptyExecutionContext(); + + final FunctionDefContextInternal fdc = new FunctionDefContextFactory (ctx.getTypesystem()).create(); + ctx.setFunctionDefContext (fdc); + + for (JavaDefinedFunction f: JavaDefinedFunction.createForEntireClass (AopTestFunctions.class, ctx.getTypesystem())) + fdc.register (new NamedFunction (f.getName(), f)); + + 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, "firstFunction", Collections.emptyList()).toString()); + assertEquals ("secondPre secondPost secondIn secondIn2 bothIn second", ctx.getFunctionDefContext().invoke (ctx, "secondFunction", Collections.emptyList()).toString()); + } + + private long _counter = 0; + + @SuppressWarnings("unchecked") + @Test + public void testCacheable () { + _counter = 0; + + final ExecutionContext ctx = createEmptyExecutionContext(); + + final FunctionDefContextInternal fdc = new FunctionDefContextFactory (ctx.getTypesystem()).create(); + ctx.setFunctionDefContext (fdc); + + for (JavaDefinedFunction f: JavaDefinedFunction.createForEntireClass (CounterFunction.class, ctx.getTypesystem())) + fdc.register (new NamedFunction (f.getName(), f)); + + fdc.register (new NamedFunction ("f", new Function () { + + public ExpressionBase getGuard () { + return null; + } + + public List<? extends BackendType> getParameterTypes () { + return new ArrayList<BackendType> (); + } + + public Object invoke (ExecutionContext localCtx, Object[] params) { + return _counter++; + } + + public boolean isCached () { + return 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, "f", Collections.EMPTY_LIST).toString()); + assertEquals ("first 3 second 4 third 2 0", ctx.getFunctionDefContext().invoke (ctx, "f", Collections.EMPTY_LIST).toString()); + assertEquals ("first 5 second 6 third 2 0", ctx.getFunctionDefContext().invoke (ctx, "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 ("nextCounterValue", Collections.EMPTY_LIST, true, null)); + toBeConcatenated.add (new LiteralExpression (" ", null)); + toBeConcatenated.add (ConcatAdviceFactory.createProceedExpression()); + + ctx.setAdviceContext (ctx.getAdviceContext().copyWithAdvice (new AroundAdvice (new ConcatExpression (toBeConcatenated, null), pointCut, cacheable))); + } +} + + diff --git a/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/AopTestFunctions.java b/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/AopTestFunctions.java new file mode 100644 index 00000000..b8a46409 --- /dev/null +++ b/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/AopTestFunctions.java @@ -0,0 +1,41 @@ +/* +Copyright (c) 2008 Arno Haase. +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 + */ +package org.eclipse.xtend.backend.aop; + +import java.util.Collection; +import java.util.List; + + +/** + * + * @author Arno Haase (http://www.haase-consulting.com) + */ +public class AopTestFunctions { + public String firstFunction () { + return "first"; + } + + public String secondFunction () { + return "second"; + } + + public String f (Object o) { + return "f(Object)"; + } + + public String f (Collection<?> c) { + return "f(Collection)"; + } + + public String f(List<?> l) { + return "f(List)"; + } +} diff --git a/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/ConcatAdviceFactory.java b/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/ConcatAdviceFactory.java new file mode 100644 index 00000000..3e053151 --- /dev/null +++ b/tests/org.eclipse.xtend.backend.test/src/org/eclipse/xtend/backend/aop/ConcatAdviceFactory.java @@ -0,0 +1,29 @@ +package org.eclipse.xtend.backend.aop; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.xtend.backend.common.ExpressionBase; +import org.eclipse.xtend.backend.common.SyntaxConstants; +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.expr.LocalVarEvalExpression; + + +class ConcatAdviceFactory { + public static ExpressionBase createConcatExpression (String prefix, String suffix, boolean callProceed) { + final List<ExpressionBase> toBeConcatenated = new ArrayList<ExpressionBase> (); + toBeConcatenated.add (new LiteralExpression (prefix, null)); + if (callProceed) + toBeConcatenated.add (createProceedExpression()); + toBeConcatenated.add (new LiteralExpression (suffix, null)); + + return new ConcatExpression (toBeConcatenated, null); + } + + public static ExpressionBase createProceedExpression () { + return new InvocationOnObjectExpression ("proceed", Arrays.asList (new LocalVarEvalExpression (SyntaxConstants.THIS_JOINPOINT, null)), true, null); + } +} |