aboutsummaryrefslogtreecommitdiffstats
path: root/docs/sandbox/common/caching/WatchSetters.java
blob: fa280f58f6d8d29dff3681bbba8ddead94105324 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//@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 
 * (http://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();
    }
}