-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'issue-1360-ruleset-compatibility' into pmd/5.4.x
- Loading branch information
Showing
5 changed files
with
370 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
190 changes: 190 additions & 0 deletions
190
pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactoryCompatibility.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} |
139 changes: 139 additions & 0 deletions
139
pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryCompatibilityTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.