001: /*
002: * Hammurapi
003: * Automated Java code review system.
004: * Copyright (C) 2004 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.org
021: * e-Mail: support@hammurapi.biz
022: */
023: package org.hammurapi.inspectors;
024:
025: import java.text.MessageFormat;
026: import java.util.Collection;
027: import java.util.HashMap;
028: import java.util.HashSet;
029: import java.util.List;
030: import java.util.Map;
031: import java.util.Set;
032: import java.util.StringTokenizer;
033:
034: import org.hammurapi.InspectorBase;
035:
036: import com.pavelvlasov.config.ConfigurationException;
037: import com.pavelvlasov.config.Parameterizable;
038: import com.pavelvlasov.jsel.Initializer;
039: import com.pavelvlasov.jsel.JselException;
040: import com.pavelvlasov.jsel.LanguageElement;
041: import com.pavelvlasov.jsel.VariableDefinition;
042: import com.pavelvlasov.jsel.expressions.MethodCall;
043: import com.pavelvlasov.jsel.expressions.PlainAssignment;
044: import com.pavelvlasov.jsel.statements.CompoundStatement;
045: import com.pavelvlasov.jsel.statements.Statement;
046: import com.pavelvlasov.jsel.statements.TryBlock;
047:
048: /**
049: * The rule checks if allocated resource is properly diposed.
050: * It follows try/finally pattern.
051: * It is configurable via parameters.
052: * Example configuration:
053: * <pre>
054: * <parameter type="java.lang.String" name="resource-def">javax.sql.DataSource:getConnection:java.sql.Connection:close</parameter>
055: * </pre>
056: * Where:
057: * - "javax.sql.DataSource" is a full class name for the resource allocator
058: * - "getConnection" is a method name of the class that allocates resource
059: * - "java.sql.Connection" is a full class name for the resource DE-allocator
060: * - "close" is a method name of the class that DE-allocates resource
061: *
062: * The following code will be checked by the paramemeter above:
063: * <pre>
064: * javax.sql.DataSource ds;
065: * // connection is allocated here...
066: * java.sql.Connection conn = ds.getConnection();
067: * ...
068: * // connection is "returned" back to data source...
069: * conn.close();
070: * </pre>
071: * @author zorror8080
072: * @version $Revision: 1.9 $
073: */
074: public class ResourceHandlingRule extends InspectorBase implements
075: Parameterizable {
076: private static final String TRY_FIN_SHOULD_FOLLOW = "try/finally block should follow right after allocated resource \"{0}"
077: + "\" to ensure that it is properly deallocated.";
078: private static final String FIN_DEALLOC = "Allocated resource \"{0}\" should be de-allocated in 'finaly' clause.";
079: private static final String FIN_DEALLOC_FIRST = "To ensure that resource \"{0}\" is properly deallocated - it should be the first in 'finaly' clause.";
080:
081: private Map resources2Check;
082: private Set violatedElements;
083:
084: public ResourceHandlingRule() {
085: resources2Check = new HashMap();
086: violatedElements = new HashSet();
087: }
088:
089: private void reportViolation(final LanguageElement element,
090: final String msg) {
091: if (!violatedElements.contains(element.getLocation())) {
092: violatedElements.add(element.getLocation());
093: context.reportViolation(element, msg);
094: }
095: }
096:
097: private ResInfo checkAllocation(final LanguageElement codeFragment,
098: final MethodCall mCall) throws JselException {
099: final String methodName = mCall.getMethodName();
100: ResInfo res = null;
101:
102: if (resources2Check.containsKey(methodName)) {
103: final ResourceDefinition resDef = (ResourceDefinition) resources2Check
104: .get(methodName);
105:
106: if (resDef.obtainClassName.equals(mCall.getProvider()
107: .getDeclaringType().getName())) {
108: res = new ResInfo(methodName, codeFragment);
109: }
110: }
111: return res;
112: }
113:
114: private boolean checkDeAllocation(final ResInfo resInfo, int index,
115: List stms) throws JselException {
116: boolean res = true;
117: Statement statement = (Statement) stms.get(index);
118: ResourceDefinition resDef = (ResourceDefinition) resources2Check
119: .get(resInfo.resourceAllocatedKey);
120: // first check if TryFinally follows right after allocation...
121: CompoundStatement fSt = null;
122: if (statement instanceof TryBlock
123: && (fSt = ((TryBlock) statement).getFinallyClause()) != null) {
124: // = ((TryBlock)statement).getFinallyClause();
125:
126: List fSts = fSt.getStatements();
127: boolean foundDeAllocation = false;
128: boolean firstDeAllocationOperation = false;
129: MethodCall releaseMethodCall = null;
130: for (int jIndex = 0, jSize = fSts.size(); jIndex < jSize; jIndex++) {
131: if (fSts.get(jIndex) instanceof CompoundStatement) {
132: List ffSts = ((CompoundStatement) fSts.get(jIndex))
133: .getStatements();
134: for (int kIndex = 0, kSize = ffSts.size(); kIndex < kSize; kIndex++) {
135: if (ffSts.get(kIndex) instanceof MethodCall
136: && resDef.releaseMethodName
137: .equals(((MethodCall) ffSts
138: .get(kIndex))
139: .getMethodName())
140: && resDef.releaseClassName
141: .equals(((MethodCall) ffSts
142: .get(kIndex))
143: .getProvider()
144: .getDeclaringType()
145: .getName())) {
146: foundDeAllocation = true;
147: firstDeAllocationOperation = (kIndex == 0);
148: releaseMethodCall = (MethodCall) ffSts
149: .get(kIndex);
150: break;
151: }
152: }
153: }
154: if (!foundDeAllocation) {
155: reportViolation(
156: resInfo.resourceAllocatedBy,
157: MessageFormat
158: .format(
159: FIN_DEALLOC,
160: new Object[] { resInfo.resourceAllocatedKey }));
161: } else {
162: if (!firstDeAllocationOperation) {
163: reportViolation(
164: (LanguageElement) releaseMethodCall,
165: MessageFormat
166: .format(
167: FIN_DEALLOC_FIRST,
168: new Object[] {
169: resDef.releaseClassName,
170: resDef.releaseMethodName }));
171: }
172: }
173: }
174: } else {
175: if (index < stms.size() - 1) {
176: checkDeAllocation(resInfo, index + 1, stms);
177: }
178: reportViolation(
179: resInfo.resourceAllocatedBy,
180: MessageFormat
181: .format(
182: TRY_FIN_SHOULD_FOLLOW,
183: new Object[] { resInfo.resourceAllocatedKey }));
184: }
185: return res;
186: }
187:
188: public boolean setParameter(String name, Object value)
189: throws ConfigurationException {
190: if ("resource-def".equals(name)) {
191: StringTokenizer tk = new StringTokenizer((String) value,
192: ":");
193:
194: String obtainClassName = null;
195: String obtainMethodName = null;
196: String releaseClassName = null;
197: String releaseMethodName = null;
198:
199: if (tk.hasMoreTokens()) {
200: obtainClassName = tk.nextToken();
201: }
202: if (tk.hasMoreTokens()) {
203: obtainMethodName = tk.nextToken();
204: }
205: if (tk.hasMoreTokens()) {
206: releaseClassName = tk.nextToken();
207: }
208: if (tk.hasMoreTokens()) {
209: releaseMethodName = tk.nextToken();
210: }
211: if (tk.hasMoreTokens() || obtainClassName == null
212: || obtainMethodName == null
213: || releaseClassName == null
214: || releaseMethodName == null) {
215: throw new ConfigurationException("Parameter '" + name
216: + "' has invalid value: '" + value + "'");
217: }
218:
219: resources2Check.put(obtainMethodName,
220: new ResourceDefinition(obtainClassName,
221: obtainMethodName, releaseClassName,
222: releaseMethodName));
223: } else {
224: throw new ConfigurationException("Parameter '" + name
225: + "' is not supported by " + getClass().getName());
226: }
227: return true;
228: }
229:
230: public void visit(CompoundStatement stmt) throws JselException {
231: ResourceDefinition resDef;
232: ResInfo resInfo = null;
233: Statement currentStatement = null;
234:
235: List stmts = stmt.getStatements();
236:
237: for (int index = 0, size = stmts.size(); index < size; index++) {
238: currentStatement = (Statement) stmts.get(index);
239: if (resInfo != null) {
240: checkDeAllocation(resInfo, index, stmts);
241: resInfo = null;
242: }
243: if (currentStatement instanceof VariableDefinition) {
244: Initializer init = ((VariableDefinition) currentStatement)
245: .getInitializer();
246:
247: if (init instanceof MethodCall) {
248: resInfo = checkAllocation(
249: (VariableDefinition) currentStatement,
250: (MethodCall) ((VariableDefinition) currentStatement)
251: .getInitializer());
252: }
253: } else {
254: if (currentStatement instanceof PlainAssignment) {
255: PlainAssignment plainAssignment = (PlainAssignment) currentStatement;
256: Collection operands = (plainAssignment)
257: .getOperands();
258: for (int jIndex = 0, jSize = operands.size(); jIndex < jSize; jIndex++) {
259: if (plainAssignment.getOperand(jIndex) instanceof MethodCall) {
260: resInfo = checkAllocation(
261: (LanguageElement) currentStatement,
262: (MethodCall) plainAssignment
263: .getOperand(jIndex));
264: if (resInfo != null) {
265: break;
266: }
267: }
268: }
269: } else {
270: if (currentStatement instanceof MethodCall) {
271: resInfo = checkAllocation(
272: (LanguageElement) currentStatement,
273: (MethodCall) currentStatement);
274: }
275: }
276: }
277: }
278: if (resInfo != null) {
279: checkDeAllocation(resInfo, stmts.size() - 1, stmts);
280: }
281: }
282:
283: private static final class ResourceDefinition {
284: private String obtainClassName;
285: private String obtainMethodName;
286: private String releaseClassName;
287: private String releaseMethodName;
288:
289: private ResourceDefinition(String obtainClassName,
290: String obtainMethodName, String releaseClassName,
291: String releaseMethodName) {
292: this .obtainClassName = obtainClassName;
293: this .releaseClassName = releaseClassName;
294: this .obtainMethodName = obtainMethodName;
295: this .releaseMethodName = releaseMethodName;
296: }
297: }
298:
299: private static final class ResInfo {
300: private String resourceAllocatedKey;
301: private LanguageElement resourceAllocatedBy;
302:
303: private ResInfo(String resourceAllocatedKey,
304: LanguageElement resourceAllocatedBy) {
305: this.resourceAllocatedKey = resourceAllocatedKey;
306: this.resourceAllocatedBy = resourceAllocatedBy;
307: }
308: }
309: }
|