View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules;
5   
6   import net.sourceforge.pmd.AbstractRule;
7   import net.sourceforge.pmd.PropertyDescriptor;
8   import net.sourceforge.pmd.ast.ASTBlock;
9   import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
10  import net.sourceforge.pmd.ast.ASTCompilationUnit;
11  import net.sourceforge.pmd.ast.ASTLocalVariableDeclaration;
12  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
13  import net.sourceforge.pmd.ast.ASTName;
14  import net.sourceforge.pmd.ast.ASTReferenceType;
15  import net.sourceforge.pmd.ast.ASTTryStatement;
16  import net.sourceforge.pmd.ast.ASTType;
17  import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
18  import net.sourceforge.pmd.ast.Node;
19  import net.sourceforge.pmd.properties.StringProperty;
20  
21  import java.util.ArrayList;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.StringTokenizer;
27  
28  /**
29   * Makes sure you close your database connections. It does this by
30   * looking for code patterned like this:
31   * <pre>
32   *  Connection c = X;
33   *  try {
34   *   // do stuff, and maybe catch something
35   *  } finally {
36   *   c.close();
37   *  }
38   *
39   *  @author original author unknown
40   *  @author Contribution from Pierre Mathien
41   * </pre>
42   */
43  public class CloseResource extends AbstractRule {
44  
45      private Set<String> types = new HashSet<String>();
46  
47      private Set<String> closeTargets = new HashSet<String>();
48      private static final PropertyDescriptor closeTargetsDescriptor = new StringProperty("closeTargets",
49              "Methods which may close this resource", "", 1.0f);
50  
51      private static final PropertyDescriptor typesDescriptor = new StringProperty("types",
52              "Types that are affected by this rule", "", 2.0f);
53  
54      private static final Map<String, PropertyDescriptor> propertyDescriptorsByName = asFixedMap(new PropertyDescriptor[] { typesDescriptor, closeTargetsDescriptor });
55  
56      protected Map<String, PropertyDescriptor> propertiesByName() {
57          return propertyDescriptorsByName;
58      }
59  
60      public Object visit(ASTCompilationUnit node, Object data) {
61          if (closeTargets.isEmpty() && getStringProperty(closeTargetsDescriptor) != null) {
62              for (StringTokenizer st = new StringTokenizer(getStringProperty(closeTargetsDescriptor), ","); st.hasMoreTokens();) {
63                  closeTargets.add(st.nextToken());
64              }
65          }
66          if (types.isEmpty() && getStringProperty(typesDescriptor) != null) {
67              for (StringTokenizer st = new StringTokenizer(getStringProperty(typesDescriptor), ","); st.hasMoreTokens();) {
68                  types.add(st.nextToken());
69              }
70          }
71          return super.visit(node, data);
72      }
73  
74      public Object visit(ASTMethodDeclaration node, Object data) {
75          List<ASTLocalVariableDeclaration> vars = node.findChildrenOfType(ASTLocalVariableDeclaration.class);
76          List<ASTVariableDeclaratorId> ids = new ArrayList<ASTVariableDeclaratorId>();
77  
78          // find all variable references to Connection objects
79          for (ASTLocalVariableDeclaration var: vars) {
80              ASTType type = var.getTypeNode();
81  
82              if (type.jjtGetChild(0) instanceof ASTReferenceType) {
83                  ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
84                  if (ref.jjtGetChild(0) instanceof ASTClassOrInterfaceType) {
85                      ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);
86                      if (types.contains(clazz.getImage())) {
87                          ASTVariableDeclaratorId id = (ASTVariableDeclaratorId) var.jjtGetChild(1).jjtGetChild(0);
88                          ids.add(id);
89                      }
90                  }
91              }
92          }
93  
94          // if there are connections, ensure each is closed.
95          for (ASTVariableDeclaratorId x : ids) {
96              ensureClosed((ASTLocalVariableDeclaration) x.jjtGetParent().jjtGetParent(), x, data);
97          }
98          return data;
99      }
100 
101     private void ensureClosed(ASTLocalVariableDeclaration var,
102                               ASTVariableDeclaratorId id, Object data) {
103         // What are the chances of a Connection being instantiated in a
104         // for-loop init block? Anyway, I'm lazy!
105         String target = id.getImage() + ".close";
106         Node n = var;
107 
108         while (!(n instanceof ASTBlock)) {
109             n = n.jjtGetParent();
110         }
111 
112         ASTBlock top = (ASTBlock) n;
113 
114         List<ASTTryStatement> tryblocks = new ArrayList<ASTTryStatement>();
115         top.findChildrenOfType(ASTTryStatement.class, tryblocks, true);
116 
117         boolean closed = false;
118 
119         // look for try blocks below the line the variable was
120         // introduced and make sure there is a .close call in a finally
121         // block.
122         for (ASTTryStatement t : tryblocks) {
123             if ((t.getBeginLine() > id.getBeginLine()) && (t.hasFinally())) {
124                 ASTBlock f = (ASTBlock) t.getFinally().jjtGetChild(0);
125                 List<ASTName> names = new ArrayList<ASTName>();
126                 f.findChildrenOfType(ASTName.class, names, true);
127                 for (ASTName oName : names) {
128                     String name = oName.getImage();
129                     if (name.equals(target) || closeTargets.contains(name)) {
130                         closed = true;
131                     }
132                 }
133             }
134         }
135 
136         // if all is not well, complain
137         if (!closed) {
138             ASTType type = (ASTType) var.jjtGetChild(0);
139             ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
140             ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);
141             addViolation(data, id, clazz.getImage());
142         }
143     }
144 }