Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[aspectj-users] An unclarity in prog guide about precedences and a problem in using perthis

                   Hi!

  I had some difficulty figuring out what declare precedence does in a
specific case. Here is a rough sketch of what I have:


abstract aspect AbstractA {

  abstract protected pointcut aPointcut();

  around() : aPointcut() {
    // do something, call proceed
  }

}

aspect A extends AbstractA {
  protected pointcut aPointcut() : // some definition here
}

aspect B {
  // some advice here that binds to (at least some of) the same join
points as A
}


  Now, if I want A to dominate B, do I simply say
declare precedence : A, B; 
  ?

  This is a little unclear to interpret from the programming guide, as
it says: 
"If aspect A is matched earlier than aspect B in some declare precedence
form, then all advice in concrete aspect A has precedence over all
advice in concrete aspect B when they are on the same join point."
http://dev.eclipse.org/viewcvs/indextech.cgi/~checkout~/aspectj-home/doc
/progguide/semantics-advice.html#d0e5915

  Now, the advice is not defined in the concrete aspect A but in its
abstract super aspect AbstractA. 

  I tried it out and it seems the in relation to the above quotation on
precedence, the advice in parent aspect are considered to be advice in
the inheriting concrete aspect. I.e. the above declare precedence clause
causes the advice inherited from AbstractA to A to dominate advice in B.


  I think the programming guide requires a little clarification here. Or
maybe I missed something there?

///////////////////////////////////////

  Another thing: In the programming guide it says:
"The criteria used to determine how an aspect is instantiated is
inherited from its parent aspect."
http://dev.eclipse.org/viewcvs/indextech.cgi/~checkout~/aspectj-home/doc
/progguide/semantics-aspects.html#d0e6619

  Now here's what I have:

  First, a rather simple aspect to cache the values of heavy
calculations: 

import java.util.Map;
import java.util.HashMap;

import org.aspectj.lang.Signature;

// NOTE THE perthis ASSOCIATION: 
public abstract aspect CachingAspect perthis(cachedOperations()) {
  
  private Map _cachedReturnValues = new HashMap();
  private Map _cachedEqualities   = new HashMap();
  
  protected pointcut mutatorOperations();
  abstract protected pointcut cachedOperations();

  protected pointcut equalsOperation(Object other);
  
  // after returning advice to mutator operations that invalidates the
cache 
  after() returning: mutatorOperations() {
    _cachedReturnValues.clear();
    _cachedEqualities.clear();
  }
  
  // around advice to cached operations that checks if the value is 
  // already in the cache. If not, calculate it.
  Object around(): cachedOperations() {
    Signature method = thisJoinPointStaticPart.getSignature();
    if(_cachedReturnValues.containsKey(method))
      return _cachedReturnValues.get(method);
    Object retVal = proceed();
    _cachedReturnValues.put(method, retVal);
    return retVal;
	  }

  boolean around(Object other) : equalsOperation(other) {
    if(_cachedEqualities.containsKey(other))
      // simplify this once autoboxing is available
      return ((Boolean)_cachedEqualities.get(other)).booleanValue();
    boolean isEqual = proceed(other);
    _cachedEqualities.put(other, Boolean.valueOf(isEqual));
    return isEqual;
  }
  
}

  Here's the unit test (that fails):

import junit.framework.Assert;
import junit.framework.TestCase;


public class CachingAspectTest extends TestCase {
  
  private CacheTestHelper _helper = new CacheTestHelper();

  private CacheTestHelper _helper;
  
  protected void setUp() throws Exception {
    super.setUp();
    _helper = new CacheTestHelper();
  }
  
  public void testReturnValueCaching() {
    int valueReturned = _helper.methodToBeCached();
    Assert.assertEquals(1, _helper._numberOfTimesMethodHasBeenExecuted);
    Assert.assertEquals(valueReturned, _helper.methodToBeCached());
    Assert.assertEquals(1, _helper._numberOfTimesMethodHasBeenExecuted);
    _helper.mutatorOperation();
    valueReturned = _helper.methodToBeCached();
    Assert.assertEquals(2, _helper._numberOfTimesMethodHasBeenExecuted);
    Assert.assertEquals(valueReturned, _helper.methodToBeCached());
    Assert.assertEquals(2, _helper._numberOfTimesMethodHasBeenExecuted);

  }
  
