Skip to main content

A substitute for a "very_private" keyword

Posted by monika_krug on January 17, 2005 at 8:21 AM PST

In my last blog entry I wrote about a substitute for a "protected-private" keyword that would allow inheritance, but not package access to the variable. I introduced around advice, binding variables to the target and arguments of a method call, and the keywords set(VariablePattern) and within(ClassPattern).



Another feature that was requested in the Mustang forum and is unlikely to see the light of day in J2SE 6.0 is a mechanism for enforcing that even the class itself has to use setVar(...) instead of var = ..., for example when the setting of the variable triggers an update to a database. This can be done with an aspect similar to the one in my last blog entry. Instead of within(ClassPattern) we need withincode(MethodPattern).



Let the class be:

package com.foo;
public class SomeClass
{
  /* don't write var directly, must call setter! */
  private Type var;
  public Type getVar()
  {
    return var;
  }
  public void setVar(Type v)
  {
    var = v;
    // side effects
    System.out.println("side effect while setting var");
  }
  public static void main(String[] args)
  {
    SomeClass s = new SomeClass();
    s.var = new Type();
  }
}

The aspect is:
package com.foo;
public aspect VeryPrivate
{
  pointcut directSet(SomeClass t, Type a) :
    set(Type SomeClass.var) && withincode(* SomeClass.*(..)) && !withincode(* SomeClass.setVar(Type))
    && target(t) && args(a);
 
  void around(SomeClass t, Type a) : directSet(t, a)
  {
    t.setVar(a);
  } 
}

This aspect substitutes all direct assignments to var with the corresponding calls to setVar.



What would happen if we forgot !withincode(* SomeClass.setVar(Type))? The assignment var = v; inside the setVar() method would be substituted with a call to setVar(), too! We would get an infinite recursion and a StackOverflowError very quickly. This is what the stack trace would look like:
Exception in thread "main" java.lang.StackOverflowError
at com.foo.SomeClass.var_aroundBody1$advice(SomeClass.java:117)
at com.foo.SomeClass.setVar(SomeClass.java:19)
at com.foo.SomeClass.var_aroundBody1$advice(SomeClass.java:117)
at com.foo.SomeClass.setVar(SomeClass.java:19)

and so on.



Declaring as Error or Warning



Maybe you would much prefer if the compiler told you that you accidentally made a direct assignment to var instead of silently replacing it with the call to setVar()? This can be done with AspectJ, too.
package com.foo;
public aspect VeryPrivateError
{
  pointcut directSet() :
    set(Type SomeClass.var) && withincode(* SomeClass.*(..)) && !withincode(* SomeClass.setVar(Type));
 
  declare error : directSet() : "var must not be set directly" ;
}

Now the compiler will show an error for the lines where you make direct assignments to var with the specified error message. Prefer a warning instead of an error? Use declare warning.



In contrast to the above joinpoint, we do not use target and args here. On the one hand, we do not need it. On the other hand, we cannot use it. The error or warning is shown at compile time, so the target and arguments cannot be known.



Development and Production Aspects



The second approach has an advantage over the first one, especially if you and your company are new to AspectJ: If you feel uneasy with using aspects for production code (or your manager does), simply use the second approach and remove the error/warning aspect before making the final build.



With development aspects it is easy and low-risk to start using AspectJ in a real customer project.

Related Topics >>