forked from phoedos/pmd
Merge branch 'issue-1360-ruleset-compatibility' into pmd/5.4.x
This commit is contained in:
@ -34,6 +34,7 @@ import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
@ -41,6 +42,7 @@ import org.xml.sax.SAXException;
|
||||
* content. By default Rules will be loaded using the ClassLoader for this
|
||||
* class, using the {@link RulePriority#LOW} priority, with Rule deprecation
|
||||
* warnings off.
|
||||
* By default, the ruleset compatibility filter is active, too. See {@link RuleSetFactoryCompatibility}.
|
||||
*/
|
||||
public class RuleSetFactory {
|
||||
|
||||
@ -49,6 +51,7 @@ public class RuleSetFactory {
|
||||
private ClassLoader classLoader = RuleSetFactory.class.getClassLoader();
|
||||
private RulePriority minimumPriority = RulePriority.LOW;
|
||||
private boolean warnDeprecated = false;
|
||||
private RuleSetFactoryCompatibility compatibilityFilter = new RuleSetFactoryCompatibility();
|
||||
|
||||
/**
|
||||
* Set the ClassLoader to use when loading Rules.
|
||||
@ -79,6 +82,22 @@ public class RuleSetFactory {
|
||||
this.warnDeprecated = warnDeprecated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the ruleset compatibility filter. Disabling this filter will cause
|
||||
* exception when loading a ruleset, which uses references to old/not existing rules.
|
||||
*/
|
||||
public void disableCompatibilityFilter() {
|
||||
compatibilityFilter = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compatibility filter in order to adjust it, e.g. add additional filters.
|
||||
* @return the {@link RuleSetFactoryCompatibility}
|
||||
*/
|
||||
public RuleSetFactoryCompatibility getCompatibilityFilter() {
|
||||
return compatibilityFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Iterator of RuleSet objects loaded from descriptions from the
|
||||
* "rulesets.properties" resource for each Language with Rule support.
|
||||
@ -220,7 +239,13 @@ public class RuleSetFactory {
|
||||
}
|
||||
try {
|
||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
Document document = builder.parse(inputStream);
|
||||
InputSource inputSource;
|
||||
if (compatibilityFilter != null) {
|
||||
inputSource = new InputSource(compatibilityFilter.filterRuleSetFile(inputStream));
|
||||
} else {
|
||||
inputSource = new InputSource(inputStream);
|
||||
}
|
||||
Document document = builder.parse(inputSource);
|
||||
Element ruleSetElement = document.getDocumentElement();
|
||||
|
||||
RuleSet ruleSet = new RuleSet();
|
||||
|
@ -0,0 +1,190 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
package net.sourceforge.pmd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
/**
|
||||
* Provides a simple filter mechanism to avoid failing to parse an old ruleset, which references rules, that
|
||||
* have either been removed from PMD already or renamed or moved to another ruleset.
|
||||
*
|
||||
* @see <a href="https://sourceforge.net/p/pmd/bugs/1360/">issue 1360</a>
|
||||
*/
|
||||
public class RuleSetFactoryCompatibility {
|
||||
private static final Logger LOG = Logger.getLogger(RuleSetFactoryCompatibility.class.getName());
|
||||
|
||||
private List<RuleSetFilter> filters = new LinkedList<RuleSetFilter>();
|
||||
|
||||
/**
|
||||
* Creates a new instance of the compatibility filter with the built-in filters for the
|
||||
* modified PMD rules.
|
||||
*/
|
||||
public RuleSetFactoryCompatibility() {
|
||||
// PMD 5.3.0
|
||||
addFilterRuleRenamed("java", "design", "UncommentedEmptyMethod", "UncommentedEmptyMethodBody");
|
||||
addFilterRuleRemoved("java", "controversial", "BooleanInversion");
|
||||
|
||||
// PMD 5.3.1
|
||||
addFilterRuleRenamed("java", "design", "UseSingleton", "UseUtilityClass");
|
||||
|
||||
// PMD 5.4.0
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyCatchBlock");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyIfStatement");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyWhileStmt");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyTryBlock");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyFinallyBlock");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptySwitchStatements");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptySynchronizedBlock");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyStatementNotInLoop");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyInitializer");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyStatementBlock");
|
||||
addFilterRuleMoved("java", "basic", "empty", "EmptyStaticInitializer");
|
||||
addFilterRuleMoved("java", "basic", "unnecessary", "UnnecessaryConversionTemporary");
|
||||
addFilterRuleMoved("java", "basic", "unnecessary", "UnnecessaryReturn");
|
||||
addFilterRuleMoved("java", "basic", "unnecessary", "UnnecessaryFinalModifier");
|
||||
addFilterRuleMoved("java", "basic", "unnecessary", "UselessOverridingMethod");
|
||||
addFilterRuleMoved("java", "basic", "unnecessary", "UselessOperationOnImmutable");
|
||||
addFilterRuleMoved("java", "basic", "unnecessary", "UnusedNullCheckInEquals");
|
||||
addFilterRuleMoved("java", "basic", "unnecessary", "UselessParentheses");
|
||||
}
|
||||
|
||||
void addFilterRuleRenamed(String language, String ruleset, String oldName, String newName) {
|
||||
filters.add(RuleSetFilter.ruleRenamed(language, ruleset, oldName, newName));
|
||||
}
|
||||
void addFilterRuleMoved(String language, String oldRuleset, String newRuleset, String ruleName) {
|
||||
filters.add(RuleSetFilter.ruleMoved(language, oldRuleset, newRuleset, ruleName));
|
||||
}
|
||||
void addFilterRuleRemoved(String language, String ruleset, String name) {
|
||||
filters.add(RuleSetFilter.ruleRemoved(language, ruleset, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all configured filters against the given input stream.
|
||||
* The resulting reader will contain the original ruleset modified by
|
||||
* the filters.
|
||||
*
|
||||
* @param stream
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public Reader filterRuleSetFile(InputStream stream) throws IOException {
|
||||
byte[] bytes = IOUtils.toByteArray(stream);
|
||||
String encoding = determineEncoding(bytes);
|
||||
String ruleset = new String(bytes, encoding);
|
||||
|
||||
ruleset = applyAllFilters(ruleset);
|
||||
|
||||
return new StringReader(ruleset);
|
||||
}
|
||||
|
||||
private String applyAllFilters(String in) {
|
||||
String result = in;
|
||||
for (RuleSetFilter filter : filters) {
|
||||
result = filter.apply(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final Pattern ENCODING_PATTERN = Pattern.compile("encoding=\"([^\"]+)\"");
|
||||
/**
|
||||
* Determines the encoding of the given bytes, assuming this is a XML document, which specifies
|
||||
* the encoding in the first 1024 bytes.
|
||||
*
|
||||
* @param bytes the input bytes, might be more or less than 1024 bytes
|
||||
* @return the determined encoding, falls back to the default UTF-8 encoding
|
||||
*/
|
||||
String determineEncoding(byte[] bytes) {
|
||||
String firstBytes = new String(bytes, 0, bytes.length > 1024 ? 1024 : bytes.length, Charset.forName("ISO-8859-1"));
|
||||
Matcher matcher = ENCODING_PATTERN.matcher(firstBytes);
|
||||
String encoding = Charset.forName("UTF-8").name();
|
||||
if (matcher.find()) {
|
||||
encoding = matcher.group(1);
|
||||
}
|
||||
return encoding;
|
||||
}
|
||||
|
||||
private static class RuleSetFilter {
|
||||
private final Pattern refPattern;
|
||||
private final String replacement;
|
||||
private Pattern exclusionPattern;
|
||||
private String exclusionReplacement;
|
||||
private final String logMessage;
|
||||
private RuleSetFilter(String refPattern, String replacement, String logMessage) {
|
||||
this.logMessage = logMessage;
|
||||
if (replacement != null) {
|
||||
this.refPattern = Pattern.compile("ref=\"" + Pattern.quote(refPattern) + "\"");
|
||||
this.replacement = "ref=\"" + replacement + "\"";
|
||||
} else {
|
||||
this.refPattern = Pattern.compile("<rule\\s+ref=\"" + Pattern.quote(refPattern) + "\"\\s*/>");
|
||||
this.replacement = "";
|
||||
}
|
||||
}
|
||||
|
||||
private void setExclusionPattern(String oldName, String newName) {
|
||||
exclusionPattern = Pattern.compile("<exclude\\s+name=[\"']" + Pattern.quote(oldName) + "[\"']\\s*/>");
|
||||
if (newName != null) {
|
||||
exclusionReplacement = "<exclude name=\"" + newName + "\" />";
|
||||
} else {
|
||||
exclusionReplacement = "";
|
||||
}
|
||||
}
|
||||
|
||||
public static RuleSetFilter ruleRenamed(String language, String ruleset, String oldName, String newName) {
|
||||
String base = "rulesets/" + language + "/" + ruleset + ".xml/";
|
||||
RuleSetFilter filter = new RuleSetFilter(base + oldName, base + newName,
|
||||
"The rule \"" + oldName + "\" has been renamed to \"" + newName + "\". Please change your ruleset!");
|
||||
filter.setExclusionPattern(oldName, newName);
|
||||
return filter;
|
||||
}
|
||||
public static RuleSetFilter ruleMoved(String language, String oldRuleset, String newRuleset, String ruleName) {
|
||||
String base = "rulesets/" + language + "/";
|
||||
return new RuleSetFilter(base + oldRuleset + ".xml/" + ruleName, base + newRuleset + ".xml/" + ruleName,
|
||||
"The rule \"" + ruleName + "\" has been moved from ruleset \"" + oldRuleset + "\" to \"" + newRuleset + "\". Please change your ruleset!");
|
||||
}
|
||||
public static RuleSetFilter ruleRemoved(String language, String ruleset, String name) {
|
||||
RuleSetFilter filter = new RuleSetFilter("rulesets/" + language + "/" + ruleset + ".xml/" + name, null,
|
||||
"The rule \"" + name + "\" in ruleset \"" + ruleset + "\" has been removed from PMD and no longer exists. Please change your ruleset!");
|
||||
filter.setExclusionPattern(name, null);
|
||||
return filter;
|
||||
}
|
||||
|
||||
String apply(String in) {
|
||||
String result = in;
|
||||
Matcher matcher = refPattern.matcher(in);
|
||||
|
||||
if (matcher.find()) {
|
||||
result = matcher.replaceAll(replacement);
|
||||
|
||||
if (LOG.isLoggable(Level.WARNING)) {
|
||||
LOG.warning("Applying rule set filter: " + logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (exclusionPattern == null) return result;
|
||||
|
||||
Matcher exclusions = exclusionPattern.matcher(result);
|
||||
if (exclusions.find()) {
|
||||
result = exclusions.replaceAll(exclusionReplacement);
|
||||
|
||||
if (LOG.isLoggable(Level.WARNING)) {
|
||||
LOG.warning("Applying rule set filter for exclusions: " + logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
package net.sourceforge.pmd;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RuleSetFactoryCompatibilityTest {
|
||||
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
@Test
|
||||
public void testCorrectOldReference() throws Exception {
|
||||
final String ruleset = "<?xml version=\"1.0\"?>\n" +
|
||||
"\n" +
|
||||
"<ruleset name=\"Test\"\n" +
|
||||
" xmlns=\"http://pmd.sourceforge.net/ruleset/2.0.0\"\n" +
|
||||
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
|
||||
" xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd\">\n" +
|
||||
" <description>Test</description>\n" +
|
||||
"\n" +
|
||||
" <rule ref=\"rulesets/dummy/notexisting.xml/DummyBasicMockRule\" />\n" +
|
||||
"</ruleset>\n";
|
||||
|
||||
RuleSetFactory factory = new RuleSetFactory();
|
||||
factory.getCompatibilityFilter().addFilterRuleMoved("dummy", "notexisting", "basic", "DummyBasicMockRule");
|
||||
|
||||
RuleSet createdRuleSet = createRulesetFromString(ruleset, factory);
|
||||
Assert.assertNotNull(createdRuleSet.getRuleByName("DummyBasicMockRule"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExclusion() throws Exception {
|
||||
final String ruleset = "<?xml version=\"1.0\"?>\n" +
|
||||
"\n" +
|
||||
"<ruleset name=\"Test\"\n" +
|
||||
" xmlns=\"http://pmd.sourceforge.net/ruleset/2.0.0\"\n" +
|
||||
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
|
||||
" xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd\">\n" +
|
||||
" <description>Test</description>\n" +
|
||||
"\n" +
|
||||
" <rule ref=\"rulesets/dummy/basic.xml\">\n" +
|
||||
" <exclude name=\"OldNameOfSampleXPathRule\"/>\n" +
|
||||
" </rule>\n" +
|
||||
"</ruleset>\n";
|
||||
|
||||
RuleSetFactory factory = new RuleSetFactory();
|
||||
factory.getCompatibilityFilter().addFilterRuleRenamed("dummy", "basic", "OldNameOfSampleXPathRule", "SampleXPathRule");
|
||||
|
||||
RuleSet createdRuleSet = createRulesetFromString(ruleset, factory);
|
||||
Assert.assertNotNull(createdRuleSet.getRuleByName("DummyBasicMockRule"));
|
||||
Assert.assertNull(createdRuleSet.getRuleByName("SampleXPathRule"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilter() throws Exception {
|
||||
RuleSetFactoryCompatibility rsfc = new RuleSetFactoryCompatibility();
|
||||
rsfc.addFilterRuleMoved("dummy", "notexisting", "basic", "DummyBasicMockRule");
|
||||
rsfc.addFilterRuleRemoved("dummy", "basic", "DeletedRule");
|
||||
rsfc.addFilterRuleRenamed("dummy", "basic", "OldNameOfBasicMockRule", "NewNameOfBasicMockRule");
|
||||
|
||||
String in = "<?xml version=\"1.0\"?>\n" +
|
||||
"\n" +
|
||||
"<ruleset name=\"Test\"\n" +
|
||||
" xmlns=\"http://pmd.sourceforge.net/ruleset/2.0.0\"\n" +
|
||||
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
|
||||
" xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd\">\n" +
|
||||
" <description>Test</description>\n" +
|
||||
"\n" +
|
||||
" <rule ref=\"rulesets/dummy/notexisting.xml/DummyBasicMockRule\" />\n" +
|
||||
" <rule ref=\"rulesets/dummy/basic.xml/DeletedRule\" />\n" +
|
||||
" <rule ref=\"rulesets/dummy/basic.xml/OldNameOfBasicMockRule\" />\n" +
|
||||
"</ruleset>\n";
|
||||
InputStream stream = new ByteArrayInputStream(in.getBytes(ISO_8859_1));
|
||||
Reader filtered = rsfc.filterRuleSetFile(stream);
|
||||
String out = IOUtils.toString(filtered);
|
||||
|
||||
Assert.assertFalse(out.contains("notexisting.xml"));
|
||||
Assert.assertTrue(out.contains("<rule ref=\"rulesets/dummy/basic.xml/DummyBasicMockRule\" />"));
|
||||
|
||||
Assert.assertFalse(out.contains("DeletedRule"));
|
||||
|
||||
Assert.assertFalse(out.contains("OldNameOfBasicMockRule"));
|
||||
Assert.assertTrue(out.contains("<rule ref=\"rulesets/dummy/basic.xml/NewNameOfBasicMockRule\" />"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExclusionFilter() throws Exception {
|
||||
RuleSetFactoryCompatibility rsfc = new RuleSetFactoryCompatibility();
|
||||
rsfc.addFilterRuleRenamed("dummy", "basic", "AnotherOldNameOfBasicMockRule", "NewNameOfBasicMockRule");
|
||||
|
||||
String in = "<?xml version=\"1.0\"?>\n" +
|
||||
"\n" +
|
||||
"<ruleset name=\"Test\"\n" +
|
||||
" xmlns=\"http://pmd.sourceforge.net/ruleset/2.0.0\"\n" +
|
||||
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
|
||||
" xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd\">\n" +
|
||||
" <description>Test</description>\n" +
|
||||
"\n" +
|
||||
" <rule ref=\"rulesets/dummy/basic.xml\">\n" +
|
||||
" <exclude name=\"AnotherOldNameOfBasicMockRule\"/>\n" +
|
||||
" </rule>\n" +
|
||||
"</ruleset>\n";
|
||||
InputStream stream = new ByteArrayInputStream(in.getBytes(ISO_8859_1));
|
||||
Reader filtered = rsfc.filterRuleSetFile(stream);
|
||||
String out = IOUtils.toString(filtered);
|
||||
|
||||
Assert.assertFalse(out.contains("OldNameOfBasicMockRule"));
|
||||
Assert.assertTrue(out.contains("<exclude name=\"NewNameOfBasicMockRule\" />"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncoding() {
|
||||
RuleSetFactoryCompatibility rsfc = new RuleSetFactoryCompatibility();
|
||||
String testString;
|
||||
|
||||
testString = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><x></x>";
|
||||
Assert.assertEquals("ISO-8859-1", rsfc.determineEncoding(testString.getBytes(ISO_8859_1)));
|
||||
|
||||
testString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><x></x>";
|
||||
Assert.assertEquals("UTF-8", rsfc.determineEncoding(testString.getBytes(ISO_8859_1)));
|
||||
}
|
||||
|
||||
private RuleSet createRulesetFromString(final String ruleset, RuleSetFactory factory)
|
||||
throws RuleSetNotFoundException {
|
||||
return factory.createRuleSet(new RuleSetReferenceId(null) {
|
||||
@Override
|
||||
public InputStream getInputStream(ClassLoader classLoader) throws RuleSetNotFoundException {
|
||||
return new ByteArrayInputStream(ruleset.getBytes(UTF_8));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -19,4 +19,18 @@ Just for test
|
||||
</rule>
|
||||
|
||||
<rule deprecated="true" name="OldNameOfDummyBasicMockRule" ref="DummyBasicMockRule"/>
|
||||
|
||||
<rule name="SampleXPathRule" language="dummy" since="1.1" message="Test Rule 2" class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/rules/dummy/basic.xml#SampleXPathRule">
|
||||
<description>Test</description>
|
||||
<priority>3</priority>
|
||||
<example> </example>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value><![CDATA[
|
||||
//DummyNode
|
||||
]]></value>
|
||||
</property>
|
||||
</properties>
|
||||
</rule>"
|
||||
</ruleset>
|
@ -9,6 +9,7 @@
|
||||
**Feature Request and Improvements:**
|
||||
|
||||
* A JSON-renderer for PMD which is compatible with CodeClimate. See [PR#83](https://github.com/pmd/pmd/pull/83).
|
||||
* [#1360](https://sourceforge.net/p/pmd/bugs/1360/): Provide backwards compatibility for PMD configuration file
|
||||
|
||||
**New/Modified/Deprecated Rules:**
|
||||
|
||||
|
Reference in New Issue
Block a user