Skip to main content

A "protected-private" keyword?

Posted by monika_krug on January 12, 2005 at 7:24 AM PST

One of the features requested in the J2SE 6.0 (Mustang) forum was a modifier that would allow a member variable to be inherited as if it were protected, but not to be accessed by the other classes from the package. This would be useful when a protected member variable must always be accessed by its getter and setter, because these have side-effects, and one wants to avoid accidentally accessing it directly.



Are we going to get such a keyword, whatever it would be named? Most likely not. Adding a keyword creates way too many problems, the most important one being that it breaks existing code that uses the new keyword as an identifier. But we can achieve the same goal with an aspect.



Let the concerned class be:

package com.foo;
public class SomeClass
{
  /* don't access var directly, must call getter and setter! */
  protected Type var;
  public Type getVar()
  {
    ... side effects here ...
    return var;
  }
  public void setVar(Type v)
  {
    var = v;
    ... side effects here ...
  }
}

Firstly, we would like to prevent calls like someClass.var = ... from classes like com.foo.SomeOtherClass. Here's how to achieve this:
package com.foo;
public aspect Protection
{
  pointcut directSet(SomeClass t, Type a) :
    set(Type SomeClass.var) && within(com.foo.*) && !within(com.foo.SomeClass)
    && target(t) && args(a);
 
  void around(SomeClass t, Type a) : directSet(t, a)
  {
    t.setVar(a);
  }
}

The Pointcut



First we declare a pointcut with the name directSet and the parameters t (of type SomeClass, for the target) and a (of type Type, for the argument). It is to pick the joinpoints where SomeClass.var is modified directly from inside the package, but we still want to allow SomeClass itself to modify var - otherwise we would get an infinite loop within setVar()! The pointcut designator set([modifiers] type x) stands for the direct setting of x, i.e. any asignment in the form x = ...;. We could also use * instead of Type in this case, because the member variable name is unique within one class of course. The pointcut designator within(AClass) is probably clear.



What about target() and args()? Are these limiting the selected set of joinpoints? They could be, but they are not in this case. If the target where not already defined to be SomeClass (e.g. if we had used a wildcard in the set() joinpoint, like set(com.foo.*.var)), this would limit the choices to only those joinpoints where the target is of type SomeClass. Also the argument could be limited like this, but of course we want to catch all assignments of var to an object of type Type, not only to some specific subtype of it, and everything else would not make sense anyway. But this is not why we are using target() and args() here, target(SomeClass) and args(Type) would suffice for this. We need target() and args() to bind the formal parameters of the pointcut. And these parameters we need to expose so that we can use them in the advice later on.



The Advice



There are three types of advice: before, after and around advice. Around advice is executed instead of the pointcut. If our pointcould selected a method, the around advice would need the same return type and declared exceptions as the method, while before and after advice do not have return types. For a setter, the return type is void, and for the getter it would obviously be Type. With proceed(theTarget, arg0, arg1, ...) one could execute the original joinpoint (like method, constructor, get or set), which is not what we want in this case evidently.



Can you figure out the pointcut and advice you need to add for catching the direct get, too? You can check for the solution below.



package com.foo;
public aspect Protection
{
  pointcut directSet(SomeClass t, Type a) :
    set(Type SomeClass.var) && within(com.foo.*) && !within(com.foo.SomeClass)
    && target(t) && args(a);

  pointcut directGet(SomeClass t) :
    get(Type SomeClass.var) && within(com.foo.*) && !within(com.foo.SomeClass)
    && target(t);
 
  void around(SomeClass t, Type a) : directSet(t, a)
  {
    t.setVar(a);
  }
 
  Type around(SomeClass t) : directGet(t)
  {
    return t.getVar();
  }
}
Related Topics >>