  public void testEqualsCaching() {
    CacheTestHelper other1 = null;
    CacheTestHelper other2 = new CacheTestHelper();
    CacheTestHelper other3 = new CacheTestHelper();
    other2.mutatorOperation(); // make this unequal to the _helper

    Assert.assertFalse(_helper.equals(other1));
    Assert.assertFalse(_helper.equals(other2));
    Assert.assertTrue( _helper.equals(other3));
    Assert.assertEquals(3, _helper._numberOfTimesEqualsHasBeenExecuted);
    // now the return values should come from the cache
    Assert.assertFalse(_helper.equals(other1));
    Assert.assertFalse(_helper.equals(other2));
    Assert.assertTrue( _helper.equals(other3));
    Assert.assertEquals(3, _helper._numberOfTimesEqualsHasBeenExecuted);

    _helper.mutatorOperation();
    Assert.assertFalse(_helper.equals(other1));
    Assert.assertTrue( _helper.equals(other2));
    Assert.assertFalse(_helper.equals(other3));
    Assert.assertEquals(6, _helper._numberOfTimesEqualsHasBeenExecuted);
    // check that the caching still works
    Assert.assertFalse(_helper.equals(other1));
    Assert.assertTrue( _helper.equals(other2));
    Assert.assertFalse(_helper.equals(other3));
    Assert.assertFalse(_helper.equals(other1));
    Assert.assertTrue( _helper.equals(other2));
    Assert.assertFalse(_helper.equals(other3));
    Assert.assertEquals(6, _helper._numberOfTimesEqualsHasBeenExecuted);
    
    // check that the cache is really per object
    Assert.assertFalse(other2.equals(other1));
    Assert.assertTrue( other2.equals(other2));
    Assert.assertFalse(other2.equals(other3));
    Assert.assertTrue( other2.equals(_helper));
    Assert.assertEquals(4, other2._numberOfTimesEqualsHasBeenExecuted);
    Assert.assertFalse(other2.equals(other1));
    Assert.assertTrue( other2.equals(other2));
    Assert.assertFalse(other2.equals(other3));
    Assert.assertTrue( other2.equals(_helper));
    Assert.assertEquals(4, other2._numberOfTimesEqualsHasBeenExecuted);
    Assert.assertEquals(6, _helper._numberOfTimesEqualsHasBeenExecuted);
    
  }
  
}

class CacheTestHelper {
  
  int _numberOfTimesMethodHasBeenExecuted = 0;
  int _numberOfTimesEqualsHasBeenExecuted = 0;
  int _currentReturnValue               = 0;
  
  int methodToBeCached() {
    _numberOfTimesMethodHasBeenExecuted++;
    return _currentReturnValue;
  }
  
  void mutatorOperation() {
    _currentReturnValue++;
  }
  
  public boolean equals(Object other) {
    _numberOfTimesEqualsHasBeenExecuted++;
    if(other == null || !(other instanceof CacheTestHelper)) return
false;
    return this == other || 
            ((CacheTestHelper)other)._currentReturnValue ==
_currentReturnValue;
  }

  private static aspect Cache extends CachingAspect { //
perthis(this(CacheTestHelper))
    protected pointcut mutatorOperations() : execution(void
CacheTestHelper.mutatorOperation());
    protected pointcut cachedOperations() :  execution(int
CacheTestHelper.methodToBeCached());
    protected pointcut equalsOperation(Object other) :
      execution(boolean CacheTestHelper.equals(Object)) && args(other);
  }
  
}

  This unit test passes fine if I use the association
perthis(this(CacheTestHelper)) that I have commented out in the test
helper class. However, I also tried associating this aspect with
perthis(cachedOperations()) and then the test fails. This is consistent
as that is the association the aspect inherits from its abstract parent
aspect.

  So, the fault is propably not in the compiler but in my limited
understanding: why doesn't the per object instantiation
perthis(cachedOperations()) associate one aspect instance per one object
whose return values are to be cached? What am I missing here?
  Is this an issue with aspect inheritance or perthis in general?


        -Antti-





Back to the top