123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- //@author Ricardo Giacomin, Wes Isberg
- //XXX author n/a at old address for explicit authorization
-
-
- //START-SAMPLE caching-dirty-reflectiveSetters Use getter/setter pattern to track dirtiness
- package caching;
-
- import java.lang.reflect.Method;
-
- /**
- * Watch setters to skip if new value is same as old
- * or to set a dirty flag otherwise.
- * Clients classes opt-in by implementing IWatched,
- * and anyone can read the dirty and dirty-valid flags.
- * <pre>
- * class Foo implements WatchSetters.IWatched {
- * ...
- * }
- * Foo foo = new Foo();
- * ...
- * if (!foo.isDirtyValid() || foo.isDirty()) {
- * foo.write();
- * }
- * </pre>
- *
- * (Initial draft was sent to aspectj-users@eclipse.org by
- * Ricardo on 5/13/2003
- * (https://dev.eclipse.org/mhonarc/lists/aspectj-users/msg00482.html)
- * but his email fails now, so we
- * did not get explicit authorization to post.)
- *
- * @author Ricardo Giacomin, Wes Isberg
- */
- public aspect WatchSetters {
- // just to invoke test code below
- public static void main(String[] args) {
- Client.handleTimer(new Timer());
- }
-
- private static final Class[] GETTER_ARG_TYPES = new Class[]{};
- private static final Object[] GETTER_ARGS = new Object[]{};
- private static final Object NONE = new Object();
-
- /** maintain dirty flag for any IWatched */
- public interface IWatched {}
-
- /** true if new value sent to any setter */
- private boolean IWatched.dirty;
-
- /** false if unable to maintain dirty b/c no privileges, no getter... */
- private boolean IWatched.dirtyValid = true;
-
- /** clients can use dirty flag */
- public boolean IWatched.isDirty() {
- return dirty;
- }
-
- /** clients handle case when dirty flag is invalid */
- public boolean IWatched.isDirtyValid() {
- return dirtyValid;
- }
-
- /** Setters are instance methods returning void,
- * prefixed "set..." and taking exactly 1 argument.
- * Does not use args(id), since that requires the
- * argument be non-null.
- */
- public pointcut setters(IWatched watched) : target(watched)
- && execution(void IWatched+.set*(*)); // advice uses args[0]
-
- /**
- * Skip setter if arg is same as current value;
- * otherwise, set dirty flag after proceeding with setter.
- * Skip this advice if we tried it but failed because
- * there wasn't a corresponding setter, we didn't
- * have the right security permissions, etc.
- */
- void around(IWatched watched) : setters(watched)
- && if(watched.dirtyValid) {
- // get value by invoking getter method
- Object value = NONE;
- try {
- String getterName = "g" +
- thisJoinPoint.getSignature().getName().substring(1);
- Method method = watched.getClass()
- .getMethod(getterName, GETTER_ARG_TYPES);
- value = method.invoke(watched, GETTER_ARGS);
- } catch (Throwable t) {
- // NoSuchMethodException, SecurityException,
- // InvocationTargetException...
- }
- if (NONE == value) {
- watched.dirtyValid = false;
- proceed(watched);
- return;
- }
-
- // compare value with arg being set - pointcut has exactly 1 parm
- Object arg = thisJoinPoint.getArgs()[0];
- if (!(null == arg ? value == null : arg.equals(value))) {
- proceed(watched);
- watched.dirty = true;
- }
- }
- }
-
- // ----------- sample clients of WatchSetter
- // classes may opt in - can also use aspects to declare.
- class Timer implements WatchSetters.IWatched {
- private static int ID;
- public final int id = ++ID;
- private int counter;
- public int getCounter() {
- return counter;
- }
- public void setCounter(int i) {
- counter = i;
- }
- public void write() {
- System.out.println("writing " + this);
- }
- public String toString() {
- return "Timer " + id + "==" + counter;
- }
- }
-
- // clients can use dirty flag directly
- class Client {
- static void handleTimer(Timer timer) {
- timer.setCounter(0); // should result in no write
- if (!timer.isDirtyValid() || timer.isDirty()) {
- timer.write();
- }
- timer.setCounter(2);
- if (!timer.isDirtyValid() || timer.isDirty()) {
- timer.write();
- }
- }
- }
-
- // ---- aspects use dirty to implement cache, etc.
- // Volatile things are flushed when dirty
- abstract aspect Volatile {
- // subaspects declare targets using Volatile.ITag
- protected interface ITag {}
- declare precedence : Volatile+, WatchSetters;
- after(WatchSetters.IWatched watched) returning :
- WatchSetters.setters(watched) {
- if (!watched.isDirtyValid() || watched.isDirty()) {
- flushCache(watched);
- }
- }
- abstract void flushCache(Object o);
- }
-
- // treat Timer as volatile, write when flushing
- aspect VolatileTimer extends Volatile {
- declare parents: Timer implements ITag;
- void flushCache(Object o) {
- Timer timer = (Timer) o;
- timer.write();
- }
- }
-
- //END-SAMPLE caching-dirty-reflectiveSetters
-
- aspect Testing {
-
- void signal(String s) {
- org.aspectj.testing.Tester.event(s);
- }
-
- static {
- org.aspectj.testing.Tester.expectEvent("client-write");
- org.aspectj.testing.Tester.expectEvent("volatile-write");
- }
-
- before() : withincode(void VolatileTimer.flushCache(Object))
- && call(void Timer.write()) {
- signal("volatile-write");
- }
-
- before() : withincode(void Client.handleTimer(Timer))
- && call(void Timer.write()) {
- signal("client-write");
- }
-
- after() returning : execution(void WatchSetters.main(String[])) {
- org.aspectj.testing.Tester.checkAllEvents();
- }
- }
|