Skip to content

Commit ca3a945

Browse files
authored
Add PMD rules for default locale, charset and time zone (#499)
* Upgrade PMD to 7.18.0 and add RelianceOnDefaultCharset rule * Create and include new custom PMD rules to manage default local and time zone * Adjust test according to changed XML schema: openhab/openhab-website#545 * Replace deprecated PMD rules Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
1 parent 31e0bd6 commit ca3a945

14 files changed

Lines changed: 630 additions & 6 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2010-2025 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.tools.analysis.pmd;
14+
15+
import java.util.List;
16+
17+
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
18+
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
19+
import net.sourceforge.pmd.lang.java.ast.JavaNode;
20+
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
21+
import net.sourceforge.pmd.lang.java.types.InvocationMatcher;
22+
23+
/**
24+
* Checks if one of the JDK provided methods which implicitly use the default locale is used.
25+
* <p>
26+
* Not all methods are covered, only public methods that can be called directly. Complex
27+
* call hierarchies that end up calling a private or protected method won't be caught. Classes
28+
* belonging to "irrelevant things" like AWT or Swing are excluded.
29+
*
30+
* @author Ravi Nadahar - Initial contribution
31+
*/
32+
public class ImplicitDefaultLocaleRule extends AbstractJavaRulechainRule {
33+
34+
private static final List<InvocationMatcher> METHODS = List.of( //
35+
InvocationMatcher.parse("java.io.OutputStreamWriter#new(java.io.OutputStream)"), //
36+
InvocationMatcher.parse("java.io.PrintStream#format(java.lang.String,java.lang.Object[])"), //
37+
InvocationMatcher.parse("java.io.PrintWriter#format(java.lang.String,java.lang.Object[])"), //
38+
InvocationMatcher.parse("java.lang.String#toLowerCase()"), //
39+
InvocationMatcher.parse("java.lang.String#toUpperCase()"), //
40+
InvocationMatcher.parse("java.text.BreakIterator#getWordInstance()"), //
41+
InvocationMatcher.parse("java.text.BreakIterator#getCharacterInstance()"), //
42+
InvocationMatcher.parse("java.text.BreakIterator#getSentenceInstance()"), //
43+
InvocationMatcher.parse("java.text.BreakIterator#getLineInstance()"), //
44+
InvocationMatcher.parse("java.text.Collator#getInstance()"), //
45+
InvocationMatcher.parse("java.text.DateFormat#getTimeInstance()"), //
46+
InvocationMatcher.parse("java.text.DateFormat#getTimeInstance(int)"), //
47+
InvocationMatcher.parse("java.text.DateFormatSymbols#new()"), //
48+
InvocationMatcher.parse("java.text.DateFormatSymbols#getInstance()"), //
49+
InvocationMatcher.parse("java.text.DecimalFormat#new()"), //
50+
InvocationMatcher.parse("java.text.DecimalFormat#new(java.lang.String)"), //
51+
InvocationMatcher.parse("java.text.DecimalFormatSymbols#new()"), //
52+
InvocationMatcher.parse("java.text.DecimalFormatSymbols#getInstance()"), //
53+
InvocationMatcher.parse("java.text.MessageFormat#new(java.lang.String)"), //
54+
InvocationMatcher.parse("java.text.NumberFormat#getInstance()"), //
55+
InvocationMatcher.parse("java.text.NumberFormat#getNumberInstance()"), //
56+
InvocationMatcher.parse("java.text.NumberFormat#getIntegerInstance()"), //
57+
InvocationMatcher.parse("java.text.NumberFormat#getCurrencyInstance()"), //
58+
InvocationMatcher.parse("java.text.NumberFormat#getPercentInstance()"), //
59+
InvocationMatcher.parse("java.text.NumberFormat#getScientificInstance()"), //
60+
InvocationMatcher.parse("java.text.SimpleDateFormat#new()"), //
61+
InvocationMatcher.parse("java.text.SimpleDateFormat#new(java.lang.String)"), //
62+
InvocationMatcher.parse("java.text.SimpleDateFormat#new(java.lang.String,java.text.DateFormatSymbols)"), //
63+
InvocationMatcher.parse("java.time.format.DateTimeFormatterBuilder#toFormatter()"), //
64+
InvocationMatcher.parse("java.util.Calendar#new()"), //
65+
InvocationMatcher.parse("java.util.Calendar#getInstance()"), //
66+
InvocationMatcher.parse("java.util.Calendar#getInstance(java.util.TimeZone)"), //
67+
InvocationMatcher.parse("java.util.Currency#getSymbol()"), //
68+
InvocationMatcher.parse("java.util.Currency#getDisplayName()"), //
69+
InvocationMatcher.parse("java.util.Formatter#new()"), //
70+
InvocationMatcher.parse("java.util.Formatter#new(java.lang.Appendable)"), //
71+
InvocationMatcher.parse("java.util.Formatter#new(java.lang.String)"), //
72+
InvocationMatcher.parse("java.util.Formatter#new(java.lang.String,java.lang.String)"), //
73+
InvocationMatcher.parse("java.util.Formatter#new(java.io.File)"), //
74+
InvocationMatcher.parse("java.util.Formatter#new(java.io.File,java.lang.String)"), //
75+
InvocationMatcher.parse("java.util.Formatter#new(java.io.PrintStream)"), //
76+
InvocationMatcher.parse("java.util.Formatter#new(java.io.OutputStream)"), //
77+
InvocationMatcher.parse("java.util.Formatter#new(java.io.OutputStream,java.lang.String)"), //
78+
InvocationMatcher.parse("java.util.GregorianCalendar#new()"), //
79+
InvocationMatcher.parse("java.util.GregorianCalendar#new(java.util.TimeZone)"), //
80+
InvocationMatcher.parse("java.util.ResourceBundle#getBundle(java.lang.String)"), //
81+
InvocationMatcher
82+
.parse("java.util.ResourceBundle#getBundle(java.lang.String,java.util.ResourceBundle.Control)"), //
83+
InvocationMatcher.parse("java.util.ResourceBundle#getBundle(java.lang.String,java.lang.Module)"), //
84+
InvocationMatcher.parse("java.util.TimeZone#getDisplayName()"), //
85+
InvocationMatcher.parse("java.util.TimeZone#getDisplayName(boolean,int)"), //
86+
InvocationMatcher.parse("javax.accessibility.AccessibleBundle#toDisplayString()"), //
87+
InvocationMatcher.parse("javax.xml.datatype.XMLGregorianCalendar#toGregorianCalendar()") //
88+
);
89+
90+
public ImplicitDefaultLocaleRule() {
91+
super(ASTConstructorCall.class, ASTMethodCall.class);
92+
}
93+
94+
@Override
95+
public String getDescription() {
96+
return "Methods that implicitly use the default Locale can lead to bugs. Use an overloaded version with an explicit Locale, and specify 'Locale.getDefault()' if the default Locale is desired.";
97+
}
98+
99+
@Override
100+
public Object visit(ASTConstructorCall node, Object data) {
101+
checkInvocation(node, data);
102+
return data;
103+
}
104+
105+
@Override
106+
public Object visit(ASTMethodCall node, Object data) {
107+
checkInvocation(node, data);
108+
return data;
109+
}
110+
111+
private void checkInvocation(JavaNode node, Object data) {
112+
for (InvocationMatcher matcher : METHODS) {
113+
if (matcher.matchesCall(node)) {
114+
asCtx(data).addViolationWithPosition(node, node.getBeginLine(), node.getEndLine(),
115+
"Avoid method with implicit default Locale: {0}", node.getText());
116+
}
117+
}
118+
}
119+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright (c) 2010-2025 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.tools.analysis.pmd;
14+
15+
import java.util.List;
16+
17+
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
18+
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
19+
import net.sourceforge.pmd.lang.java.ast.JavaNode;
20+
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
21+
import net.sourceforge.pmd.lang.java.types.InvocationMatcher;
22+
23+
/**
24+
* Checks if one of the JDK provided methods which implicitly use the default time zone is used.
25+
* <p>
26+
* Not all methods are covered, only public methods that can be called directly. Complex
27+
* call hierarchies that end up calling a private or protected method won't be caught. Classes
28+
* belonging to "irrelevant" things like AWT or Swing are excluded.
29+
*
30+
* @author Ravi Nadahar - Initial contribution
31+
*/
32+
public class ImplicitDefaultTimeZoneRule extends AbstractJavaRulechainRule {
33+
34+
private static final List<InvocationMatcher> METHODS = List.of( //
35+
InvocationMatcher.parse("java.util.Calendar#new()"), //
36+
InvocationMatcher.parse("java.util.Calendar#getInstance()"), //
37+
InvocationMatcher.parse("java.util.Calendar#getInstance(java.util.Locale)"), //
38+
InvocationMatcher.parse("javax.xml.datatype.XMLGregorianCalendar#toGregorianCalendar()"), //
39+
InvocationMatcher.parse("java.time.chrono.Chronology#dateNow()"), //
40+
InvocationMatcher.parse("java.time.chrono.HijrahChronology#dateNow()"), //
41+
InvocationMatcher.parse("java.time.chrono.IsoChronology#dateNow()"), //
42+
InvocationMatcher.parse("java.time.chrono.JapaneseChronology#dateNow()"), //
43+
InvocationMatcher.parse("java.time.chrono.MinguoChronology#dateNow()"), //
44+
InvocationMatcher.parse("java.time.chrono.ThaiBuddhistChronology#dateNow()"), //
45+
InvocationMatcher.parse("java.time.chrono.HijrahDate#now()"), //
46+
InvocationMatcher.parse("java.time.chrono.JapaneseDate#now()"), //
47+
InvocationMatcher.parse("java.time.chrono.MinguoDate#now()"), //
48+
InvocationMatcher.parse("java.time.chrono.ThaiBuddhistDate#now()"), //
49+
InvocationMatcher.parse("java.time.LocalDate#now()"), //
50+
InvocationMatcher.parse("java.time.LocalDateTime#now()"), //
51+
InvocationMatcher.parse("java.time.LocalTime#now()"), //
52+
InvocationMatcher.parse("java.time.MonthDay#now()"), //
53+
InvocationMatcher.parse("java.time.OffsetDateTime#now()"), //
54+
InvocationMatcher.parse("java.time.OffsetTime#now()"), //
55+
InvocationMatcher.parse("java.time.Year#now()"), //
56+
InvocationMatcher.parse("java.time.YearMonth#now()"), //
57+
InvocationMatcher.parse("java.time.ZonedDateTime#now()") //
58+
);
59+
60+
public ImplicitDefaultTimeZoneRule() {
61+
super(ASTConstructorCall.class, ASTMethodCall.class);
62+
}
63+
64+
@Override
65+
public String getDescription() {
66+
return "Methods that implicitly use the default time zone can lead to bugs. Use an overloaded version with an explicit time zone.";
67+
}
68+
69+
@Override
70+
public Object visit(ASTConstructorCall node, Object data) {
71+
checkInvocation(node, data);
72+
return data;
73+
}
74+
75+
@Override
76+
public Object visit(ASTMethodCall node, Object data) {
77+
checkInvocation(node, data);
78+
return data;
79+
}
80+
81+
private void checkInvocation(JavaNode node, Object data) {
82+
for (InvocationMatcher matcher : METHODS) {
83+
if (matcher.matchesCall(node)) {
84+
asCtx(data).addViolationWithPosition(node, node.getBeginLine(), node.getEndLine(),
85+
"Avoid method with implicit default time zone: {0}", node.getText());
86+
}
87+
}
88+
}
89+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2010-2025 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.tools.analysis.pmd;
14+
15+
import java.util.List;
16+
17+
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
18+
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
19+
import net.sourceforge.pmd.lang.java.ast.JavaNode;
20+
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
21+
import net.sourceforge.pmd.lang.java.types.InvocationMatcher;
22+
23+
/**
24+
* Checks if the default locale is being set.
25+
*
26+
* @author Ravi Nadahar - Initial contribution
27+
*/
28+
public class SetDefaultLocaleRule extends AbstractJavaRulechainRule {
29+
30+
private static final List<InvocationMatcher> METHODS = List.of( //
31+
InvocationMatcher.parse("java.util.Locale#setDefault(java.util.Locale)"), //
32+
InvocationMatcher.parse("java.util.Locale#setDefault(java.util.Locale.Category,java.util.Locale)") //
33+
);
34+
35+
public SetDefaultLocaleRule() {
36+
super(ASTConstructorCall.class, ASTMethodCall.class);
37+
}
38+
39+
@Override
40+
public String getDescription() {
41+
return "Applications should not set the default Locale since it affects the whole JVM.";
42+
}
43+
44+
@Override
45+
public Object visit(ASTConstructorCall node, Object data) {
46+
checkInvocation(node, data);
47+
return data;
48+
}
49+
50+
@Override
51+
public Object visit(ASTMethodCall node, Object data) {
52+
checkInvocation(node, data);
53+
return data;
54+
}
55+
56+
private void checkInvocation(JavaNode node, Object data) {
57+
for (InvocationMatcher matcher : METHODS) {
58+
if (matcher.matchesCall(node)) {
59+
asCtx(data).addViolationWithPosition(node, node.getBeginLine(), node.getEndLine(),
60+
"Avoid setting the default Locale: {0}", node.getText());
61+
}
62+
}
63+
}
64+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (c) 2010-2025 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.tools.analysis.pmd;
14+
15+
import java.util.List;
16+
17+
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
18+
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
19+
import net.sourceforge.pmd.lang.java.ast.JavaNode;
20+
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
21+
import net.sourceforge.pmd.lang.java.types.InvocationMatcher;
22+
23+
/**
24+
* Checks if the default time zone is being set.
25+
*
26+
* @author Ravi Nadahar - Initial contribution
27+
*/
28+
public class SetDefaultTimeZoneRule extends AbstractJavaRulechainRule {
29+
30+
private static final List<InvocationMatcher> METHODS = List.of( //
31+
InvocationMatcher.parse("java.util.TimeZone#setDefault(java.util.TimeZone)") //
32+
);
33+
34+
public SetDefaultTimeZoneRule() {
35+
super(ASTConstructorCall.class, ASTMethodCall.class);
36+
}
37+
38+
@Override
39+
public String getDescription() {
40+
return "Applications should not set the default time zone since it affects the whole JVM.";
41+
}
42+
43+
@Override
44+
public Object visit(ASTConstructorCall node, Object data) {
45+
checkInvocation(node, data);
46+
return data;
47+
}
48+
49+
@Override
50+
public Object visit(ASTMethodCall node, Object data) {
51+
checkInvocation(node, data);
52+
return data;
53+
}
54+
55+
private void checkInvocation(JavaNode node, Object data) {
56+
for (InvocationMatcher matcher : METHODS) {
57+
if (matcher.matchesCall(node)) {
58+
asCtx(data).addViolationWithPosition(node, node.getBeginLine(), node.getEndLine(),
59+
"Avoid setting the default time zone: {0}", node.getText());
60+
}
61+
}
62+
}
63+
}

custom-checks/pmd/src/test/java/org/openhab/tools/analysis/pmd/test/CustomRulesTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,9 @@ public class CustomRulesTest extends SimpleAggregatorTst {
2626
@BeforeEach
2727
public void setUp() {
2828
addRule("pmd/ruleset/customrules.xml", "UseSLF4JLoggerRule");
29+
addRule("pmd/ruleset/customrules.xml", "ImplicitDefaultLocale");
30+
addRule("pmd/ruleset/customrules.xml", "ImplicitDefaultTimeZone");
31+
addRule("pmd/ruleset/customrules.xml", "SetDefaultLocale");
32+
addRule("pmd/ruleset/customrules.xml", "SetDefaultTimeZone");
2933
}
3034
}

0 commit comments

Comments
 (0)