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