package com.instantiations.assist.eclipse.analysis.audit.rule;
 
import com.instantiations.assist.eclipse.analysis.audit.core.*;
import com.instantiations.assist.eclipse.analysis.engine.core.*;
import org.eclipse.jdt.core.dom.*;
 
/**
 * Instances of the class MissingBlockAuditRule implement an audit
 * rule used to find structured statements (such as if,
 * while, and for statements) whose bodies contain a
 * single statement rather than a block.
 * 
 * Copyright (c) 2002-2003, Google, Inc.
* All Rights Reserved * * @author Brian Wilkerson * @version $Revision: 1.0 $ */ public class MissingBlockAuditRule extends CompilationUnitAuditRule { //////////////////////////////////////////////////////////////////////////// // // Preference Constants // //////////////////////////////////////////////////////////////////////////// /** * The suffix added to the preferences identifier to compose the name of * the preference indicating whether a block is to be required as the body * of a do statement. */ protected static final String REQUIRED_FOR_DO_PREFERENCE_SUFFIX = ".requiredForDo"; /** * The suffix added to the preferences identifier to compose the name of * the preference indicating whether a block is to be required as the body * of a for statement. */ protected static final String REQUIRED_FOR_FOR_PREFERENCE_SUFFIX = ".requiredForFor"; /** * The suffix added to the preferences identifier to compose the name of * the preference indicating whether a block is to be required as the body * of both the then and else portions of an if statement. */ protected static final String REQUIRED_FOR_IF_PREFERENCE_SUFFIX = ".requiredForIf"; /** * The suffix added to the preferences identifier to compose the name of * the preference indicating whether a block is to be required as the body * of a while statement. */ protected static final String REQUIRED_FOR_WHILE_PREFERENCE_SUFFIX = ".requiredForWhile"; /** * The suffix added to the preferences identifier to compose the name of * the preference indicating whether single statements on the same line * should be ignored */ protected static final String IGNORE_SINGLE_STATEMENTS_ON_SAME_LINE_PREFERENCE_SUFFIX = ".ignoreSingleStatementsOnSameLine"; //////////////////////////////////////////////////////////////////////////// // // Constructors // //////////////////////////////////////////////////////////////////////////// /** * Initialize a newly created audit rule. */ public MissingBlockAuditRule() { super(); } //////////////////////////////////////////////////////////////////////////// // // Accessing // //////////////////////////////////////////////////////////////////////////// /** * Return true if a block is required for the body of a * do statement. * * @return true if a block is required for a do * statement */ public boolean getRequiredForDo() { String preferenceName; preferenceName = getPreferenceIdentifier() + REQUIRED_FOR_DO_PREFERENCE_SUFFIX; getPreferenceStore().setDefault(preferenceName, true); return getPreferenceStore().getBoolean(preferenceName); } /** * Set whether a block is required for the body of a do statement * to the given value. * * @param required true if a block is required for a do * statement */ public void setRequiredForDo(boolean required) { String preferenceName; preferenceName = getPreferenceIdentifier() + REQUIRED_FOR_DO_PREFERENCE_SUFFIX; getPreferenceStore().setValue(preferenceName, required); } /** * Return true if a block is required for the body of a * for statement. * * @return true if a block is required for a for * statement */ public boolean getRequiredForFor() { String preferenceName; preferenceName = getPreferenceIdentifier() + REQUIRED_FOR_FOR_PREFERENCE_SUFFIX; getPreferenceStore().setDefault(preferenceName, true); return getPreferenceStore().getBoolean(preferenceName); } /** * Set whether a block is required for the body of a for statement * to the given value. * * @param required true if a block is required for a for * statement */ public void setRequiredForFor(boolean required) { String preferenceName; preferenceName = getPreferenceIdentifier() + REQUIRED_FOR_FOR_PREFERENCE_SUFFIX; getPreferenceStore().setValue(preferenceName, required); } /** * Return true if a block is required for the then and else * portions of an if statement. * * @return true if a block is required for an if * statement */ public boolean getRequiredForIf() { String preferenceName; preferenceName = getPreferenceIdentifier() + REQUIRED_FOR_IF_PREFERENCE_SUFFIX; getPreferenceStore().setDefault(preferenceName, true); return getPreferenceStore().getBoolean(preferenceName); } /** * Set whether a block is required for the then and else portions of an * if statement to the given value. * * @param required true if a block is required for an if * statement */ public void setRequiredForIf(boolean required) { String preferenceName; preferenceName = getPreferenceIdentifier() + REQUIRED_FOR_IF_PREFERENCE_SUFFIX; getPreferenceStore().setValue(preferenceName, required); } /** * Return true if a block is required for the body of a * while statement. * * @return true if a block is required for a while * statement */ public boolean getRequiredForWhile() { String preferenceName; preferenceName = getPreferenceIdentifier() + REQUIRED_FOR_WHILE_PREFERENCE_SUFFIX; getPreferenceStore().setDefault(preferenceName, true); return getPreferenceStore().getBoolean(preferenceName); } /** * Set whether a block is required for the body of a while statement * to the given value. * * @param required true if a block is required for a while * statement */ public void setRequiredForWhile(boolean required) { String preferenceName; preferenceName = getPreferenceIdentifier() + REQUIRED_FOR_WHILE_PREFERENCE_SUFFIX; getPreferenceStore().setValue(preferenceName, required); } /** * Return true if single statements on the same line should be ignored * * @return true if single statements on the same line should be ignored */ public boolean getIgnoreSingleStatementsOnSameLine() { String preferenceName; preferenceName = getPreferenceIdentifier() + IGNORE_SINGLE_STATEMENTS_ON_SAME_LINE_PREFERENCE_SUFFIX; getPreferenceStore().setDefault(preferenceName, true); return getPreferenceStore().getBoolean(preferenceName); } /** * Set whether single statements on the same line should be ignored * * @param ignore true if single statements on the same line should be ignored */ public void setIgnoreSingleStatementsOnSameLine(boolean ignore) { String preferenceName; preferenceName = getPreferenceIdentifier() + IGNORE_SINGLE_STATEMENTS_ON_SAME_LINE_PREFERENCE_SUFFIX; getPreferenceStore().setValue(preferenceName, ignore); } //////////////////////////////////////////////////////////////////////////// // // Analyzer Creation // //////////////////////////////////////////////////////////////////////////// /** * Create an analyzer that can perform the analysis implied by this analysis * item. * * @param context the context in which the analysis will be performed * * @return the analyzer that was created */ public Analyzer createAnalyzer(AnalysisContext context) { return new MissingBlockCodeAuditor(context, this); } //////////////////////////////////////////////////////////////////////////// // // Inner Classes // //////////////////////////////////////////////////////////////////////////// /** * Instances of the class MissingBlockCodeAuditor implement a * code auditor used to find missing blocks in structured statements. * * Copyright (c) 2002, Google, Inc.
* All Rights Reserved * * @author Brian Wilkerson */ public class MissingBlockCodeAuditor extends AbstractCodeAuditor { //////////////////////////////////////////////////////////////////////// // // Constructors // //////////////////////////////////////////////////////////////////////// /** * Initialize a newly created analyzer to perform an analysis in the * given context for the given analysis item. * * @param context the context in which the analysis is to be performed * @param item the analysis item for which the analysis is being * performed */ public MissingBlockCodeAuditor(AnalysisContext context, AnalysisItem item) { super(context, item); } //////////////////////////////////////////////////////////////////////// // // Visiting // //////////////////////////////////////////////////////////////////////// public boolean visit(DoStatement node) { Statement body; int startIndex, endIndex; AuditViolationImpl violation; if (getRequiredForDo()) { body = node.getBody(); if (body != null && !(body instanceof Block)) { if (getIgnoreSingleStatementsOnSameLine() && !hasLineTerminatorBefore(body)) { return true; } startIndex = body.getStartPosition(); endIndex = startIndex + body.getLength(); violation = new AuditViolationImpl( "com.instantiations.assist.eclipse.audit.missingBlock.statement", MissingBlockAuditRule.this, getContext().getCurrentTarget(), startIndex, endIndex, new String[] { "do", getSource(body), }); violation.setResolutionParameter( RECOMMENDATION_PARAM1, "do"); addViolation(violation); } } return true; } public boolean visit(ForStatement node) { Statement body; int startIndex, endIndex; AuditViolationImpl violation; if (getRequiredForFor()) { body = node.getBody(); if (body != null && !(body instanceof Block)) { if (getIgnoreSingleStatementsOnSameLine() && !hasLineTerminatorBefore(body)) { return true; } startIndex = body.getStartPosition(); endIndex = startIndex + body.getLength(); violation = new AuditViolationImpl( "com.instantiations.assist.eclipse.audit.missingBlock.statement", MissingBlockAuditRule.this, getContext().getCurrentTarget(), startIndex, endIndex, new String[] { "for", getSource(body), }); violation.setResolutionParameter( RECOMMENDATION_PARAM1, "for"); addViolation(violation); } } return true; } public boolean visit(IfStatement node) { Statement statement; int conditionEnd, startIndex, endIndex; String source; AuditViolationImpl violation; if (getRequiredForIf()) { statement = node.getThenStatement(); if (statement != null && !(statement instanceof Block)) { if (getIgnoreSingleStatementsOnSameLine() && !hasLineTerminatorBefore(statement)) { return true; } if (statement instanceof EmptyStatement) { conditionEnd = node.getExpression().getStartPosition() + node.getExpression().getLength() + 1; source = getAuditContext().getSource(); if (source == null) { startIndex = conditionEnd; endIndex = conditionEnd; } else { startIndex = source.indexOf(';', conditionEnd); if (startIndex < 0) { startIndex = conditionEnd; endIndex = conditionEnd; } else { endIndex = startIndex + 1; } } violation = new AuditViolationImpl( "com.instantiations.assist.eclipse.audit.missingBlock.clause", MissingBlockAuditRule.this, getContext().getCurrentTarget(), startIndex, endIndex, new String[] { "if", "(empty statement)", }); violation.setResolutionParameter( RECOMMENDATION_PARAM1, "if"); addViolation(violation); } else { startIndex = statement.getStartPosition(); endIndex = startIndex + statement.getLength(); violation = new AuditViolationImpl( "com.instantiations.assist.eclipse.audit.missingBlock.clause", MissingBlockAuditRule.this, getContext().getCurrentTarget(), startIndex, endIndex, new String[] { "if", getSource(statement), }); violation.setResolutionParameter( RECOMMENDATION_PARAM1, "if"); addViolation(violation); } } statement = node.getElseStatement(); if (statement != null && !((statement instanceof Block) || (statement instanceof IfStatement))) { if (getIgnoreSingleStatementsOnSameLine() && !hasLineTerminatorBefore(statement)) { return true; } if (statement instanceof EmptyStatement) { conditionEnd = node.getExpression().getStartPosition() + node.getExpression().getLength() + 1; source = getAuditContext().getSource(); if (source == null) { startIndex = conditionEnd; endIndex = conditionEnd; } else { startIndex = source.indexOf(';', conditionEnd); if (startIndex < 0) { startIndex = conditionEnd; endIndex = conditionEnd; } else { endIndex = startIndex + 1; } } violation = new AuditViolationImpl( "com.instantiations.assist.eclipse.audit.missingBlock.clause", MissingBlockAuditRule.this, getContext().getCurrentTarget(), startIndex, endIndex, new String[] { "else", "(empty statement)", }); violation.setResolutionParameter( RECOMMENDATION_PARAM1, "else"); addViolation(violation); } else { startIndex = statement.getStartPosition(); endIndex = startIndex + statement.getLength(); violation = new AuditViolationImpl( "com.instantiations.assist.eclipse.audit.missingBlock.clause", MissingBlockAuditRule.this, getContext().getCurrentTarget(), startIndex, endIndex, new String[] { "else", getSource(statement), }); violation.setResolutionParameter( RECOMMENDATION_PARAM1, "while"); addViolation(violation); } } } return true; } public boolean visit(WhileStatement node) { Statement body; int startIndex, endIndex; AuditViolationImpl violation; if (getRequiredForWhile()) { body = node.getBody(); if (body != null && !(body instanceof Block)) { if (getIgnoreSingleStatementsOnSameLine() && !hasLineTerminatorBefore(body)) { return true; } startIndex = body.getStartPosition(); endIndex = startIndex + body.getLength(); violation = new AuditViolationImpl( "com.instantiations.assist.eclipse.audit.missingBlock.statement", MissingBlockAuditRule.this, getContext().getCurrentTarget(), startIndex, endIndex, new String[] { "while", getSource(body), }); violation.setResolutionParameter( RECOMMENDATION_PARAM1, "while"); addViolation(violation); } } return true; } //////////////////////////////////////////////////////////////////////// // // Utilities // //////////////////////////////////////////////////////////////////////// /** * Answer whether there is a line terminator before the node, but after * any preceeding non-whitespace. * * @param firstNode the AST node just before the source to be checked * @param secondNode the AST node just after the source to be checked * * @return whether there is a line terminator between the two nodes. */ protected boolean hasLineTerminatorBefore(ASTNode node) { String source; int index; source = getAuditContext().getSource(); if (source == null) { return false; } index = node.getStartPosition() - 1; while (index >= 0) { if (isLineTerminator(source.charAt(index))) { return true; } else if (!Character.isWhitespace(source.charAt(index))) { return false; } index--; } return false; } } }