diff --git a/pmd/etc/changelog.txt b/pmd/etc/changelog.txt index 1d27b723ec..49ee49f29c 100644 --- a/pmd/etc/changelog.txt +++ b/pmd/etc/changelog.txt @@ -259,6 +259,8 @@ The following is relatively complete list of the major changes (this may not be Added - LanguageVersion Rule.getMinimumLanguageVersion() Added - Rule.setMaximumLanguageVersion(LanguageVersion) Added - LanguageVersion Rule.getMaximumLanguageVersion() + Added - Rule.setDeprecated(boolean) + Added - boolean Rule.isDeprecated() API Change - Changes to PMD class Renamed - PMD.EXCLUDE_MARKER to PMD.SUPPRESS_MARKER @@ -272,6 +274,7 @@ The following is relatively complete list of the major changes (this may not be Moved - PMD.setClassLoader(ClassLoader) to Configuration.getClassLoader(ClassLoader) Moved - PMD.setDefaultLanguageVersion(LanguageVersion) to Configuration.setDefaultLanguageVersion(LanguageVersion) Moved - PMD.setDefaultLanguageVersions(List) to Configuration.setDefaultLanguageVersions(List) + Moved - PMD.createClasspathClassLoader(String) to Configuration.createClasspathClassLoader(String) API Change - Changes to Node interface Renamed - findChildrenOfType() as findDescendantsOfType() @@ -286,6 +289,7 @@ New Language 'xml' added, for writing XPathRules against XML documents Rules can now define a 'violationSuppressRegex' property to universally suppress violations with messages matching the given regular expression Rules can now define a 'violationSuppressXPath' property to universally suppress violations on nodes which match the given relative XPath expression Rules are now directly associated with a corresponding Language, and a can also be associated with a specific Language Version range if desired. +Rules can now be flagged with deprecated='true' in the RuleSet XML to allow the PMD Project to indicate a Rule (1) is scheduled for removal, (2) has been removed, or (3) has been renamed/moved. Other changes: diff --git a/pmd/etc/ruleset.dtd b/pmd/etc/ruleset.dtd index b20f964077..268eca42a1 100644 --- a/pmd/etc/ruleset.dtd +++ b/pmd/etc/ruleset.dtd @@ -16,6 +16,7 @@ + diff --git a/pmd/etc/ruleset_xml_schema.xsd b/pmd/etc/ruleset_xml_schema.xsd index 0315f14350..e3c0a9f64b 100644 --- a/pmd/etc/ruleset_xml_schema.xsd +++ b/pmd/etc/ruleset_xml_schema.xsd @@ -55,6 +55,7 @@ + diff --git a/pmd/regress/test/net/sourceforge/pmd/RuleReferenceTest.java b/pmd/regress/test/net/sourceforge/pmd/RuleReferenceTest.java index 6f000740e7..2b2cbee05e 100644 --- a/pmd/regress/test/net/sourceforge/pmd/RuleReferenceTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/RuleReferenceTest.java @@ -39,6 +39,7 @@ public class RuleReferenceTest { ruleReference.setLanguage(Language.JAVA); ruleReference.setMinimumLanguageVersion(LanguageVersion.JAVA_13); ruleReference.setMaximumLanguageVersion(LanguageVersion.JAVA_17); + ruleReference.setDeprecated(true); ruleReference.setName("name2"); ruleReference.addProperty("property1", "value2"); ruleReference.setMessage("message2"); @@ -56,6 +57,10 @@ public class RuleReferenceTest { assertEquals("Override failed", LanguageVersion.JAVA_17, ruleReference.getMaximumLanguageVersion()); assertEquals("Override failed", LanguageVersion.JAVA_17, ruleReference.getOverriddenMaximumLanguageVersion()); + assertEquals("Override failed", false, ruleReference.getRule().isDeprecated()); + assertEquals("Override failed", true, ruleReference.isDeprecated()); + assertEquals("Override failed", true, ruleReference.isOverriddenDeprecated()); + assertEquals("Override failed", "name2", ruleReference.getName()); assertEquals("Override failed", "name2", ruleReference.getOverriddenName()); @@ -101,6 +106,7 @@ public class RuleReferenceTest { ruleReference.setLanguage(Language.JAVA); ruleReference.setMinimumLanguageVersion(LanguageVersion.JAVA_13); ruleReference.setMaximumLanguageVersion(LanguageVersion.JAVA_17); + ruleReference.setDeprecated(false); ruleReference.setName("name1"); ruleReference.addProperty("property1", "value1"); ruleReference.setMessage("message1"); @@ -118,6 +124,9 @@ public class RuleReferenceTest { assertEquals("Override failed", LanguageVersion.JAVA_17, ruleReference.getMaximumLanguageVersion()); assertNull("Override failed", ruleReference.getOverriddenMaximumLanguageVersion()); + assertEquals("Override failed", false, ruleReference.isDeprecated()); + assertNull("Override failed", ruleReference.isOverriddenDeprecated()); + assertEquals("Override failed", "name1", ruleReference.getName()); assertNull("Override failed", ruleReference.getOverriddenName()); diff --git a/pmd/regress/test/net/sourceforge/pmd/RuleSetFactoryTest.java b/pmd/regress/test/net/sourceforge/pmd/RuleSetFactoryTest.java index e4ecabaa23..86532b187c 100644 --- a/pmd/regress/test/net/sourceforge/pmd/RuleSetFactoryTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/RuleSetFactoryTest.java @@ -254,6 +254,35 @@ public class RuleSetFactoryTest { loadFirstRule(INVERTED_MINIMUM_MAXIMUM_LANGUAGE_VERSIONS); } + @Test + public void testDirectDeprecatedRule() { + Rule r = loadFirstRule(DIRECT_DEPRECATED_RULE); + assertNotNull("Direct Deprecated Rule", r); + } + + @Test + public void testReferenceToDeprecatedRule() { + Rule r = loadFirstRule(REFERENCE_TO_DEPRECATED_RULE); + assertNotNull("Reference to Deprecated Rule", r); + assertTrue("Rule Reference", r instanceof RuleReference); + assertFalse("Not deprecated", r.isDeprecated()); + assertTrue("Original Rule Deprecated", ((RuleReference)r).getRule().isDeprecated()); + assertEquals("Rule name", r.getName(), DEPRECATED_RULE_NAME); + } + + @Test + public void testRuleSetReferenceWithDeprecatedRule() { + RuleSet ruleSet = loadRuleSet(REFERENCE_TO_RULESET_WITH_DEPRECATED_RULE); + assertNotNull("RuleSet", ruleSet); + assertFalse("RuleSet empty", ruleSet.getRules().isEmpty()); + // No deprecated Rules should be loaded when loading an entire RuleSet by reference. + Rule r = ruleSet.getRuleByName(DEPRECATED_RULE_NAME); + assertNull("Deprecated Rule Reference", r); + for (Rule rule: ruleSet.getRules()) { + assertFalse("Rule not deprecated", rule.isDeprecated()); + } + } + @Test public void testIncludeExcludePatterns() { RuleSet ruleSet = loadRuleSet(INCLUDE_EXCLUDE_RULESET); @@ -478,6 +507,14 @@ public class RuleSetFactoryTest { if (rule1 instanceof RuleReference) { RuleReference ruleReference1 = (RuleReference) rule1; RuleReference ruleReference2 = (RuleReference) rule2; + assertEquals(message + ", RuleReference overridden language", ruleReference1.getOverriddenLanguage(), + ruleReference2.getOverriddenLanguage()); + assertEquals(message + ", RuleReference overridden minimum language version", ruleReference1.getOverriddenMinimumLanguageVersion(), + ruleReference2.getOverriddenMinimumLanguageVersion()); + assertEquals(message + ", RuleReference overridden maximum language version", ruleReference1.getOverriddenMaximumLanguageVersion(), + ruleReference2.getOverriddenMaximumLanguageVersion()); + assertEquals(message + ", RuleReference overridden deprecated", ruleReference1.isOverriddenDeprecated(), + ruleReference2.isOverriddenDeprecated()); assertEquals(message + ", RuleReference overridden name", ruleReference1.getOverriddenName(), ruleReference2.getOverriddenName()); assertEquals(message + ", RuleReference overridden description", ruleReference1 @@ -774,6 +811,28 @@ public class RuleSetFactoryTest { + " minimumLanguageVersion=\"1.7\"" + PMD.EOL + "maximumLanguageVersion=\"1.4\">" + PMD.EOL + ""; + private static final String DIRECT_DEPRECATED_RULE = "" + PMD.EOL + + "" + PMD.EOL + "testdesc" + PMD.EOL + + "" + PMD.EOL + + ""; + + // Note: Update this RuleSet name to a different RuleSet with deprecated Rules when the Rules are finally removed. + private static final String DEPRECATED_RULE_RULESET_NAME = "rulesets/basic.xml"; + + // Note: Update this Rule name to a different deprecated Rule when the one listed here is finally removed. + private static final String DEPRECATED_RULE_NAME = "EmptyCatchBlock"; + + private static final String REFERENCE_TO_DEPRECATED_RULE = "" + PMD.EOL + + "" + PMD.EOL + "testdesc" + PMD.EOL + "" + PMD.EOL + + ""; + + private static final String REFERENCE_TO_RULESET_WITH_DEPRECATED_RULE = "" + PMD.EOL + + "" + PMD.EOL + "testdesc" + PMD.EOL + "" + PMD.EOL + ""; + private static final String DFA = "" + PMD.EOL + "" + PMD.EOL + "testdesc" + PMD.EOL + "exclude2" + PMD.EOL + "exclude3" + PMD.EOL + ""; + private static final String EXTERNAL_REFERENCE_RULE_SET = "" + PMD.EOL + + "" + PMD.EOL + "testdesc" + PMD.EOL + + "" + PMD.EOL + ""; + private Rule loadFirstRule(String ruleSetXml) { RuleSet rs = loadRuleSet(ruleSetXml); return rs.getRules().iterator().next(); @@ -809,10 +872,6 @@ public class RuleSetFactoryTest { assertEquals(UnusedLocalVariableRule.class.getName(), rs.getRuleByName("UnusedLocalVariable").getRuleClass()); } - private static final String EXTERNAL_REFERENCE_RULE_SET = "" + PMD.EOL - + "" + PMD.EOL + "testdesc" + PMD.EOL - + "" + PMD.EOL + ""; - public static junit.framework.Test suite() { return new JUnit4TestAdapter(RuleSetFactoryTest.class); } diff --git a/pmd/rulesets/basic.xml b/pmd/rulesets/basic.xml index 95cf52b190..ea23b57fee 100644 --- a/pmd/rulesets/basic.xml +++ b/pmd/rulesets/basic.xml @@ -762,7 +762,25 @@ public class Foo { ]]> - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pmd/rulesets/internal/dogfood.xml b/pmd/rulesets/internal/dogfood.xml index 14b24b93ec..d9b2351fff 100644 --- a/pmd/rulesets/internal/dogfood.xml +++ b/pmd/rulesets/internal/dogfood.xml @@ -17,69 +17,54 @@ Rules to check PMD itself .*cppast/.*.java - - - - - - - + - - - - - - - - - - + - - - - - - - - - - - + + + + + + + + - + + + + diff --git a/pmd/src/net/sourceforge/pmd/PMD.java b/pmd/src/net/sourceforge/pmd/PMD.java index e592119669..811281f218 100644 --- a/pmd/src/net/sourceforge/pmd/PMD.java +++ b/pmd/src/net/sourceforge/pmd/PMD.java @@ -259,7 +259,9 @@ public class PMD { RuleSetFactory ruleSetFactory = new RuleSetFactory(); ruleSetFactory.setMinimumPriority(opts.getMinPriority()); + ruleSetFactory.setWarnDeprecated(true); RuleSets rulesets = ruleSetFactory.createRuleSets(opts.getRulesets()); + ruleSetFactory.setWarnDeprecated(false); printRuleNamesInDebug(rulesets); long endLoadRules = System.nanoTime(); Benchmark.mark(Benchmark.TYPE_LOAD_RULES, endLoadRules - startLoadRules, 0); diff --git a/pmd/src/net/sourceforge/pmd/Rule.java b/pmd/src/net/sourceforge/pmd/Rule.java index 8849ea7cfb..b3d4f644e4 100644 --- a/pmd/src/net/sourceforge/pmd/Rule.java +++ b/pmd/src/net/sourceforge/pmd/Rule.java @@ -58,6 +58,23 @@ public interface Rule { */ void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion); + /** + * Gets whether this Rule is deprecated. A deprecated Rule is one which: + *
    + *
  • is scheduled for removal in a future version of PMD
  • + *
  • or, has been removed and replaced with a non-functioning place-holder + * and will be completely removed in a future version of PMD
  • + *
  • or, has been renamed/moved and the old name will be completely + * removed in a future version of PMD
  • + *
      + */ + boolean isDeprecated(); + + /** + * Sets whether this Rule is deprecated. + */ + void setDeprecated(boolean deprecated); + /** * Get the name of this Rule. */ diff --git a/pmd/src/net/sourceforge/pmd/RuleSetFactory.java b/pmd/src/net/sourceforge/pmd/RuleSetFactory.java index d11fbefa0d..9abf129945 100644 --- a/pmd/src/net/sourceforge/pmd/RuleSetFactory.java +++ b/pmd/src/net/sourceforge/pmd/RuleSetFactory.java @@ -5,10 +5,12 @@ package net.sourceforge.pmd; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; +import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -16,6 +18,7 @@ import javax.xml.parsers.ParserConfigurationException; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.rule.MockRule; import net.sourceforge.pmd.lang.rule.RuleReference; import net.sourceforge.pmd.util.ResourceLoader; @@ -30,7 +33,29 @@ import org.xml.sax.SAXException; */ public class RuleSetFactory { + private static final Logger LOG = Logger.getLogger(RuleSetFactory.class.getName()); + private RulePriority minPriority = RulePriority.LOW; + private boolean warnDeprecated = false; + + // This is a cache of RuleSets loaded while loading RuleSets in bulk. It is not used during individual RuleSet loading. + private Map ruleSetCache = new HashMap(); + + /** + * Default constructor. + */ + public RuleSetFactory() { + } + + /** + * This constructor is to be used internally when there is a need to load + * a RuleSet using default settings. Certain global state will be + * propagated between the original RuleSetFactory and the new RuleSetFactory. + * @param ruleSetFactory The RuleSetFactory creating the new RuleSetFactory. + */ + private RuleSetFactory(RuleSetFactory ruleSetFactory) { + this.ruleSetCache = ruleSetFactory.ruleSetCache; + } /** * Set the minimum rule priority threshold for all Rules which are loaded @@ -42,6 +67,14 @@ public class RuleSetFactory { this.minPriority = minPriority; } + /** + * Set whether warning messages should be logged for usage of deprecated Rules. + * @param warnDeprecated true to log warning messages. + */ + public void setWarnDeprecated(boolean warnDeprecated) { + this.warnDeprecated = warnDeprecated; + } + /** * Returns an Iterator of RuleSet objects loaded from descriptions from the * "rulesets.properties" resource. @@ -70,6 +103,7 @@ public class RuleSetFactory { * @throws RuleSetNotFoundException if unable to find a resource. */ public RuleSets createRuleSets(String ruleSetFileNames) throws RuleSetNotFoundException { + // Warning: This method should not be used to implement the internals of RuleSetFactory, because it does not take an explicit ClassLoader. return createRuleSets(ruleSetFileNames, getClass().getClassLoader()); } @@ -82,14 +116,21 @@ public class RuleSetFactory { * @throws RuleSetNotFoundException if unable to find a resource. */ public RuleSets createRuleSets(String ruleSetFileNames, ClassLoader classLoader) throws RuleSetNotFoundException { - RuleSets ruleSets = new RuleSets(); + try { + // Create the cache for bulk RuleSet processing. + ruleSetCache = new HashMap(); + RuleSets ruleSets = new RuleSets(); - for (StringTokenizer st = new StringTokenizer(ruleSetFileNames, ","); st.hasMoreTokens();) { - RuleSet ruleSet = createSingleRuleSet(st.nextToken().trim(), classLoader); - ruleSets.addRuleSet(ruleSet); + for (StringTokenizer st = new StringTokenizer(ruleSetFileNames, ","); st.hasMoreTokens();) { + RuleSet ruleSet = createSingleRuleSet(st.nextToken().trim(), classLoader); + ruleSets.addRuleSet(ruleSet); + } + + return ruleSets; + } finally { + // Remove the cache, so we don't affect behavior of subsequent or single RuleSet processing. + ruleSetCache = null; } - - return ruleSets; } /** @@ -122,6 +163,7 @@ public class RuleSetFactory { * @throws RuleSetNotFoundException if unable to find a resource. */ public RuleSet createSingleRuleSet(String ruleSetFileName) throws RuleSetNotFoundException { + // Warning: This method should not be used to implement the internals of RuleSetFactory, because it does not take an explicit ClassLoader. return createSingleRuleSet(ruleSetFileName, getClass().getClassLoader()); } @@ -135,7 +177,18 @@ public class RuleSetFactory { */ private RuleSet createSingleRuleSet(String ruleSetFileName, ClassLoader classLoader) throws RuleSetNotFoundException { - return parseRuleSetNode(ruleSetFileName, tryToGetStreamTo(ruleSetFileName, classLoader), classLoader); + // If we have a RuleSet cache, check in there first. + RuleSet ruleSet = null; + if (ruleSetCache != null) { + ruleSet = ruleSetCache.get(ruleSetFileName); + } + if (ruleSet == null) { + ruleSet = parseRuleSetNode(ruleSetFileName, tryToGetStreamTo(ruleSetFileName, classLoader), classLoader); + if (ruleSetCache != null) { + ruleSetCache.put(ruleSet.getFileName(), ruleSet); + } + } + return ruleSet; } /** @@ -146,6 +199,7 @@ public class RuleSetFactory { * @return A new RuleSet. */ public RuleSet createRuleSet(InputStream inputStream) { + // Warning: This method should not be used to implement the internals of RuleSetFactory, because it does not take an explicit ClassLoader. return createRuleSet(inputStream, getClass().getClassLoader()); } @@ -251,24 +305,26 @@ public class RuleSetFactory { Element ruleElement = (Element) ruleNode; String ref = ruleElement.getAttribute("ref"); if (ref.endsWith("xml")) { - parseRuleSetReferenceNode(ruleSet, ruleElement, ref); + parseRuleSetReferenceNode(ruleSet, ruleElement, ref, classLoader); } else if (ref.trim().length() == 0) { parseSingleRuleNode(ruleSet, ruleNode, classLoader); } else { - parseRuleReferenceNode(ruleSet, ruleNode, ref); + parseRuleReferenceNode(ruleSet, ruleNode, ref, classLoader); } } /** * Parse a rule node as an RuleSetReference for all Rules. Every Rule from * the referred to RuleSet will be added as a RuleReference except for those - * explicitly excluded. + * explicitly excluded, below the minimum priority threshold for this + * RuleSetFactory, or which are deprecated. * * @param ruleSet The RuleSet being constructed. * @param ruleElement Must be a rule element node. * @param ref The RuleSet reference. + * @param classLoader The ClassLoader to load Classes and resources. */ - private void parseRuleSetReferenceNode(RuleSet ruleSet, Element ruleElement, String ref) + private void parseRuleSetReferenceNode(RuleSet ruleSet, Element ruleElement, String ref, ClassLoader classLoader) throws RuleSetNotFoundException { RuleSetReference ruleSetReference = new RuleSetReference(); @@ -283,11 +339,12 @@ public class RuleSetFactory { } } - RuleSetFactory ruleSetFactory = new RuleSetFactory(); - RuleSet otherRuleSet = ruleSetFactory.createRuleSet(ResourceLoader.loadResourceAsStream(ref)); + RuleSetFactory ruleSetFactory = new RuleSetFactory(this); + RuleSet otherRuleSet = ruleSetFactory.createSingleRuleSet(ref, classLoader); for (Rule rule : otherRuleSet.getRules()) { if (!ruleSetReference.getExcludes().contains(rule.getName()) - && rule.getPriority().compareTo(minPriority) <= 0) { + && rule.getPriority().compareTo(minPriority) <= 0 + && !rule.isDeprecated()) { RuleReference ruleReference = new RuleReference(); ruleReference.setRuleSetReference(ruleSetReference); ruleReference.setRule(rule); @@ -409,21 +466,38 @@ public class RuleSetFactory { * * @param ruleSet The RuleSet being constructed. * @param ruleNode Must be a rule element node. - * @param classLoader The ClassLoader to load Classes and resources. * @param ref A reference to a Rule. + * @param classLoader The ClassLoader to load Classes and resources. */ - private void parseRuleReferenceNode(RuleSet ruleSet, Node ruleNode, String ref) throws RuleSetNotFoundException { - RuleSetFactory ruleSetFactory = new RuleSetFactory(); + private void parseRuleReferenceNode(RuleSet ruleSet, Node ruleNode, String ref, ClassLoader classLoader) + throws RuleSetNotFoundException { + RuleSetFactory ruleSetFactory = new RuleSetFactory(this); ExternalRuleID externalRuleID = new ExternalRuleID(ref); - RuleSet externalRuleSet = ruleSetFactory.createRuleSet(ResourceLoader.loadResourceAsStream(externalRuleID - .getFilename())); + RuleSet externalRuleSet = ruleSetFactory.createSingleRuleSet(externalRuleID.getFilename(), classLoader); Rule externalRule = externalRuleSet.getRuleByName(externalRuleID.getRuleName()); if (externalRule == null) { throw new IllegalArgumentException("Unable to find rule " + externalRuleID.getRuleName() + "; perhaps the rule name is mispelled?"); } + if (warnDeprecated && externalRule.isDeprecated()) { + if (externalRule instanceof RuleReference) { + RuleReference ruleReference = (RuleReference) externalRule; + LOG.warning("Use Rule name " + ruleReference.getRuleSetReference().getRuleSetFileName() + "/" + + ruleReference.getName() + " instead of the deprecated Rule name " + externalRuleID + + ". Future versions of PMD will remove support for this deprecated Rule name usage."); + } else if (externalRule instanceof MockRule) { + LOG.warning("Discontinue using Rule name " + externalRuleID + + " as it has been removed from PMD and no longer functions." + + " Future versions of PMD will remove support for this Rule."); + } else { + LOG.warning("Discontinue using Rule name " + externalRuleID + + " as it is scheduled for removal from PMD." + + " Future versions of PMD will remove support for this Rule."); + } + } + RuleSetReference ruleSetReference = new RuleSetReference(); ruleSetReference.setAllRules(false); ruleSetReference.setRuleSetFileName(externalRuleID.getFilename()); @@ -433,6 +507,9 @@ public class RuleSetFactory { ruleReference.setRule(externalRule); Element ruleElement = (Element) ruleNode; + if (ruleElement.hasAttribute("deprecated")) { + ruleReference.setDeprecated(Boolean.parseBoolean(ruleElement.getAttribute("deprecated"))); + } if (ruleElement.hasAttribute("name")) { ruleReference.setName(ruleElement.getAttribute("name")); } diff --git a/pmd/src/net/sourceforge/pmd/RuleSetWriter.java b/pmd/src/net/sourceforge/pmd/RuleSetWriter.java index 6da69b1a35..11561cd371 100644 --- a/pmd/src/net/sourceforge/pmd/RuleSetWriter.java +++ b/pmd/src/net/sourceforge/pmd/RuleSetWriter.java @@ -138,6 +138,7 @@ public class RuleSetWriter { Language language = ruleReference.getOverriddenLanguage(); LanguageVersion minimumLanguageVersion = ruleReference.getOverriddenMinimumLanguageVersion(); LanguageVersion maximumLanguageVersion = ruleReference.getOverriddenMaximumLanguageVersion(); + Boolean deprecated = ruleReference.isOverriddenDeprecated(); String name = ruleReference.getOverriddenName(); String ref = ruleReference.getRuleSetReference().getRuleSetFileName() + "/" + ruleReference.getName(); String message = ruleReference.getOverriddenMessage(); @@ -146,22 +147,22 @@ public class RuleSetWriter { RulePriority priority = ruleReference.getOverriddenPriority(); Properties properties = ruleReference.getOverriddenProperties(); List examples = ruleReference.getOverriddenExamples(); - return createSingleRuleElement(language, minimumLanguageVersion, maximumLanguageVersion, name, null, + return createSingleRuleElement(language, minimumLanguageVersion, maximumLanguageVersion, deprecated, name, null, ref, message, externalInfoUrl, null, null, null, description, priority, properties, examples); } } else { return createSingleRuleElement(rule instanceof ImmutableLanguage ? null : rule.getLanguage(), rule - .getMinimumLanguageVersion(), rule.getMaximumLanguageVersion(), rule.getName(), rule.getSince(), - null, rule.getMessage(), rule.getExternalInfoUrl(), rule.getRuleClass(), rule.usesDFA(), rule - .usesTypeResolution(), rule.getDescription(), rule.getPriority(), rule.getProperties(), - rule.getExamples()); + .getMinimumLanguageVersion(), rule.getMaximumLanguageVersion(), rule.isDeprecated(), + rule.getName(), rule.getSince(), null, rule.getMessage(), rule.getExternalInfoUrl(), rule + .getRuleClass(), rule.usesDFA(), rule.usesTypeResolution(), rule.getDescription(), rule + .getPriority(), rule.getProperties(), rule.getExamples()); } } private Element createSingleRuleElement(Language language, LanguageVersion minimumLanguageVersion, - LanguageVersion maximumLanguageVersion, String name, String since, String ref, String message, - String externalInfoUrl, String clazz, Boolean dfa, Boolean typeResolution, String description, - RulePriority priority, Properties properties, List examples) { + LanguageVersion maximumLanguageVersion, Boolean deprecated, String name, String since, String ref, + String message, String externalInfoUrl, String clazz, Boolean dfa, Boolean typeResolution, + String description, RulePriority priority, Properties properties, List examples) { Element ruleElement = document.createElement("rule"); if (language != null) { ruleElement.setAttribute("language", language.getTerseName()); @@ -172,6 +173,9 @@ public class RuleSetWriter { if (maximumLanguageVersion != null) { ruleElement.setAttribute("maximumLanguageVersion", maximumLanguageVersion.getVersion()); } + if (deprecated != null) { + ruleElement.setAttribute("deprecated", deprecated.toString()); + } if (name != null) { ruleElement.setAttribute("name", name); } diff --git a/pmd/src/net/sourceforge/pmd/ant/PMDTask.java b/pmd/src/net/sourceforge/pmd/ant/PMDTask.java index 5f85b49439..8e378b938b 100644 --- a/pmd/src/net/sourceforge/pmd/ant/PMDTask.java +++ b/pmd/src/net/sourceforge/pmd/ant/PMDTask.java @@ -194,7 +194,9 @@ public class PMDTask extends Task { // This is just used to validate and display rules. Each thread will create its own ruleset RuleSets rules; ruleSetFactory.setMinimumPriority(minPriority); + ruleSetFactory.setWarnDeprecated(true); rules = ruleSetFactory.createRuleSets(ruleSetFiles, classLoader); + ruleSetFactory.setWarnDeprecated(false); logRulesUsed(rules); } catch (RuleSetNotFoundException e) { throw new BuildException(e.getMessage()); diff --git a/pmd/src/net/sourceforge/pmd/lang/rule/AbstractDelegateRule.java b/pmd/src/net/sourceforge/pmd/lang/rule/AbstractDelegateRule.java index 2668a1e27a..c9080a4a1a 100644 --- a/pmd/src/net/sourceforge/pmd/lang/rule/AbstractDelegateRule.java +++ b/pmd/src/net/sourceforge/pmd/lang/rule/AbstractDelegateRule.java @@ -17,7 +17,7 @@ import net.sourceforge.pmd.lang.ast.Node; /** * Base class for Rule implementations which delegate to another Rule instance. */ -public class AbstractDelegateRule implements Rule { +public abstract class AbstractDelegateRule implements Rule { private Rule rule; public AbstractDelegateRule() { @@ -55,6 +55,14 @@ public class AbstractDelegateRule implements Rule { return rule.getMaximumLanguageVersion(); } + public boolean isDeprecated() { + return rule.isDeprecated(); + } + + public void setDeprecated(boolean deprecated) { + rule.setDeprecated(deprecated); + } + public String getName() { return rule.getName(); } diff --git a/pmd/src/net/sourceforge/pmd/lang/rule/AbstractRule.java b/pmd/src/net/sourceforge/pmd/lang/rule/AbstractRule.java index e6ddc8e4e5..ecaa054ff9 100644 --- a/pmd/src/net/sourceforge/pmd/lang/rule/AbstractRule.java +++ b/pmd/src/net/sourceforge/pmd/lang/rule/AbstractRule.java @@ -34,6 +34,7 @@ public abstract class AbstractRule implements Rule { private Language language; private LanguageVersion minimumLanguageVersion; private LanguageVersion maximumLanguageVersion; + private boolean deprecated; private String name = getClass().getName(); private String since; private String ruleClass = getClass().getName(); @@ -76,6 +77,14 @@ public abstract class AbstractRule implements Rule { this.maximumLanguageVersion = maximumLanguageVersion; } + public boolean isDeprecated() { + return deprecated; + } + + public void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } + public String getName() { return name; } diff --git a/pmd/src/net/sourceforge/pmd/lang/rule/RuleReference.java b/pmd/src/net/sourceforge/pmd/lang/rule/RuleReference.java index 7c131147b2..c1582c02cd 100644 --- a/pmd/src/net/sourceforge/pmd/lang/rule/RuleReference.java +++ b/pmd/src/net/sourceforge/pmd/lang/rule/RuleReference.java @@ -23,6 +23,7 @@ public class RuleReference extends AbstractDelegateRule { private Language language; private LanguageVersion minimumLanguageVersion; private LanguageVersion maximumLanguageVersion; + private Boolean deprecated; private String name; private Properties properties; private String message; @@ -71,6 +72,22 @@ public class RuleReference extends AbstractDelegateRule { } } + public Boolean isOverriddenDeprecated() { + return deprecated; + } + + @Override + public boolean isDeprecated() { + return deprecated != null && deprecated.booleanValue(); + } + + @Override + public void setDeprecated(boolean deprecated) { + // Deprecation does not propagate to the underlying Rule. It is the + // Rule reference itself which is being deprecated. + this.deprecated = deprecated ? deprecated : null; + } + public String getOverriddenName() { return name; } @@ -199,8 +216,8 @@ public class RuleReference extends AbstractDelegateRule { return StringUtil.isSame(s1, s2, true, false, true); } - private static boolean isSame(Enum e1, Enum e2) { - return e1 == e2 || (e1 != null && e2 != null && e1.equals(e2)); + private static boolean isSame(Object o1, Object o2) { + return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2)); } private static boolean contains(Collection collection, String s1) {