/* 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 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.common.QualifiedName; import org.eclipse.xtend.backend.util.ObjectWrapper; import org.eclipse.xtend.backend.util.Triplet; /** * This class represents a function with all applicable Advice.<br> * * It could formally be treated as a function, but it is a conscious design decision * not to do that. The rationale behind that decision is that advice is bound to a * name or context (via its PointCut) whereas a function is not. In other words, if * a function is passed around in a program, the applicable advice can vary - and * for that reason, advice is only applied "just in time", based on the then current * name and context. * * @author Arno Haase (http://www.haase-consulting.com) * @author Andr� Arnold */ 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 (QualifiedName functionName, Function function, List<AroundAdvice> advice, AdviceScopeCounter scopeCounter) { _function = function; _advice = advice; _scopeCounter = scopeCounter; _thisJoinPointStaticPart = new ThisJoinPointStaticPart (functionName, _function); if (function.isCached()) { int firstCacheableIndex = advice.size(); while (firstCacheableIndex > 0 && advice.get (firstCacheableIndex - 1).isCacheable()) firstCacheableIndex--; _firstCacheableIndex = firstCacheableIndex; } else _firstCacheableIndex = advice.size(); } 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 // 2. advice that is cacheable but not yet cached is evaluated and cached // (iff the function is cached) // 3. once cached advice is encountered, the evaluation is short-circuited // and the result from the cache is returned return proceedInternal (ctx, 0, params); } private Object proceedInternal (final ExecutionContext ctx, final int indNextAdvice, final List<?> params) { if (indNextAdvice >= _advice.size()) 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), params)); if (ow != null) return ow._content; } final ThisJoinPoint thisJoinPoint = new ThisJoinPoint (ctx.getStacktrace(), params) { @Override public Object proceedWithParams (List<?> localParams) { return proceedInternal (ctx, indNextAdvice+1, localParams); } }; final AroundAdvice advice = _advice.get (indNextAdvice); 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), params); ctx.getAdviceContext().getResultCache().put (key, new ObjectWrapper (result)); } return result; } }