1 package org.codehaus.nanning.contract;
2
3
4
5 import java.text.MessageFormat;
6
7 import java.util.ArrayList;
8
9 import java.util.List;
10
11 import java.util.ListIterator;
12
13 import java.util.Map;
14
15 import java.util.regex.Matcher;
16
17 import java.util.regex.Pattern;
18
19
20
21 import org.codehaus.nanning.Invocation;
22
23 import org.codehaus.nanning.MethodInterceptor;
24
25 import org.codehaus.nanning.attribute.Attributes;
26
27 import ognl.MethodFailedException;
28
29 import ognl.Ognl;
30
31 import org.apache.commons.logging.Log;
32
33 import org.apache.commons.logging.LogFactory;
34
35
36
37 /***
38
39 * TODO document ContractInterceptor.
40
41 * Tip: use <code>Class.desiredAssertionStatus()</code> to check wheather to addLink this interceptor or not, that way
42
43 * you can enable and disable contract-checking in the same way you enable and disable assertions (java -ea and so on).
44
45 *
46
47 * @author <a href="mailto:jon_tirsen@yahoo.org">Jon Tirsén</a>
48
49 * @version $Revision: 1.1 $
50
51 */
52
53 public class ContractInterceptor implements MethodInterceptor {
54
55 private static final Log logger = LogFactory.getLog(ContractInterceptor.class);
56
57 private static final Pattern oldPattern =
58
59 Pattern.compile("(.*)//{old (.*?)}(.*)");
60
61
62
63 /***
64
65 * If this is non-null don't execute contracts, used when executing the expressions.
66
67 */
68
69 private ThreadLocal checkContracts = new ThreadLocal();
70
71
72
73 public Object invoke(Invocation invocation) throws Throwable {
74
75 String ensures = Attributes.getAttribute(invocation.getMethod(), "ensures");
76
77 String requires = Attributes.getAttribute(invocation.getMethod(), "requires");
78
79 String invariant = Attributes.getAttribute(invocation.getMethod().getDeclaringClass(), "invariant");
80
81
82
83 StringBuffer parsedEnsure = null;
84
85 List oldValues = null;
86
87
88
89 if (checkContracts.get() == null) {
90
91 assertExpressionTrue(invocation, requires,
92
93 "precondition violated: {0}");
94
95
96
97 // execute and remove the old-references
98
99 if (ensures != null) {
100
101 oldValues = new ArrayList();
102
103 parsedEnsure = new StringBuffer();
104
105 Matcher matcher = oldPattern.matcher(ensures);
106
107 while (matcher.matches()) {
108
109 String head = matcher.group(1);
110
111 String old = matcher.group(2);
112
113 String tail = matcher.group(3);
114
115 oldValues.add(executeExpression(invocation, old));
116
117 String oldRef = "#" + getOldReference(oldValues.size() - 1);
118
119 parsedEnsure.append(head + oldRef + tail);
120
121 matcher = oldPattern.matcher(tail);
122
123 }
124
125 // if there wasn't any old-references just addLink all of the expression
126
127 if (oldValues.size() == 0) {
128
129 parsedEnsure.append(ensures);
130
131 }
132
133 }
134
135 }
136
137
138
139 Object result = invocation.invokeNext();
140
141
142
143 if (checkContracts.get() == null) {
144
145
146
147 // check ensures with old-references
148
149 if (parsedEnsure != null) {
150
151 Map context = createContext(invocation);
152
153 for (ListIterator iterator = oldValues.listIterator(); iterator.hasNext();) {
154
155 Object oldValue = iterator.next();
156
157 context.put(getOldReference(iterator.previousIndex()), oldValue);
158
159 }
160
161
162
163 assertExpressionTrue(parsedEnsure.toString(),
164
165 invocation.getProxy(), context, "postcondition violated: " + ensures);
166
167 }
168
169
170
171 assertExpressionTrue(invocation, invariant, "invariant violated: {0}");
172
173 }
174
175
176
177 return result;
178
179 }
180
181
182
183 private static String getOldReference(int i) {
184
185 return "old" + i;
186
187 }
188
189
190
191 private Object executeExpression(Invocation invocation, String expression) {
192
193 Map context = createContext(invocation);
194
195 try {
196
197 return executeExpression(expression, invocation.getProxy(), context);
198
199 } catch (Exception e) {
200
201 throw new RuntimeException("Could not execute: " + expression, e);
202
203 }
204
205 }
206
207
208
209 private void assertExpressionTrue(Invocation invocation, String expression, String message) {
210
211 if (expression != null) {
212
213 Map context = createContext(invocation);
214
215 assertExpressionTrue(expression, invocation.getProxy(), context, MessageFormat.format(message, new Object[]{expression}));
216
217 }
218
219 }
220
221
222
223 private static Map createContext(Invocation invocation) {
224
225 Map variables = Ognl.createDefaultContext(invocation.getProxy());
226
227 variables.put("this", invocation.getProxy());
228
229 Object[] args = invocation.getArgs();
230
231 if (args != null) {
232
233 for (int i = 0; i < args.length; i++) {
234
235 Object arg = args[i];
236
237 variables.put("arg" + i, arg);
238
239 }
240
241 }
242
243 return variables;
244
245 }
246
247
248
249 private void assertExpressionTrue(String expression, Object root, Map context, String message) {
250
251 // disable execution of contracts when contracts are executed (to avoid looping)
252
253 if (checkContracts.get() == null) {
254
255 checkContracts.set(checkContracts);
256
257 try {
258
259 boolean result;
260
261 Boolean aBoolean = (Boolean) executeExpression(expression, root, context);
262
263 result = aBoolean.booleanValue();
264
265 if (!result) {
266
267 throw new AssertionError(message);
268
269 }
270
271 } catch (MethodFailedException e) {
272
273 if (e.getReason() instanceof Error) {
274
275 throw (Error) e.getReason();
276
277 } else {
278
279 logger.error("Could not execute expression: " + expression);
280
281 throw new AssertionError("Could not execute expression: " + expression);
282
283 }
284
285 } catch (Exception e) {
286
287 logger.error("Could not execute expression: " + expression);
288
289 throw new AssertionError("Could not execute expression: " + expression);
290
291 } finally {
292
293 checkContracts.set(null);
294
295 }
296
297 }
298
299 }
300
301
302
303 private static Object executeExpression(String expression, Object root, Map context) throws Exception {
304
305 return Ognl.getValue(Ognl.parseExpression(expression), context, root);
306
307 }
308
309 }
310
This page was automatically generated by Maven