/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.rule.properties; import static net.sourceforge.pmd.PropertyDescriptorFields.DEFAULT_VALUE; import static net.sourceforge.pmd.PropertyDescriptorFields.DESCRIPTION; import static net.sourceforge.pmd.PropertyDescriptorFields.NAME; import java.util.HashMap; import java.util.Map; import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.PropertyDescriptorFields; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.util.StringUtil; /** * * @author Brian Remedios * @param <T> */ public abstract class AbstractProperty<T> implements PropertyDescriptor<T> { private final String name; private final String description; private final T defaultValue; private final boolean isRequired; private final float uiOrder; /** * Default delimiter for multi properties. Note: Numeric properties usual * use the {@value #DEFAULT_NUMERIC_DELIMITER}. */ public static final char DEFAULT_DELIMITER = '|'; /** * Default delimiter for numeric properties. */ public static final char DEFAULT_NUMERIC_DELIMITER = ','; private char multiValueDelimiter = DEFAULT_DELIMITER; protected AbstractProperty(String theName, String theDescription, T theDefault, float theUIOrder) { this(theName, theDescription, theDefault, theUIOrder, DEFAULT_DELIMITER); } /** * Constructor for AbstractPMDProperty. * * @param theName * String * @param theDescription * String * @param theDefault * Object * @param theUIOrder * float * @throws IllegalArgumentException */ protected AbstractProperty(String theName, String theDescription, T theDefault, float theUIOrder, char delimiter) { name = checkNotEmpty(theName, NAME); description = checkNotEmpty(theDescription, DESCRIPTION); defaultValue = theDefault; isRequired = false; // TODO - do we need this? uiOrder = checkPositive(theUIOrder, "UI order"); multiValueDelimiter = delimiter; } /** * @param arg * String * @param argId * String * @return String * @throws IllegalArgumentException */ private static String checkNotEmpty(String arg, String argId) { if (StringUtil.isEmpty(arg)) { throw new IllegalArgumentException("Property attribute '" + argId + "' cannot be null or blank"); } return arg; } /** * @param arg * float * @param argId * String * @return float * @throws IllegalArgumentException */ private static float checkPositive(float arg, String argId) { if (arg < 0) { throw new IllegalArgumentException("Property attribute " + argId + "' must be zero or positive"); } return arg; } /** * {@inheritDoc} */ @Override public char multiValueDelimiter() { return multiValueDelimiter; } /** * {@inheritDoc} */ @Override public String name() { return name; } /** * {@inheritDoc} */ @Override public String description() { return description; } /** * {@inheritDoc} */ @Override public T defaultValue() { return defaultValue; } /** * Method defaultHasNullValue. * * @return boolean */ protected boolean defaultHasNullValue() { if (defaultValue == null) { return true; } if (isMultiValue() && isArray(defaultValue)) { Object[] defaults = (Object[]) defaultValue; for (Object default1 : defaults) { if (default1 == null) { return true; } } } return false; } /** * {@inheritDoc} */ @Override public boolean isMultiValue() { return false; } /** * {@inheritDoc} */ @Override public boolean isRequired() { return isRequired; } /** * {@inheritDoc} */ @Override public float uiOrder() { return uiOrder; } /** * Return the value as a string that can be easily recognized and parsed * when we see it again. * * @param value * Object * @return String */ protected String asString(Object value) { return value == null ? "" : value.toString(); } /** * {@inheritDoc} */ @Override public String asDelimitedString(T values) { return asDelimitedString(values, multiValueDelimiter()); } /** * Return the specified values as a single string using the delimiter. * * @param values * Object * @param delimiter * char * @return String * @see net.sourceforge.pmd.PropertyDescriptor#asDelimitedString(Object) */ public String asDelimitedString(T values, char delimiter) { if (values == null) { return ""; } if (values instanceof Object[]) { Object[] valueSet = (Object[]) values; if (valueSet.length == 0) { return ""; } if (valueSet.length == 1) { return asString(valueSet[0]); } StringBuilder sb = new StringBuilder(); sb.append(asString(valueSet[0])); for (int i = 1; i < valueSet.length; i++) { sb.append(delimiter); sb.append(asString(valueSet[i])); } return sb.toString(); } return asString(values); } /** * {@inheritDoc} */ @Override public int compareTo(PropertyDescriptor<?> otherProperty) { float otherOrder = otherProperty.uiOrder(); return (int) (otherOrder - uiOrder); } /** * {@inheritDoc} */ @Override public String errorFor(Object value) { String typeError = typeErrorFor(value); if (typeError != null) { return typeError; } return isMultiValue() ? valuesErrorFor(value) : valueErrorFor(value); } /** * @param value * Object * @return String */ protected String valueErrorFor(Object value) { if (value == null) { if (defaultHasNullValue()) { return null; } return "missing value"; } return null; } /** * @param value * Object * @return String */ protected String valuesErrorFor(Object value) { if (!isArray(value)) { return "multiple values expected"; } Object[] values = (Object[]) value; String err = null; for (Object value2 : values) { err = valueErrorFor(value2); if (err != null) { return err; } } return null; } /** * @param value * Object * @return boolean */ protected static boolean isArray(Object value) { return value != null && value.getClass().getComponentType() != null; } /** * @param value * Object * @return String */ protected String typeErrorFor(Object value) { if (value == null && !isRequired) { return null; } if (isMultiValue()) { if (!isArray(value)) { return "Value is not an array of type: " + type(); } Class<?> arrayType = value.getClass().getComponentType(); if (arrayType == null || !arrayType.isAssignableFrom(type().getComponentType())) { return "Value is not an array of type: " + type(); } return null; } if (!type().isAssignableFrom(value.getClass())) { return value + " is not an instance of " + type(); } return null; } /** * {@inheritDoc} */ @Override public String propertyErrorFor(Rule rule) { Object realValue = rule.getProperty(this); if (realValue == null && !isRequired()) { return null; } return errorFor(realValue); } /** * {@inheritDoc} */ @Override public Object[][] choices() { return null; } /** * {@inheritDoc} */ @Override public int preferredRowCount() { return 1; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (obj instanceof PropertyDescriptor) { return name.equals(((PropertyDescriptor<?>) obj).name()); } return false; } /** * {@inheritDoc} */ @Override public int hashCode() { return name.hashCode(); } /** * {@inheritDoc} */ @Override public String toString() { return "[PropertyDescriptor: name=" + name() + ", type=" + type() + ", value=" + defaultValue() + "]"; } /** * @return String */ protected String defaultAsString() { if (isMultiValue()) { return asDelimitedString(defaultValue(), multiValueDelimiter()); } else { return defaultValue().toString(); } } /** * @param value * Object * @param otherValue * Object * @return boolean */ @SuppressWarnings("PMD.CompareObjectsWithEquals") public static final boolean areEqual(Object value, Object otherValue) { if (value == otherValue) { return true; } if (value == null) { return false; } if (otherValue == null) { return false; } return value.equals(otherValue); } @Override public Map<String, String> attributeValuesById() { Map<String, String> values = new HashMap<>(); addAttributesTo(values); return values; } protected void addAttributesTo(Map<String, String> attributes) { attributes.put(NAME, name); attributes.put(DESCRIPTION, description); attributes.put(DEFAULT_VALUE, defaultAsString()); if (isMultiValue()) { attributes.put(PropertyDescriptorFields.DELIMITER, Character.toString(multiValueDelimiter())); } } }