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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
|
package aspects;
import java.io.*;
import java.util.*;
import org.aspectj.runtime.*;
import org.aspectj.lang.*;
import org.aspectj.lang.reflect.*;
import java.lang.reflect.*;
/** Trace is an aspect that traces execution through code.
*
*/
public abstract aspect Trace issingleton()
{
// our internal instance
private static Trace _trace;
// Call depth on trace
private static final ThreadLocal traceDepths = new ThreadLocal();
// An object to synchronize on
protected static final Object lock = new Object();
private static final String NL = System.getProperty("line.separator");
// Space indentation increment
private static final int INDENT = 4;
// Used for indentation
private static final byte[] SPACES = new byte[100];
private static boolean traceActive = true;
static
{
Arrays.fill(SPACES,(byte)' ');
}
/** Trace constructor. Since this aspect is a singleton, we can be
* assured that only a single instance exists.
*/
protected Trace() {_trace = this;}
/**
* This abstract pointcut indicates what classes we should trace. Typically
* you will define this using a within() PCD. We leave that up to concrete aspects.
*/
protected abstract pointcut lexicalScope();
/**
* Common scope for all traces - includes lexicalScope
*/
final pointcut scope() : if(_trace != null && _trace.canTraceJoinpoint(thisJoinPoint)) && lexicalScope() && !within(Trace+);
/**
* This pointcut designates tracing constructors within lexicalScope()
*/
protected final pointcut constructorTrace() : scope() && (call( new(..) ) || execution( new(..)));
/**
* This pointcut designates tracing method executions within lexicalScope()
*/
protected final pointcut methodTrace() : scope() && (call(* *(..)) || execution(* *(..)));
/**
* This pointcut designates tracing exception handlers within lexicalScope()
*/
protected final pointcut handlerTrace(Exception e) : scope() && args(e) && handler(Exception+);
/**
* This pointcut picks out joinpoints within this aspect that implement
* the actual tracing. Since parameters and return values are printed
* out via implicit or explicit call to Object.toString(), there is the possibility
* of an execution joinpoint on toString() causing the trace logic
* to be re-entered. This is undesireable because it makes the trace output
* difficult to read and adds unecessary overhead. <p>
* This pointcut is used within a cflowbelow pointcut to prevent recursive
* trace calls.
*/
private pointcut internalMethods() :
execution( void Trace.trace*(..,(JoinPoint||JoinPoint.StaticPart),..) );
/**
* For methods, we use around() advice to capture calls/executions.
*/
Object around() : methodTrace() && !cflowbelow(internalMethods())
{
traceEnter(thisJoinPoint);
try
{
Object result = proceed();
traceResult(result,thisJoinPoint);
return result;
}
finally
{
traceExit(thisJoinPoint);
}
}
/**
* For Constructors, we use around() advice to capture calls/executions.
*/
Object around() : constructorTrace() && !cflowbelow(internalMethods())
{
traceEnter(thisJoinPoint);
try
{
return proceed();
}
finally
{
traceExit(thisJoinPoint);
}
}
/**
* Trace Exceptions that may occur with constructors or methods
*/
after() throwing(Throwable e): (constructorTrace() || methodTrace()) && !cflowbelow(internalMethods())
{
traceThrowable(e,thisJoinPoint);
}
/**
* Trace Exception handlers entry
*/
before(Exception e) : handlerTrace(e) && !cflowbelow(internalMethods())
{
traceHandlerEntry(e,thisJoinPointStaticPart);
}
/**
* Trace Exception handlers exit
*/
after(Exception e) : handlerTrace(e) && !cflowbelow(internalMethods())
{
traceHandlerExit();
}
/**
* Subaspects can override this method to log the data as needed. The default
* mechanism is to log to System.out
*
* Clients should be aware that this method is not synchronized.
*/
protected void log(String data) {System.out.println(data);}
/**
* Can be overridden by subaspects to filter what constructors/methods should be
* traced at runtime. This method is always called prior to the log()
* method. The default always returns true.<p> Note that exceptions thrown
* by constructors/methods are filtered through this method.
* @param currentlyExecutingClass The Class that is currently executing.
* @param signature The signature of the member being traced
* @param traceType The type of trace entry (see AspectJ doc for the available types)
*/
protected boolean isTraceable(Class currentlyExecutingClass, CodeSignature signature,String traceType) {return true;}
/**
* Can be overridden by subaspects to filter what exception handlers should be
* traced at runtime. This method is always called prior to the log()
* method. The default always returns false.<p>
* Note that exception handlers are catch(...){} blocks and are filtered
* independently from constructor/method calls and execution.
* @param currentlyExecutingClass The Class that is currently executing.
* @param signature The signature of the member being traced
*/
protected boolean isTraceable(Class currentlyExecutingClass, CatchClauseSignature signature) {return false;}
/**
* Retrieves the signature of the joinpoint and asks if it can be traced
*/
private boolean canTraceJoinpoint(JoinPoint jp)
{
if ( !traceActive ) return false;
final Signature sig = jp.getSignature();
final Object o = jp.getThis(); // current object
Class currentType;
if ( o == null ) // must be a static
currentType = jp.getStaticPart().getSourceLocation().getWithinType();
else
currentType = o.getClass();
// dispatch the correct filter method
if ( sig instanceof CodeSignature )
return isTraceable(currentType,(CodeSignature)sig,jp.getKind());
else
return isTraceable(currentType,(CatchClauseSignature)sig);
}
/**
* This method creates a trace entry line based on information in the
* supplied join point.
*/
private void traceEnter(JoinPoint thisJoinPoint)
{
// Get the indent level (call depth for current thread * 4).
int depth = getTraceDepth(INDENT);
Class[] parameterTypes = ((CodeSignature)thisJoinPoint.getSignature()).getParameterTypes();
String[] parameterNames = ((CodeSignature)thisJoinPoint.getSignature()).getParameterNames();
boolean isCall = thisJoinPoint.getKind().endsWith("call");
StringBuffer enterPhrase = new StringBuffer(100);
enterPhrase.append(getSpaces(depth));
if ( isCall )
enterPhrase.append("Call ");
else
enterPhrase.append("Entering ");
enterPhrase.append(methodSignature(parameterNames,parameterTypes,thisJoinPoint));
// if ( isCall )
// enterPhrase.append(" From: ").append(thisJoinPoint.getSourceLocation().getWithinType());
// Prepare the methods parameter list
String parmStr = null;
Object[] parameters = thisJoinPoint.getArgs();
if (parameters.length > 0)
{
String spaces = getSpaces(depth + 6);
StringBuffer parms = new StringBuffer();
for (int i = 0; i < parameters.length; i++)
{
if (parameters[i] != null && parameters[i].getClass().isArray())
{
// arrays can be huge...limit to first 100 elements
final int len = Math.min(Array.getLength(parameters[i]),100);
if ( len == 0 )
{
parms.append(spaces);
parms.append(parameterNames[i]);
parms.append(": 0 length array");
parms.append(NL);
}
else
{
Object o = null;
for ( int x = 0; x < len; x++ )
{
parms.append(spaces);
parms.append(parameterNames[i]);
parms.append("[");
parms.append(x);
parms.append("]:");
o = Array.get(parameters[i],x);
try{parms.append(" " + (o != null?o:"null"));} // implicit toString()
catch(Throwable t) {parms.append(" " + parameters[i]);}
parms.append(NL);
}
}
}
else
{
// Not an array.
parms.append(spaces);
parms.append(parameterNames[i]);
parms.append(": ");
try
{
parms.append("" + parameters[i]);
}
catch (Throwable t ) {parms.append("" + parameters[i].getClass().getName());}
}
parmStr = parms.toString();
}
}
if (parmStr != null)
enterPhrase.append(NL).append(parmStr);
log(enterPhrase.toString());
}
/**
* This method creates an exception handler trace entry based on a Throwable
* and information contained in the join point.
*/
private void traceHandlerEntry(Throwable t, JoinPoint.StaticPart thisJoinPoint)
{
int depth = getTraceDepth(INDENT);
String phrase = getSpaces(depth) +
"Exception caught at: " +
thisJoinPoint;
log(printStackTrace(phrase,t));
}
/**
* This method simply adjusts the trace depth - no other information printed.
*/
private void traceHandlerExit()
{
getTraceDepth(-INDENT);
}
/**
* This method creates a stack trace entry based on a Throwable and
* information contained in the join point.
*/
private void traceThrowable(Throwable t, JoinPoint thisJoinPoint)
{
int depth = getTraceDepth(0);
String phrase = getSpaces(depth+4) +
"Throwing Exception at: " +
thisJoinPoint;
log(printStackTrace(phrase,t));
}
private String printStackTrace(String phrase, Throwable t)
{
try {
StringWriter sw = new StringWriter(4096);
PrintWriter pw = new PrintWriter(sw,true);
pw.println(phrase);
pw.println();
pw.println("Exception Stack Trace:");
pw.println();
t.printStackTrace(pw);
pw.println();
pw.flush();
sw.flush();
pw.close();
sw.close();
return sw.toString();
}
catch(IOException IOE) {
log(IOE.toString());
return IOE.getMessage();
}
}
/**
* This method creates a trace exit entry based on the join point
* information.
*/
private void traceExit(JoinPoint thisJoinPoint)
{
int depth = getTraceDepth(-INDENT);
// Assemble the method's signature.
Class[] parameterTypes = ((CodeSignature)thisJoinPoint.getSignature()).getParameterTypes();
String[] parameterNames = ((CodeSignature)thisJoinPoint.getSignature()).getParameterNames();
boolean isCall = thisJoinPoint.getKind().endsWith("call");
StringBuffer exitPhrase = new StringBuffer(100);
exitPhrase.append(getSpaces(depth));
if ( isCall )
exitPhrase.append("Return ");
else
exitPhrase.append("Exiting ");
exitPhrase.append(methodSignature(parameterNames,parameterTypes,thisJoinPoint)).append(NL);
log(exitPhrase.toString());
}
/**
* This method creates a trace result entry based on a result and the
* join point.
*/
private void traceResult(Object thisResult, JoinPoint thisJoinPoint)
{
Class returnType = ((MethodSignature)thisJoinPoint.getSignature()).getReturnType();
if ( returnType.toString().equals("void") )
return;
int depth = getTraceDepth(0);
if ( thisResult == null )
thisResult = "null";
if ( thisResult.getClass().isArray() )
{
// arrays can be Oprah-sized - limit to 100 elements
final int len = Math.min(Array.getLength(thisResult),100);
StringBuffer buf = new StringBuffer();
if ( len == 0 )
buf.append(">>>zero-length array<<<");
else
{
Object o;
for ( int i = 0; i < len; i++ )
{
o = Array.get(thisResult,i);
buf.append("data[").append(i).append("] ");
try{buf.append(o != null?o:"null");} // implicit toString()
catch(Throwable t) {buf.append(thisResult);}
buf.append(NL);
}
}
thisResult = buf.toString();
}
thisResult = thisResult.toString();
StringBuffer returnPhrase = new StringBuffer(100);
returnPhrase.append(getSpaces(depth+2)).append(thisJoinPoint);
returnPhrase.append(" returned >>>>>>> ").append(thisResult);
log(returnPhrase.toString());
}
/**
* This method returns the current trace line indentation for the
* thread.
*/
private int getTraceDepth(int incr)
{
int rc = 0;
Integer depth = (Integer) traceDepths.get();
if (depth == null)
{
if ( incr > 0 )
{
traceDepths.set(new Integer(incr));
return incr;
}
else return rc;
}
rc = depth.intValue();
if ( incr > 0 )
{
depth = new Integer(rc += incr);
traceDepths.set(depth);
}
else if ( incr < 0 )
{
depth = new Integer(rc + incr);
traceDepths.set(depth);
}
return rc;
}
/**
* This method returns a String containing the number of spaces desired to
* be used as padding for formatting trace log entries.
*/
private String getSpaces(int num)
{
return new String(SPACES,0,Math.min(num,SPACES.length));
}
/**
* Create a method signature
*/
private String methodSignature(String[] parameterNames,
Class[] parameterTypes,
JoinPoint thisJoinPoint)
{
// Assemble the method's signature.
StringBuffer signature = new StringBuffer("(");
for (int i = 0; i < parameterTypes.length; i++)
{
signature.append(parameterTypes[i].getName());
signature.append(" ");
signature.append(parameterNames[i]);
if (i < (parameterTypes.length-1))
signature.append(", ");
}
signature.append(")");
return thisJoinPoint.getSignature().getDeclaringType().getName() + "." +
thisJoinPoint.getSignature().getName() +
signature;
}
}
|