Factorized enumerated property

This commit is contained in:
oowekyala
2017-07-25 11:27:55 +02:00
parent 9143630351
commit edf8ce0732
10 changed files with 277 additions and 144 deletions

View File

@ -8,7 +8,10 @@ import static net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery.XPATH_1_0;
import static net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery.XPATH_1_0_COMPATIBILITY;
import static net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery.XPATH_2_0;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.PropertySource;
import net.sourceforge.pmd.RuleContext;
@ -23,15 +26,27 @@ import net.sourceforge.pmd.util.StringUtil;
/**
* Rule that tries to match an XPath expression against a DOM view of an AST.
*
* This rule needs a "xpath" property value in order to function.
* <p>This rule needs a "xpath" property value in order to function.
*/
public class XPathRule extends AbstractRule {
public static final StringProperty XPATH_DESCRIPTOR = new StringProperty("xpath", "XPath expression", "", 1.0f);
private static final Map<String, String> XPATH_VERSIONS;
static {
Map<String, String> tmp = new HashMap<>();
tmp.put(XPATH_1_0, XPATH_1_0);
tmp.put(XPATH_1_0_COMPATIBILITY, XPATH_1_0_COMPATIBILITY);
tmp.put(XPATH_2_0, XPATH_2_0);
XPATH_VERSIONS = Collections.unmodifiableMap(tmp);
}
public static final EnumeratedProperty<String> VERSION_DESCRIPTOR
= new EnumeratedProperty<>("version",
"XPath specification version", new String[] {XPATH_1_0, XPATH_1_0_COMPATIBILITY, XPATH_2_0},
new String[] {XPATH_1_0, XPATH_1_0_COMPATIBILITY, XPATH_2_0}, 0, String.class, 2.0f);
"XPath specification version", XPATH_VERSIONS, XPATH_1_0, String.class, 2.0f);
private XPathRuleQuery xpathRuleQuery;
@ -96,7 +111,7 @@ public class XPathRule extends AbstractRule {
private boolean init() {
if (xpathRuleQuery == null) {
String xpath = getProperty(XPATH_DESCRIPTOR);
String version = (String) getProperty(VERSION_DESCRIPTOR);
String version = getProperty(VERSION_DESCRIPTOR);
if (XPATH_1_0.equals(version)) {
xpathRuleQuery = new JaxenXPathRuleQuery();
} else {

View File

@ -5,13 +5,13 @@
package net.sourceforge.pmd.lang.rule.properties;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.EnumeratedPropertyDescriptor;
import net.sourceforge.pmd.PropertyDescriptorFactory;
import net.sourceforge.pmd.PropertyDescriptorField;
import net.sourceforge.pmd.lang.rule.properties.modules.EnumeratedPropertyModule;
import net.sourceforge.pmd.util.CollectionUtil;
/**
@ -21,6 +21,7 @@ import net.sourceforge.pmd.util.CollectionUtil;
* @param <E> The type of the values
*
* @author Brian Remedios
* @author Clément Fournier
* @version Refactored June 2017 (6.0.0)
*/
public final class EnumeratedMultiProperty<E> extends AbstractMultiValueProperty<E>
@ -42,22 +43,25 @@ public final class EnumeratedMultiProperty<E> extends AbstractMultiValueProperty
}
}; // @formatter:on
private final Map<String, E> choicesByLabel;
private final Map<E, String> labelsByChoice;
private final Class<E> valueType;
private final EnumeratedPropertyModule<E> module;
/**
* Constructor using arrays to define the label-value mappings. The correct construction of the property depends
* on the correct ordering of the arrays.
* Constructor using arrays to define the label-value mappings. The correct construction of the property depends on
* the correct ordering of the arrays.
*
* @param theName Name
* @param theDescription Description
* @param theLabels Labels of the choices
* @param theChoices Values that can be chosen
* @param choiceIndices The indices of the default values.
* @param choiceIndices Indices of the default values
* @param valueType Type of the values
* @param theUIOrder UI order
*
* @deprecated Use {@link #EnumeratedMultiProperty(String, String, Map, List, Class, float)}. Will be removed in
* 7.0.0
*/
@Deprecated
public EnumeratedMultiProperty(String theName, String theDescription, String[] theLabels, E[] theChoices,
int[] choiceIndices, Class<E> valueType, float theUIOrder) {
this(theName, theDescription, CollectionUtil.mapFrom(theLabels, theChoices),
@ -65,16 +69,72 @@ public final class EnumeratedMultiProperty<E> extends AbstractMultiValueProperty
}
/**
* Constructor using a map to define the label-value mappings. The default values are specified with a list.
*
* @param theName Name
* @param theDescription Description
* @param choices Map of labels to values
* @param defaultValues List of default values
* @param valueType Type of the values
* @param theUIOrder UI order
*/
public EnumeratedMultiProperty(String theName, String theDescription, Map<String, E> choices,
List<E> defaultValues, Class<E> valueType, float theUIOrder) {
this(theName, theDescription, choices, defaultValues, valueType, theUIOrder, false);
}
private EnumeratedMultiProperty(String theName, String theDescription, Map<String, E> choices,
List<E> defaultValues, Class<E> valueType, float theUIOrder,
boolean isDefinedExternally) {
super(theName, theDescription, defaultValues, theUIOrder, isDefinedExternally);
checkDefaults(defaultValues, choices);
module = new EnumeratedPropertyModule<>(choices, valueType);
checkDefaults(defaultValues);
}
this.valueType = valueType;
choicesByLabel = Collections.unmodifiableMap(choices);
labelsByChoice = Collections.unmodifiableMap(CollectionUtil.invertedMapFrom(choicesByLabel));
@Override
public Map<String, E> mappings() {
return module.getChoicesByLabel(); // unmodifiable
}
@Override
public Class<E> type() {
return module.getValueType();
}
@Override
public String errorFor(List<E> values) {
for (E value : values) {
String error = module.errorFor(value);
if (error != null) {
return error;
}
}
return null;
}
@Override
protected E createFrom(String toParse) {
return module.choiceFrom(toParse);
}
@Override
public String asString(E item) {
return module.getLabelsByChoice().get(item);
}
private void checkDefaults(List<E> defaults) {
for (E elt : defaults) {
module.checkValue(elt);
}
}
@ -89,78 +149,4 @@ public final class EnumeratedMultiProperty<E> extends AbstractMultiValueProperty
return selected;
}
private static <E> void checkDefaults(List<E> defaults, Map<String, E> choices) {
for (E elt : defaults) {
if (!choices.containsValue(elt)) {
throw new IllegalArgumentException("Invalid default value: no mapping to this value");
}
}
}
/**
* Constructor using a map to define the label-value mappings. The default values are specified with a list.
*
* @param theName Name
* @param theDescription Description
* @param choices Map of labels to values
* @param defaultValues List of default values
* @param theUIOrder UI order
*/
public EnumeratedMultiProperty(String theName, String theDescription, Map<String, E> choices,
List<E> defaultValues, Class<E> valueType, float theUIOrder) {
this(theName, theDescription, choices, defaultValues, valueType, theUIOrder, false);
}
@Override
public Map<String, E> mappings() {
return choicesByLabel; // unmodifiable
}
@Override
public Class<E> type() {
return valueType;
}
@Override
public String errorFor(List<E> values) {
for (E value : values) {
if (!labelsByChoice.containsKey(value)) {
return nonLegalValueMsgFor(value);
}
}
return null;
}
private String nonLegalValueMsgFor(E value) {
return value + " is not a legal value";
}
@Override
protected E createFrom(String toParse) {
return choiceFrom(toParse);
}
private E choiceFrom(String label) {
E result = choicesByLabel.get(label);
if (label == null || result == null) {
throw new IllegalArgumentException(label);
}
return result;
}
@Override
public String asString(E item) {
return labelsByChoice.get(item);
}
}

View File

@ -4,24 +4,25 @@
package net.sourceforge.pmd.lang.rule.properties;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import net.sourceforge.pmd.EnumeratedPropertyDescriptor;
import net.sourceforge.pmd.PropertyDescriptorFactory;
import net.sourceforge.pmd.PropertyDescriptorField;
import net.sourceforge.pmd.lang.rule.properties.modules.EnumeratedPropertyModule;
import net.sourceforge.pmd.util.CollectionUtil;
/**
* Property which can take only a fixed set of values of any type, then selected via String labels. The
* mappings method returns the set of mappings between the labels and their values.
* Property which can take only a fixed set of values of any type, then selected via String labels. The mappings method
* returns the set of mappings between the labels and their values.
*
* <p>This property currently doesn't support serialization and cannot be defined in a ruleset file.z
*
* @param <E> Type of the values
*
* @author Brian Remedios
* @author Clément Fournier
* @version Refactored June 2017 (6.0.0)
*/
public final class EnumeratedProperty<E> extends AbstractSingleValueProperty<E>
@ -46,14 +47,12 @@ public final class EnumeratedProperty<E> extends AbstractSingleValueProperty<E>
}; // @formatter:on
private final Map<String, E> choicesByLabel;
private final Map<E, String> labelsByChoice;
private final Class<E> valueType;
private final EnumeratedPropertyModule<E> module;
/**
* Constructor using arrays to define the label-value mappings. The correct construction of the property depends
* on the correct ordering of the arrays.
* Constructor using arrays to define the label-value mappings. The correct construction of the property depends on
* the correct ordering of the arrays.
*
* @param theName Name
* @param theDescription Description
@ -62,8 +61,9 @@ public final class EnumeratedProperty<E> extends AbstractSingleValueProperty<E>
* @param defaultIndex The index of the default value.
* @param theUIOrder UI order
*
* @deprecated will be removed in 7.0.0. Use a map.
* @deprecated will be removed in 7.0.0. Use {@link #EnumeratedProperty(String, String, Map, Object, Class, float)}
*/
@Deprecated
public EnumeratedProperty(String theName, String theDescription, String[] theLabels, E[] theChoices,
int defaultIndex, Class<E> valueType, float theUIOrder) {
this(theName, theDescription, CollectionUtil.mapFrom(theLabels, theChoices),
@ -75,9 +75,8 @@ public final class EnumeratedProperty<E> extends AbstractSingleValueProperty<E>
E defaultValue, Class<E> valueType, float theUIOrder, boolean isDefinedExternally) {
super(theName, theDescription, defaultValue, theUIOrder, isDefinedExternally);
this.valueType = valueType;
choicesByLabel = Collections.unmodifiableMap(labelsToChoices);
labelsByChoice = Collections.unmodifiableMap(CollectionUtil.invertedMapFrom(choicesByLabel));
module = new EnumeratedPropertyModule<>(labelsToChoices, valueType);
module.checkValue(defaultValue);
}
@ -98,45 +97,31 @@ public final class EnumeratedProperty<E> extends AbstractSingleValueProperty<E>
@Override
public Class<E> type() {
return valueType;
return module.getValueType();
}
@Override
public String errorFor(Object value) {
return labelsByChoice.containsKey(value) ? null : nonLegalValueMsgFor(value);
}
private String nonLegalValueMsgFor(Object value) {
return value + " is not a legal value";
public String errorFor(E value) {
return module.errorFor(value);
}
@Override
public E createFrom(String value) throws IllegalArgumentException {
return choiceFrom(value);
}
private E choiceFrom(String label) {
E result = choicesByLabel.get(label);
if (result != null) {
return result;
}
throw new IllegalArgumentException(label);
return module.choiceFrom(value);
}
@Override
public String asString(E value) {
return labelsByChoice.get(value);
return module.getLabelsByChoice().get(value);
}
@Override
public Map<String, E> mappings() {
return choicesByLabel; // unmodifiable
return module.getChoicesByLabel(); // unmodifiable
}

View File

@ -0,0 +1,71 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.rule.properties.modules;
import java.util.Collections;
import java.util.Map;
import net.sourceforge.pmd.util.CollectionUtil;
/**
* Factorises common functionality for enumerated properties.
*
* @author Clément Fournier
*/
public class EnumeratedPropertyModule<E> {
private final Map<String, E> choicesByLabel;
private final Map<E, String> labelsByChoice;
private final Class<E> valueType;
public EnumeratedPropertyModule(Map<String, E> choicesByLabel, Class<E> valueType) {
this.valueType = valueType;
this.choicesByLabel = Collections.unmodifiableMap(choicesByLabel);
this.labelsByChoice = Collections.unmodifiableMap(CollectionUtil.invertedMapFrom(choicesByLabel));
}
public Class<E> getValueType() {
return valueType;
}
public Map<E, String> getLabelsByChoice() {
return labelsByChoice;
}
public Map<String, E> getChoicesByLabel() {
return choicesByLabel;
}
private String nonLegalValueMsgFor(E value) {
return value + " is not a legal value";
}
public String errorFor(E value) {
return labelsByChoice.containsKey(value) ? null : nonLegalValueMsgFor(value);
}
public E choiceFrom(String label) {
E result = choicesByLabel.get(label);
if (result != null) {
return result;
}
throw new IllegalArgumentException(label);
}
public void checkValue(E value) {
if (!choicesByLabel.containsValue(value)) {
throw new IllegalArgumentException("Invalid default value: no mapping to this value");
}
}
}

View File

@ -11,6 +11,8 @@ import java.util.Map;
import net.sourceforge.pmd.util.ClassUtil;
/**
* Factorises common functionality for method properties.
*
* @author Clément Fournier
*/
public class MethodPropertyModule extends PackagedPropertyModule<Method> {
@ -45,15 +47,14 @@ public class MethodPropertyModule extends PackagedPropertyModule<Method> {
/**
* Return the value of `method' as a string that can be easily recognized
* and parsed when we see it again.
* Return the value of `method' as a string that can be easily recognized and parsed when we see it again.
*
* @param method the method to convert
*
* @return the string value
*/
private static String asStringFor(Method method) { // TODO:cf we could replace that with a QualifiedName's toString
StringBuilder sb = new StringBuilder(); // once it can parse Class and Method
private static String asStringFor(Method method) {
StringBuilder sb = new StringBuilder();
asStringOn(method, sb);
return sb.toString();
}

View File

@ -18,6 +18,8 @@ import net.sourceforge.pmd.PropertyDescriptorField;
import net.sourceforge.pmd.util.StringUtil;
/**
* Factorises common functionality for packaged properties.
*
* @author Clément Fournier
*/
public abstract class PackagedPropertyModule<T> {
@ -62,8 +64,8 @@ public abstract class PackagedPropertyModule<T> {
/**
* Evaluates the names of the items against the allowable name prefixes. If
* one or more do not have valid prefixes then an exception will be thrown.
* Evaluates the names of the items against the allowable name prefixes. If one or more do not have valid prefixes
* then an exception will be thrown.
*
* @param items Items to check
* @param legalNamePrefixes Legal name prefixes

View File

@ -7,6 +7,8 @@ package net.sourceforge.pmd.lang.rule.properties.modules;
import java.util.List;
/**
* Factorises common functionality for type properties.
*
* @author Clément Fournier
*/
public class TypePropertyModule extends PackagedPropertyModule<Class> {

View File

@ -6,6 +6,11 @@ package net.sourceforge.pmd.renderers;
import static net.sourceforge.pmd.renderers.CodeClimateRenderer.REMEDIATION_POINTS_DEFAULT;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
import net.sourceforge.pmd.lang.rule.properties.EnumeratedMultiProperty;
@ -17,6 +22,40 @@ import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
* two code climate properties "categories" and "remediation multiplier".
*/
public interface CodeClimateRule extends Rule {
/** Represent a CodeClimate category. */
enum CodeClimateCategory {
BUG_RISK("Bug Risk"),
CLARITY("Clarity"),
COMPATIBILITY("Compatibility"),
COMPLEXITY("Complexity"),
DUPLICATION("Duplication"),
PERFORMANCE("Performance"),
SECURITY("Security"),
STYLE("Style");
private String name;
CodeClimateCategory(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
/** Makes a map to define the categories for use in the descriptor. */
private static Map<String, String> categoryMap() {
Map<String, String> result = new HashMap<>();
for (CodeClimateCategory cat : values()) {
result.put(cat.name, cat.name);
}
return result;
}
}
/**
* Defines the code climate categories for which this rule will find
* violations. Possible categories are: Bug Risk, Clarity, Compatibility,
@ -26,13 +65,12 @@ public interface CodeClimateRule extends Rule {
* "https://github.com/codeclimate/spec/blob/master/SPEC.md#categories">Code
* Climate Spec</a>
*/
EnumeratedMultiProperty<String> CODECLIMATE_CATEGORIES = new EnumeratedMultiProperty<>("cc_categories",
"Code Climate Categories",
new String[] { "Bug Risk", "Clarity", "Compatibility", "Complexity", "Duplication", "Performance",
"Security", "Style", },
new String[] { "Bug Risk", "Clarity", "Compatibility", "Complexity", "Duplication", "Performance",
"Security", "Style", },
new int[] { 7 }, String.class, 1.0f);
EnumeratedMultiProperty<String> CODECLIMATE_CATEGORIES // better would be to use CodeClimateCategory as values but might break the API
= new EnumeratedMultiProperty<>("cc_categories",
"Code Climate Categories",
CodeClimateCategory.categoryMap(),
Collections.singletonList(CodeClimateCategory.STYLE.name),
String.class, 1.0f);
/**
* Defines the remediation points for this rule. The remediation points are

View File

@ -6,7 +6,10 @@ package net.sourceforge.pmd.properties;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.ast.Node;
@ -73,17 +76,31 @@ public class NonRuleWithAllPropertyTypes extends AbstractRule {
new String[] {"java.lang"}, 5.0f);
public static final TypeMultiProperty MULTI_TYPE = new TypeMultiProperty("multiType", "Multiple types",
Arrays.<Class>asList(Integer.class, Object.class), new String[] {"java.lang"}, 6.0f);
public static final EnumeratedProperty<Class> ENUM_TYPE = new EnumeratedProperty<>("enumType",
"Enumerated choices",
new String[] {"String", "Object"}, new Class[] {String.class, Object.class}, 1, Class.class, 5.0f);
public static final EnumeratedMultiProperty<Class> MULTI_ENUM_TYPE = new EnumeratedMultiProperty<>("multiEnumType",
"Multiple enumerated choices", new String[] {"String", "Object"},
new Class[] {String.class, Object.class}, new int[] {0, 1}, Class.class, 5.0f);
private static final Map<String, Class> ENUM_MAPPINGS;
static {
Map<String, Class> tmp = new HashMap<>();
tmp.put("String", String.class);
tmp.put("Object", Object.class);
ENUM_MAPPINGS = Collections.unmodifiableMap(tmp);
}
public static final EnumeratedProperty<Class> ENUM_TYPE = new EnumeratedProperty<>("enumType", "Enumerated choices", ENUM_MAPPINGS, Object.class, Class.class, 5.0f);
public static final EnumeratedMultiProperty<Class> MULTI_ENUM_TYPE = new EnumeratedMultiProperty<>("multiEnumType", "Multiple enumerated choices", ENUM_MAPPINGS,
Arrays.<Class>asList(String.class, Object.class), Class.class, 5.0f);
private static final Method STRING_LENGTH = ClassUtil.methodFor(String.class, "length", ClassUtil.EMPTY_CLASS_ARRAY);
public static final MethodProperty SINGLE_METHOD = new MethodProperty("singleMethod", "Single method", STRING_LENGTH,
new String[] {"java.lang"}, 5.0f);
public static final MethodProperty SINGLE_METHOD = new MethodProperty("singleMethod", "Single method",
STRING_LENGTH, new String[] {"java.lang"}, 5.0f);
private static final Method STRING_TO_LOWER_CASE = ClassUtil.methodFor(String.class, "toLowerCase",
ClassUtil.EMPTY_CLASS_ARRAY);
public static final MethodMultiProperty MULTI_METHOD = new MethodMultiProperty("multiMethod", "Multiple methods",
new Method[] {STRING_LENGTH, STRING_TO_LOWER_CASE}, new String[] {"java.lang"}, 6.0f);

View File

@ -4,6 +4,10 @@
package net.sourceforge.pmd.lang.java.rule.basic;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -19,9 +23,21 @@ public class AvoidUsingHardCodedIPRule extends AbstractJavaRule {
public static final String IPV6 = "IPv6";
public static final String IPV4_MAPPED_IPV6 = "IPv4 mapped IPv6";
private static final Map<String, String> ADDRESSES_TO_CHECK;
static {
Map<String, String> tmp = new HashMap<>();
tmp.put(IPV4, IPV4);
tmp.put(IPV6, IPV6);
tmp.put(IPV4_MAPPED_IPV6, IPV4_MAPPED_IPV6);
ADDRESSES_TO_CHECK = Collections.unmodifiableMap(tmp);
}
public static final EnumeratedMultiProperty<String> CHECK_ADDRESS_TYPES_DESCRIPTOR = new EnumeratedMultiProperty<>(
"checkAddressTypes", "Check for IP address types.", new String[] { IPV4, IPV6, IPV4_MAPPED_IPV6 },
new String[] { IPV4, IPV6, IPV4_MAPPED_IPV6 }, new int[] { 0, 1, 2 }, String.class, 2.0f);
"checkAddressTypes", "Check for IP address types.", ADDRESSES_TO_CHECK,
Arrays.asList(IPV4, IPV6, IPV4_MAPPED_IPV6),
String.class, 2.0f);
// Provides 4 capture groups that can be used for additional validation
protected static final String IPV4_REGEXP = "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})";