0
# Visitor Pattern
1
2
The JSP AST uses the visitor pattern to enable traversal and analysis of JSP documents. The visitor pattern provides a clean way to implement operations on AST nodes without modifying the node classes themselves.
3
4
## Visitor Interface
5
6
### JspVisitor
7
8
Core visitor interface for JSP AST traversal. This interface is generated by JavaCC during the build process and provides type-safe visit methods for all AST node types.
9
10
```java { .api }
11
public interface JspVisitor<P, R> {
12
// Generated interface with visit methods for all AST node types
13
// This interface is generated by JavaCC during the build process
14
// and contains visit methods for each concrete AST node class
15
16
R visit(ASTCompilationUnit node, P data);
17
R visit(ASTElement node, P data);
18
R visit(ASTAttribute node, P data);
19
R visit(ASTAttributeValue node, P data);
20
R visit(ASTCData node, P data);
21
R visit(ASTCommentTag node, P data);
22
R visit(ASTContent node, P data);
23
R visit(ASTDeclaration node, P data);
24
R visit(ASTDoctypeDeclaration node, P data);
25
R visit(ASTDoctypeExternalId node, P data);
26
R visit(ASTElExpression node, P data);
27
R visit(ASTHtmlScript node, P data);
28
R visit(ASTJspComment node, P data);
29
R visit(ASTJspDeclaration node, P data);
30
R visit(ASTJspDirective node, P data);
31
R visit(ASTJspDirectiveAttribute node, P data);
32
R visit(ASTJspExpression node, P data);
33
R visit(ASTJspExpressionInAttribute node, P data);
34
R visit(ASTJspScriptlet node, P data);
35
R visit(ASTText node, P data);
36
R visit(ASTUnparsedText node, P data);
37
R visit(ASTValueBinding node, P data);
38
}
39
```
40
41
**Type Parameters:**
42
- `P`: Parameter type passed to visit methods (often RuleContext for rules)
43
- `R`: Return type from visit methods (often the same as P, or void)
44
45
## Base Visitor Implementation
46
47
### JspVisitorBase
48
49
Base implementation providing default visitor behavior.
50
51
```java { .api }
52
public class JspVisitorBase<P, R> extends AstVisitorBase<P, R> implements JspVisitor<P, R> {
53
}
54
```
55
56
**Usage:**
57
58
```java
59
import net.sourceforge.pmd.lang.jsp.ast.JspVisitorBase;
60
import net.sourceforge.pmd.lang.jsp.ast.*;
61
62
public class MyJspVisitor extends JspVisitorBase<Void, Void> {
63
64
@Override
65
public Void visit(ASTElement node, Void data) {
66
System.out.println("Found element: " + node.getName());
67
return super.visit(node, data); // Continue traversal
68
}
69
70
@Override
71
public Void visit(ASTJspExpression node, Void data) {
72
System.out.println("Found JSP expression: " + node.getContent());
73
return super.visit(node, data);
74
}
75
}
76
```
77
78
## Visitor Dispatch
79
80
### AbstractJspNode Visitor Support
81
82
All JSP AST nodes support visitor dispatch through the visitor pattern.
83
84
```java { .api }
85
public abstract class AbstractJspNode extends AbstractJjtreeNode<AbstractJspNode, JspNode> implements JspNode {
86
public final <P, R> R acceptVisitor(AstVisitor<? super P, ? extends R> visitor, P data);
87
protected abstract <P, R> R acceptVisitor(JspVisitor<? super P, ? extends R> visitor, P data);
88
}
89
```
90
91
**Usage:**
92
93
```java
94
import net.sourceforge.pmd.lang.jsp.ast.ASTCompilationUnit;
95
96
// Apply visitor to AST
97
MyJspVisitor visitor = new MyJspVisitor();
98
compilationUnit.acceptVisitor(visitor, null);
99
```
100
101
**Methods:**
102
103
- `acceptVisitor(AstVisitor, P)`: Generic visitor dispatch that checks for JSP visitor capability
104
- `acceptVisitor(JspVisitor, P)`: Direct JSP visitor dispatch implemented by each node type
105
106
## Visitor Implementation Patterns
107
108
### Simple Analysis Visitor
109
110
```java
111
import net.sourceforge.pmd.lang.jsp.ast.*;
112
113
public class JspAnalysisVisitor extends JspVisitorBase<Void, Void> {
114
115
private int elementCount = 0;
116
private int expressionCount = 0;
117
118
@Override
119
public Void visit(ASTElement node, Void data) {
120
elementCount++;
121
122
// Analyze element properties
123
if (node.isUnclosed()) {
124
System.out.println("Warning: Unclosed element " + node.getName());
125
}
126
127
if ("script".equals(node.getName())) {
128
System.out.println("Found script element");
129
}
130
131
return super.visit(node, data);
132
}
133
134
@Override
135
public Void visit(ASTJspExpression node, Void data) {
136
expressionCount++;
137
138
String content = node.getContent();
139
if (content.contains("request.")) {
140
System.out.println("Found request access: " + content);
141
}
142
143
return super.visit(node, data);
144
}
145
146
@Override
147
public Void visit(ASTElExpression node, Void data) {
148
expressionCount++;
149
150
String content = node.getContent();
151
System.out.println("EL Expression: " + content);
152
153
return super.visit(node, data);
154
}
155
156
public void printStats() {
157
System.out.println("Elements found: " + elementCount);
158
System.out.println("Expressions found: " + expressionCount);
159
}
160
}
161
```
162
163
### Data Collection Visitor
164
165
```java
166
import java.util.*;
167
168
public class JspDataCollector extends JspVisitorBase<Void, Void> {
169
170
private List<String> elementNames = new ArrayList<>();
171
private List<String> jspExpressions = new ArrayList<>();
172
private List<String> elExpressions = new ArrayList<>();
173
private Map<String, List<String>> elementAttributes = new HashMap<>();
174
175
@Override
176
public Void visit(ASTElement node, Void data) {
177
String elementName = node.getName();
178
elementNames.add(elementName);
179
180
// Collect attributes for this element
181
List<String> attrs = node.children(ASTAttribute.class)
182
.map(ASTAttribute::getName)
183
.collect(Collectors.toList());
184
185
if (!attrs.isEmpty()) {
186
elementAttributes.put(elementName, attrs);
187
}
188
189
return super.visit(node, data);
190
}
191
192
@Override
193
public Void visit(ASTJspExpression node, Void data) {
194
jspExpressions.add(node.getContent());
195
return super.visit(node, data);
196
}
197
198
@Override
199
public Void visit(ASTElExpression node, Void data) {
200
elExpressions.add(node.getContent());
201
return super.visit(node, data);
202
}
203
204
// Getters for collected data
205
public List<String> getElementNames() { return elementNames; }
206
public List<String> getJspExpressions() { return jspExpressions; }
207
public List<String> getElExpressions() { return elExpressions; }
208
public Map<String, List<String>> getElementAttributes() { return elementAttributes; }
209
}
210
```
211
212
### Conditional Visitor
213
214
```java
215
public class SecurityAnalysisVisitor extends JspVisitorBase<List<String>, List<String>> {
216
217
@Override
218
public List<String> visit(ASTElExpression node, List<String> issues) {
219
String content = node.getContent();
220
221
// Check for potentially unsafe EL expressions
222
if (!isInTaglib(node) && !isSanitized(content)) {
223
issues.add("Potentially unsafe EL expression: " + content +
224
" at line " + node.getBeginLine());
225
}
226
227
return super.visit(node, issues);
228
}
229
230
@Override
231
public List<String> visit(ASTElement node, List<String> issues) {
232
// Check for inline event handlers
233
if (hasInlineEventHandler(node)) {
234
issues.add("Inline event handler found in " + node.getName() +
235
" at line " + node.getBeginLine());
236
}
237
238
return super.visit(node, issues);
239
}
240
241
private boolean isInTaglib(ASTElExpression node) {
242
// Check if EL expression is within a taglib element
243
return node.ancestors(ASTElement.class)
244
.anyMatch(elem -> elem.getNamespacePrefix() != null);
245
}
246
247
private boolean isSanitized(String content) {
248
return content.matches("^fn:escapeXml\\(.+\\)$");
249
}
250
251
private boolean hasInlineEventHandler(ASTElement element) {
252
return element.children(ASTAttribute.class)
253
.anyMatch(attr -> attr.getName().startsWith("on")); // onclick, onload, etc.
254
}
255
}
256
```
257
258
## Advanced Visitor Techniques
259
260
### Visitor with Context
261
262
```java
263
public class ContextAwareVisitor extends JspVisitorBase<VisitorContext, Void> {
264
265
public static class VisitorContext {
266
private Stack<String> elementStack = new Stack<>();
267
private Set<String> seenIds = new HashSet<>();
268
269
public void pushElement(String name) { elementStack.push(name); }
270
public String popElement() { return elementStack.pop(); }
271
public String getCurrentElement() {
272
return elementStack.isEmpty() ? null : elementStack.peek();
273
}
274
public boolean addId(String id) { return seenIds.add(id); }
275
}
276
277
@Override
278
public Void visit(ASTElement node, VisitorContext context) {
279
String elementName = node.getName();
280
context.pushElement(elementName);
281
282
// Check for duplicate IDs
283
node.children(ASTAttribute.class)
284
.filter(attr -> "id".equals(attr.getName()))
285
.forEach(attr -> {
286
String id = getAttributeValue(attr);
287
if (!context.addId(id)) {
288
System.out.println("Duplicate ID found: " + id);
289
}
290
});
291
292
// Continue traversal
293
super.visit(node, context);
294
295
context.popElement();
296
return null;
297
}
298
299
private String getAttributeValue(ASTAttribute attr) {
300
return attr.children(ASTAttributeValue.class)
301
.findFirst()
302
.map(ASTAttributeValue::getValue)
303
.orElse("");
304
}
305
}
306
```
307
308
### Visitor Composition
309
310
```java
311
public class CompositeVisitor extends JspVisitorBase<Void, Void> {
312
313
private List<JspVisitor<Void, Void>> visitors = new ArrayList<>();
314
315
public void addVisitor(JspVisitor<Void, Void> visitor) {
316
visitors.add(visitor);
317
}
318
319
@Override
320
public Void visit(ASTElement node, Void data) {
321
// Apply all visitors to this node
322
for (JspVisitor<Void, Void> visitor : visitors) {
323
node.acceptVisitor(visitor, data);
324
}
325
return super.visit(node, data);
326
}
327
328
// Override other visit methods similarly...
329
}
330
```
331
332
## Integration with PMD Rules
333
334
The visitor pattern integrates seamlessly with PMD's rule framework:
335
336
```java
337
import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
338
339
public class CustomJspRule extends AbstractJspRule {
340
341
@Override
342
public Object visit(ASTElExpression node, Object data) {
343
// Rule-specific logic
344
if (violatesRule(node)) {
345
asCtx(data).addViolation(node, "Rule violation message");
346
}
347
348
return super.visit(node, data);
349
}
350
351
private boolean violatesRule(ASTElExpression node) {
352
// Custom rule logic
353
return node.getContent().contains("unsafeMethod");
354
}
355
}
356
```
357
358
## Best Practices
359
360
### Performance Considerations
361
362
```java
363
// Avoid creating objects in visit methods for frequently called nodes
364
@Override
365
public Void visit(ASTText node, Void data) {
366
// Good: Direct operations
367
if (node.getContent().trim().isEmpty()) {
368
// Handle empty text
369
}
370
371
// Avoid: Creating unnecessary objects
372
// String trimmed = node.getContent().trim(); // Creates new string
373
374
return super.visit(node, data);
375
}
376
```
377
378
### Null Safety
379
380
```java
381
@Override
382
public Void visit(ASTElement node, Void data) {
383
String name = node.getName();
384
if (name != null && name.equals("script")) {
385
// Safe null check
386
}
387
388
return super.visit(node, data);
389
}
390
```
391
392
### Selective Traversal
393
394
```java
395
@Override
396
public Void visit(ASTElement node, Void data) {
397
if ("script".equals(node.getName())) {
398
// Don't traverse into script elements
399
return null;
400
}
401
402
return super.visit(node, data); // Continue normal traversal
403
}
404
```
405
406
The visitor pattern provides a powerful and flexible way to analyze JSP AST structures while keeping analysis logic separate from the AST node implementations.