diff --git a/pmd/etc/changelog.txt b/pmd/etc/changelog.txt
index d64f7d05bf..b3d9971eb6 100644
--- a/pmd/etc/changelog.txt
+++ b/pmd/etc/changelog.txt
@@ -1,7 +1,7 @@
????, 2006 - 3.6:
New rules:
Design ruleset: UnsynchronizedStaticDateFormatter
- Strings ruleset: InefficientEmptyStringCheck
+ Strings ruleset: InefficientEmptyStringCheck, InsufficientStringBufferDeclaration
Fixed bug 1414985 - ConsecutiveLiteralAppends now checks for intervening references between appends.
Fixed bug 1418424 - ConsecutiveLiteralAppends no longer flags appends in separate methods.
Fixed bug 1416167 - AppendCharacterWithChar now catches cases involving escaped characters.
diff --git a/pmd/regress/test/net/sourceforge/pmd/rules/strings/InsufficientStringBufferDeclarationTest.java b/pmd/regress/test/net/sourceforge/pmd/rules/strings/InsufficientStringBufferDeclarationTest.java
new file mode 100644
index 0000000000..06e091f26f
--- /dev/null
+++ b/pmd/regress/test/net/sourceforge/pmd/rules/strings/InsufficientStringBufferDeclarationTest.java
@@ -0,0 +1,386 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+*/
+package test.net.sourceforge.pmd.rules.strings;
+
+import net.sourceforge.pmd.PMD;
+import net.sourceforge.pmd.Rule;
+import test.net.sourceforge.pmd.testframework.SimpleAggregatorTst;
+import test.net.sourceforge.pmd.testframework.TestDescriptor;
+
+public class InsufficientStringBufferDeclarationTest extends SimpleAggregatorTst {
+
+ private Rule rule;
+
+ public void setUp() throws Exception {
+ rule = findRule("strings", "InsufficientStringBufferDeclaration");
+ }
+
+ public void testAll() {
+
+ // first run the legal tests
+ runTests(new TestDescriptor[] {
+ new TestDescriptor(TEST1, "1, StringBuffer allocated with enough space", 0, rule),
+ new TestDescriptor(TEST3, "3, StringBuffer allocated with space", 0, rule),
+ new TestDescriptor(TEST4, "4, StringBuffer allocated from variable", 0, rule),
+ new TestDescriptor(TEST5, "5, creating a new StringBuffer", 0, rule),
+ new TestDescriptor(TEST6, "6, Initialize with a specific String", 1, rule),
+ new TestDescriptor(TEST7, "7, appends inside if statements", 0, rule),
+ new TestDescriptor(TEST8, "8, Field level variable", 0, rule),
+ new TestDescriptor(TEST10, "10, Appending non-literals", 0, rule),
+ new TestDescriptor(TEST11, "11, Initialized to null", 0, rule),
+ new TestDescriptor(TEST12, "12, Passed in as parameter", 0, rule),
+ new TestDescriptor(TEST14, "14, Compound append, presized just fine", 0, rule),
+ new TestDescriptor(TEST16, "16, Append int, properly presized", 0, rule),
+ new TestDescriptor(TEST18, "18, Append char, properly presized", 0, rule),
+ new TestDescriptor(TEST22, "22, appends inside if/else if/else statements", 0, rule),
+ new TestDescriptor(TEST23, "23, appends inside if/else if/else statements", 0, rule),
+ new TestDescriptor(TEST24, "24, appends inside if/else if/else statements", 1, rule),
+ new TestDescriptor(TEST25, "25, Compound ifs", 0, rule),
+ new TestDescriptor(TEST27, "27, Switch statement doesn't exceed 16 characters", 0, rule),
+ });
+
+ // Then run the failure tests
+ runTests(new TestDescriptor[] {
+ new TestDescriptor(TEST2_FAIL, "2, StringBuffer not allocated with enough space", 1, rule),
+ new TestDescriptor(TEST9_FAIL, "9, Field level variable", 1, rule),
+ new TestDescriptor(TEST13_FAIL, "13, compound append", 1, rule),
+ new TestDescriptor(TEST15_FAIL, "15, Append int, incorrect presize", 1, rule),
+ new TestDescriptor(TEST17_FAIL, "17, Append char, incorrect presize", 1, rule),
+ new TestDescriptor(TEST19_FAIL, "19, String concatenation, incorrect presize", 1, rule),
+ new TestDescriptor(TEST20_FAIL, "20, String concatenation with non-literal, incorrect presize", 1, rule),
+ new TestDescriptor(TEST21_FAIL, "21, Incorrectly presized twice", 2, rule),
+ new TestDescriptor(TEST26_FAIL, "26, Compound if, pushed over the edge", 1, rule),
+ new TestDescriptor(TEST28_FAIL, "28, Compound if, pushed over the edge", 1, rule),
+ });
+ }
+
+ private static final String TEST1 =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(16);" + PMD.EOL +
+ " sb.append(\"foo\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST2_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " sb.append(\"Hello\");" + PMD.EOL +
+ " sb.append(\"World\");" + PMD.EOL +
+ " sb.append(\"How are you today world\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+
+ private static final String TEST3 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(l.size());" + PMD.EOL +
+ " sb.append(\"Hello\");" + PMD.EOL +
+ " sb.append(\"World\");" + PMD.EOL +
+ " sb.append(\"How are you today world\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST4 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " int x = 3;" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(x);" + PMD.EOL +
+ " sb.append(\"Hello\");" + PMD.EOL +
+ " sb.append(\"World\");" + PMD.EOL +
+ " sb.append(\"How are you today world\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST5 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " int x = 3;" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(5);" + PMD.EOL +
+ " sb.append(\"Hello\");" + PMD.EOL +
+ " sb = new StringBuffer(23);" + PMD.EOL +
+ " sb.append(\"How are you today world\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST6 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " int x = 3;" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(\"Initialize With A String\");" + PMD.EOL +
+ " sb.append(\"Hello\");" + PMD.EOL +
+ " sb.append(\"How are you today world\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST7 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " sb.append(\"1234567890\");" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"123456789\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST8 =
+ "public class Foo {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(200);" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " sb.append(\"Hello\");" + PMD.EOL +
+ " sb.append(\"How are you today world\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST9_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " sb.append(\"Hello\");" + PMD.EOL +
+ " sb.append(\"How are you today world\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST10 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(0);" + PMD.EOL +
+ " sb.append(l.get(2));" + PMD.EOL +
+ " sb.append(l.toString());" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST11 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = null;" + PMD.EOL +
+ " sb = new StringBuffer(20);" + PMD.EOL +
+ " sb.append(l.toString());" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+
+ private static final String TEST12 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(StringBuffer param) {" + PMD.EOL +
+ " param.append(\"Append something\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+
+ private static final String TEST13_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " sb.append(\"foo\").append(\"this will make it long\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+
+ private static final String TEST14 =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(30);" + PMD.EOL +
+ " sb.append(\"foo\").append(\"this is presized just right\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST15_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " sb.append(12345678901234567890);" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+
+ private static final String TEST16 =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " sb.append(12345);" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST17_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(2);" + PMD.EOL +
+ " sb.append('a');" + PMD.EOL +
+ " sb.append('a');" + PMD.EOL +
+ " sb.append('a');" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+
+ private static final String TEST18 =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(3);" + PMD.EOL +
+ " sb.append('a');" + PMD.EOL +
+ " sb.append('a');" + PMD.EOL +
+ " sb.append('a');" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+
+ private static final String TEST19_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar() {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " sb.append(\"This string\" + \" \" + \"isn't nice, but valid\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST20_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Foo.class);" + PMD.EOL +
+ " public void bar(String x) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " sb.append(\"This string\" + x + \"isn't nice, but valid\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST21_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " int x = 3;" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer(2);" + PMD.EOL +
+ " sb.append(\"Hello\");" + PMD.EOL +
+ " sb = new StringBuffer(5);" + PMD.EOL +
+ " sb.append(\"How are you today world\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST22 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " sb.append(\"1234567890\");" + PMD.EOL +
+ " } else if( l.size() == 5){" + PMD.EOL +
+ " sb.append(\"1234567890\");" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"1234567890\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST23 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " sb.append(\"12345\");" + PMD.EOL +
+ " } else if( l.size() == 5){" + PMD.EOL +
+ " sb.append(\"12345\");" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"12345\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " sb.append(\"12345\");" + PMD.EOL +
+ " } else if( l.size() == 5){" + PMD.EOL +
+ " sb.append(\"12345\");" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"12345\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+
+ private static final String TEST24 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " sb.append(\"This should use\");" + PMD.EOL +
+ " } else if( l.size() == 5){" + PMD.EOL +
+ " sb.append(\"The longest if\");" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"statement for its violation, which is this one\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST25 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " sb.append(\"More\");" + PMD.EOL +
+ " } else if( l.size() == 5){" + PMD.EOL +
+ " sb.append(\"Compound\");" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"If\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"A compound if\");" + PMD.EOL +
+ " } " + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST26_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(List l) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " if(true){" + PMD.EOL +
+ " sb.append(\"More\");" + PMD.EOL +
+ " } else if( l.size() == 5){" + PMD.EOL +
+ " sb.append(\"Compound\");" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"If\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " } else {" + PMD.EOL +
+ " sb.append(\"A compound if\");" + PMD.EOL +
+ " } " + PMD.EOL +
+ " sb.append(\"Push\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST27 =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(String str) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " switch(str.charAt(0)){" + PMD.EOL +
+ " case 'a':" + PMD.EOL +
+ " sb.append(\"Switch block\");" + PMD.EOL +
+ " break;" + PMD.EOL +
+ " case 'b':" + PMD.EOL +
+ " sb.append(\"Doesn't exceed\");" + PMD.EOL +
+ " break;" + PMD.EOL +
+ " default:" + PMD.EOL +
+ " sb.append(\"16 chars\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+ private static final String TEST28_FAIL =
+ "public class Foo {" + PMD.EOL +
+ " public void bar(String str) {" + PMD.EOL +
+ " StringBuffer sb = new StringBuffer();" + PMD.EOL +
+ " switch(str.charAt(0)){" + PMD.EOL +
+ " case 'a':" + PMD.EOL +
+ " sb.append(\"Switch block\");" + PMD.EOL +
+ " break;" + PMD.EOL +
+ " default:" + PMD.EOL +
+ " sb.append(\"The default block exceeds 16 characters and will fail\");" + PMD.EOL +
+ " }" + PMD.EOL +
+ " }" + PMD.EOL +
+ "}";
+
+}
\ No newline at end of file
diff --git a/pmd/rulesets/releases/36.xml b/pmd/rulesets/releases/36.xml
index 4f26c2abab..ccf99853d2 100644
--- a/pmd/rulesets/releases/36.xml
+++ b/pmd/rulesets/releases/36.xml
@@ -8,6 +8,7 @@ This ruleset contains links to rules that are new in PMD v3.6
+
diff --git a/pmd/rulesets/strings.xml b/pmd/rulesets/strings.xml
index b9e891b43b..bc3590f415 100644
--- a/pmd/rulesets/strings.xml
+++ b/pmd/rulesets/strings.xml
@@ -235,6 +235,31 @@ public class Foo {
}
}
}
+]]>
+
+
+
+
+
+Failing to pre-size a StringBuffer properly could cause it to re-size many times
+during runtime. This rule checks the characters that are actually passed into
+StringBuffer.append(), but represents a best guess "worst case" scenario. An
+empty StringBuffer constructor initializes the object to 16 characters. This default
+is assumed if the length of the constructor can not be determined.
+
+ 3
+
+
diff --git a/pmd/src/net/sourceforge/pmd/rules/strings/InsufficientStringBufferDeclaration.java b/pmd/src/net/sourceforge/pmd/rules/strings/InsufficientStringBufferDeclaration.java
new file mode 100644
index 0000000000..e373d804a6
--- /dev/null
+++ b/pmd/src/net/sourceforge/pmd/rules/strings/InsufficientStringBufferDeclaration.java
@@ -0,0 +1,274 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+package net.sourceforge.pmd.rules.strings;
+
+import net.sourceforge.pmd.AbstractRule;
+import net.sourceforge.pmd.ast.ASTAdditiveExpression;
+import net.sourceforge.pmd.ast.ASTBlockStatement;
+import net.sourceforge.pmd.ast.ASTFieldDeclaration;
+import net.sourceforge.pmd.ast.ASTFormalParameter;
+import net.sourceforge.pmd.ast.ASTIfStatement;
+import net.sourceforge.pmd.ast.ASTLiteral;
+import net.sourceforge.pmd.ast.ASTName;
+import net.sourceforge.pmd.ast.ASTPrimaryExpression;
+import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
+import net.sourceforge.pmd.ast.ASTPrimarySuffix;
+import net.sourceforge.pmd.ast.ASTSwitchLabel;
+import net.sourceforge.pmd.ast.ASTSwitchStatement;
+import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
+import net.sourceforge.pmd.ast.Node;
+import net.sourceforge.pmd.ast.SimpleNode;
+import net.sourceforge.pmd.symboltable.NameOccurrence;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.oro.text.perl.Perl5Util;
+
+/**
+ * This rule finds StringBuffers which may have been pre-sized incorrectly
+ *
+ * @see http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
+ * @author Allan Caplan
+ */
+public class InsufficientStringBufferDeclaration extends AbstractRule {
+
+ private final static Set blockParents;
+
+ static {
+ blockParents = new HashSet();
+ blockParents.add(ASTIfStatement.class);
+ blockParents.add(ASTSwitchStatement.class);
+ }
+
+ private final Perl5Util regexp = new Perl5Util();
+
+ public Object visit(ASTVariableDeclaratorId node, Object data) {
+
+ if (!"StringBuffer".equals(node.getNameDeclaration().getTypeImage())) {
+ return data;
+ }
+ Node rootNode = node;
+ int anticipatedLength = 0;
+ int constructorLength = 16;
+
+ constructorLength = getConstructorLength(node, constructorLength);
+ List usage = node.getUsages();
+ Map blocks = new HashMap();
+ for (int ix = 0; ix < usage.size(); ix++) {
+ NameOccurrence no = (NameOccurrence) usage.get(ix);
+ SimpleNode n = no.getLocation();
+ if (!InefficientStringBuffering.isInStringBufferAppend(n, 3)) {
+ if (!no.isOnLeftHandSide()) {
+ continue;
+ }
+ if (constructorLength != -1 && anticipatedLength > constructorLength) {
+ anticipatedLength += processBlocks(blocks);
+ String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
+ addViolation(data, rootNode, param);
+ }
+ constructorLength = getConstructorLength(n, constructorLength);
+ rootNode = n;
+ anticipatedLength = 0;
+ }
+ ASTPrimaryExpression s = (ASTPrimaryExpression) n.getFirstParentOfType(ASTPrimaryExpression.class);
+ int numChildren = s.jjtGetNumChildren();
+ for (int jx = 0; jx < numChildren; jx++) {
+ SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
+ if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
+ continue;
+ }
+ int thisSize = 0;
+ Node block = getFirstParentBlock(sn);
+ if (isAdditive(sn)) {
+ thisSize = processAdditive(sn);
+ } else {
+ thisSize = processNode(sn);
+ }
+ if (block != null) {
+ storeBlockStatistics(blocks, thisSize, block);
+ } else {
+ anticipatedLength += thisSize;
+ }
+ }
+ }
+ anticipatedLength += processBlocks(blocks);
+ if (constructorLength != -1 && anticipatedLength > constructorLength) {
+ String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
+ addViolation(data, rootNode, param);
+ }
+ return data;
+ }
+
+ /**
+ * This rule is concerned with IF and Switch blocks. Process the block into
+ * a local Map, from which we can later determine which is the longest block
+ * inside
+ *
+ * @param blocks
+ * The map of blocks in the method being investigated
+ * @param thisSize
+ * The size of the current block
+ * @param block
+ * The block in question
+ */
+ private void storeBlockStatistics(Map blocks, int thisSize, Node block) {
+ Node statement = block.jjtGetParent();
+ if (ASTIfStatement.class.equals(block.jjtGetParent().getClass())) {
+ // Else Ifs are their own subnode in AST. So we have to
+ // look a little farther up the tree to find the IF statement
+ Node possibleStatement = ((SimpleNode) statement).getFirstParentOfType(ASTIfStatement.class);
+ if (possibleStatement != null && possibleStatement.getClass().equals(ASTIfStatement.class)) {
+ statement = possibleStatement;
+ }
+ }
+ Map thisBranch = (Map) blocks.get(statement);
+ if (thisBranch == null) {
+ thisBranch = new HashMap();
+ blocks.put(statement, thisBranch);
+ }
+ Integer x = (Integer) thisBranch.get(block);
+ if (x != null) {
+ thisSize += x.intValue();
+ }
+ thisBranch.put(statement, new Integer(thisSize));
+ }
+
+ private int processBlocks(Map blocks) {
+ int anticipatedLength = 0;
+ int ifLength = 0;
+ for (Iterator iter = blocks.entrySet().iterator(); iter.hasNext();) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ ifLength = 0;
+ for (Iterator iter2 = ((Map) entry.getValue()).entrySet().iterator(); iter2.hasNext();) {
+ Map.Entry entry2 = (Map.Entry) iter2.next();
+ Integer value = (Integer) entry2.getValue();
+ ifLength = Math.max(ifLength, value.intValue());
+ }
+ anticipatedLength += ifLength;
+ }
+ return anticipatedLength;
+ }
+
+ private int processAdditive(SimpleNode sn) {
+ ASTAdditiveExpression additive = (ASTAdditiveExpression) sn.getFirstChildOfType(ASTAdditiveExpression.class);
+ if (additive == null) {
+ return 0;
+ }
+ int anticipatedLength = 0;
+ for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
+ SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
+ ASTLiteral literal = (ASTLiteral) childNode.getFirstChildOfType(ASTLiteral.class);
+ if (literal != null && literal.getImage() != null) {
+ anticipatedLength += literal.getImage().length() - 2;
+ }
+ }
+
+ return anticipatedLength;
+ }
+
+ private int processNode(SimpleNode sn) {
+ int anticipatedLength = 0;
+ ASTPrimaryPrefix xn = (ASTPrimaryPrefix) sn.getFirstChildOfType(ASTPrimaryPrefix.class);
+ if (xn.jjtGetNumChildren() != 0 && xn.jjtGetChild(0).getClass().equals(ASTLiteral.class)) {
+
+ String str = ((SimpleNode) xn.jjtGetChild(0)).getImage();
+ int i = (regexp.match("/^[\"']/", str)) ? 2 : 0;
+ anticipatedLength += str.length() - i;
+ }
+ return anticipatedLength;
+ }
+
+ private int getConstructorLength(SimpleNode node, int constructorLength) {
+ SimpleNode block = (SimpleNode) node.getFirstParentOfType(ASTBlockStatement.class);
+ List literal;
+
+ if (block == null) {
+ block = (ASTFieldDeclaration) node.getFirstParentOfType(ASTFieldDeclaration.class);
+ }
+ if (block == null) {
+ block = (ASTFormalParameter) node.getFirstParentOfType(ASTFormalParameter.class);
+ if (block != null) {
+ constructorLength = -1;
+ }
+ }
+ literal = (block.findChildrenOfType(ASTLiteral.class));
+ if (literal.size() == 0) {
+ List name = (block.findChildrenOfType(ASTName.class));
+ if (name.size() != 0) {
+ constructorLength = -1;
+ }
+ } else if (literal.size() == 1) {
+ String str = ((SimpleNode) literal.get(0)).getImage();
+ if (str == null) {
+ constructorLength = 0;
+ } else if (regexp.match("/^['\"]/", str)) {
+ // since it's not taken into account
+ // anywhere. only count the extra 16
+ // characters
+ constructorLength = 16; // don't add the constructor's length,
+ } else {
+ constructorLength = Integer.parseInt(str);
+ }
+ }
+ return constructorLength;
+ }
+
+ private boolean isAdditive(SimpleNode n) {
+ return n.findChildrenOfType(ASTAdditiveExpression.class).size() >= 1;
+ }
+
+ /**
+ * Locate the block that the given node is in, if any
+ *
+ * @param node
+ * The node we're looking for a parent of
+ * @return Node - The node that corresponds to any block that may be a
+ * parent of this object
+ */
+ private Node getFirstParentBlock(Node node) {
+ Node parentNode = node.jjtGetParent();
+
+ Node lastNode = node;
+ while (parentNode != null && !blockParents.contains(parentNode.getClass())) {
+ lastNode = parentNode;
+ parentNode = parentNode.jjtGetParent();
+ }
+ if (parentNode != null && ASTIfStatement.class.equals(parentNode.getClass())) {
+ parentNode = lastNode;
+ } else if (parentNode != null && parentNode.getClass().equals(ASTSwitchStatement.class)) {
+ parentNode = getSwitchParent(parentNode, lastNode);
+ }
+ return parentNode;
+ }
+
+ /**
+ * Determine which SwitchLabel we belong to inside a switch
+ *
+ * @param parentNode
+ * The parent node we're looking at
+ * @param lastNode
+ * The last node processed
+ * @return The parent node for the switch statement
+ */
+ private static Node getSwitchParent(Node parentNode, Node lastNode) {
+ int allChildren = parentNode.jjtGetNumChildren();
+ ASTSwitchLabel label = null;
+ for (int ix = 0; ix < allChildren; ix++) {
+ Node n = parentNode.jjtGetChild(ix);
+ if (n.getClass().equals(ASTSwitchLabel.class)) {
+ label = (ASTSwitchLabel) n;
+ } else if (n.equals(lastNode)) {
+ parentNode = label;
+ break;
+ }
+ }
+ return parentNode;
+ }
+
+}
\ No newline at end of file