View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.rule.properties;
5   
6   import static net.sourceforge.pmd.PropertyDescriptorFields.DEFAULT_VALUE;
7   import static net.sourceforge.pmd.PropertyDescriptorFields.DESCRIPTION;
8   import static net.sourceforge.pmd.PropertyDescriptorFields.NAME;
9   
10  import java.util.HashMap;
11  import java.util.Map;
12  
13  import net.sourceforge.pmd.PropertyDescriptor;
14  import net.sourceforge.pmd.PropertyDescriptorFields;
15  import net.sourceforge.pmd.Rule;
16  import net.sourceforge.pmd.util.StringUtil;
17  
18  /**
19   *
20   * @author Brian Remedios
21   * @param <T>
22   */
23  public abstract class AbstractProperty<T> implements PropertyDescriptor<T> {
24  
25      private final String name;
26      private final String description;
27      private final T defaultValue;
28      private final boolean isRequired;
29      private final float uiOrder;
30  
31      /**
32       * Default delimiter for multi properties.
33       * Note: Numeric properties usual use the {@value #DEFAULT_NUMERIC_DELIMITER}.
34       */
35      public static final char DEFAULT_DELIMITER = '|';
36      /**
37       * Default delimiter for numeric properties.
38       */
39      public static final char DEFAULT_NUMERIC_DELIMITER = ',';
40  
41      private char multiValueDelimiter = DEFAULT_DELIMITER;
42  
43      protected AbstractProperty(String theName, String theDescription, T theDefault, float theUIOrder) {
44          this(theName, theDescription, theDefault, theUIOrder, DEFAULT_DELIMITER);
45      }
46  
47      /**
48       * Constructor for AbstractPMDProperty.
49       * 
50       * @param theName String
51       * @param theDescription String
52       * @param theDefault Object
53       * @param theUIOrder float
54       * @throws IllegalArgumentException
55       */
56      protected AbstractProperty(String theName, String theDescription, T theDefault, float theUIOrder, char delimiter) {
57          name = checkNotEmpty(theName, NAME);
58          description = checkNotEmpty(theDescription, DESCRIPTION);
59          defaultValue = theDefault;
60          isRequired = false; // TODO - do we need this?
61          uiOrder = checkPositive(theUIOrder, "UI order");
62          multiValueDelimiter = delimiter;
63      }
64  
65      /**
66       * @param arg String
67       * @param argId String
68       * @return String
69       * @throws IllegalArgumentException
70       */
71      private static String checkNotEmpty(String arg, String argId) {
72  
73          if (StringUtil.isEmpty(arg)) {
74              throw new IllegalArgumentException("Property attribute '" + argId + "' cannot be null or blank");
75          }
76  
77          return arg;
78      }
79  
80      /**
81       * @param arg float
82       * @param argId String
83       * @return float
84       * @throws IllegalArgumentException
85       */
86      private static float checkPositive(float arg, String argId) {
87          if (arg < 0) {
88              throw new IllegalArgumentException("Property attribute " + argId + "' must be zero or positive");
89          }
90          return arg;
91      }
92  
93      /**
94       * {@inheritDoc}
95       */
96      public char multiValueDelimiter() {
97          return multiValueDelimiter;
98      }
99  
100     /**
101      * {@inheritDoc}
102      */
103     public String name() {
104         return name;
105     }
106 
107     /**
108      * {@inheritDoc}
109      */
110     public String description() {
111         return description;
112     }
113 
114     /**
115      * {@inheritDoc}
116      */
117     public T defaultValue() {
118         return defaultValue;
119     }
120 
121     /**
122      * Method defaultHasNullValue.
123      * 
124      * @return boolean
125      */
126     protected boolean defaultHasNullValue() {
127 
128         if (defaultValue == null) {
129             return true;
130         }
131 
132         if (isMultiValue() && isArray(defaultValue)) {
133             Object[] defaults = (Object[]) defaultValue;
134             for (Object default1 : defaults) {
135                 if (default1 == null) {
136                     return true;
137                 }
138             }
139         }
140 
141         return false;
142     }
143 
144     /**
145      * {@inheritDoc}
146      */
147     public boolean isMultiValue() {
148         return false;
149     }
150 
151     /**
152      * {@inheritDoc}
153      */
154     public boolean isRequired() {
155         return isRequired;
156     }
157 
158     /**
159      * {@inheritDoc}
160      */
161     public float uiOrder() {
162         return uiOrder;
163     }
164 
165     /**
166      * Return the value as a string that can be easily recognized and parsed
167      * when we see it again.
168      *
169      * @param value Object
170      * @return String
171      */
172     protected String asString(Object value) {
173         return value == null ? "" : value.toString();
174     }
175 
176     /**
177      * {@inheritDoc}
178      */
179     public String asDelimitedString(T values) {
180         return asDelimitedString(values, multiValueDelimiter());
181     }
182 
183     /**
184      * Return the specified values as a single string using the delimiter.
185      * 
186      * @param values Object
187      * @param delimiter char
188      * @return String
189      * @see net.sourceforge.pmd.PropertyDescriptor#asDelimitedString(Object)
190      */
191     public String asDelimitedString(T values, char delimiter) {
192         if (values == null) {
193             return "";
194         }
195 
196         if (values instanceof Object[]) {
197             Object[] valueSet = (Object[]) values;
198             if (valueSet.length == 0) {
199                 return "";
200             }
201             if (valueSet.length == 1) {
202                 return asString(valueSet[0]);
203             }
204 
205             StringBuilder sb = new StringBuilder();
206             sb.append(asString(valueSet[0]));
207             for (int i = 1; i < valueSet.length; i++) {
208                 sb.append(delimiter);
209                 sb.append(asString(valueSet[i]));
210             }
211             return sb.toString();
212         }
213 
214         return asString(values);
215     }
216 
217     /**
218      * {@inheritDoc}
219      */
220     public int compareTo(PropertyDescriptor<?> otherProperty) {
221         float otherOrder = otherProperty.uiOrder();
222         return (int) (otherOrder - uiOrder);
223     }
224 
225     /**
226      * {@inheritDoc}
227      */
228     public String errorFor(Object value) {
229 
230         String typeError = typeErrorFor(value);
231         if (typeError != null) {
232             return typeError;
233         }
234         return isMultiValue() ? valuesErrorFor(value) : valueErrorFor(value);
235     }
236 
237     /**
238      * @param value Object
239      * @return String
240      */
241     protected String valueErrorFor(Object value) {
242 
243         if (value == null) {
244             if (defaultHasNullValue()) {
245                 return null;
246             }
247             return "missing value";
248         }
249         return null;
250     }
251 
252     /**
253      * @param value Object
254      * @return String
255      */
256     protected String valuesErrorFor(Object value) {
257 
258         if (!isArray(value)) {
259             return "multiple values expected";
260         }
261 
262         Object[] values = (Object[]) value;
263 
264         String err = null;
265         for (Object value2 : values) {
266             err = valueErrorFor(value2);
267             if (err != null) {
268                 return err;
269             }
270         }
271 
272         return null;
273     }
274 
275     /**
276      * @param value Object
277      * @return boolean
278      */
279     protected static boolean isArray(Object value) {
280         return value != null && value.getClass().getComponentType() != null;
281     }
282 
283     /**
284      * @param value Object
285      * @return String
286      */
287     protected String typeErrorFor(Object value) {
288 
289         if (value == null && !isRequired) {
290             return null;
291         }
292 
293         if (isMultiValue()) {
294             if (!isArray(value)) {
295                 return "Value is not an array of type: " + type();
296             }
297 
298             Class<?> arrayType = value.getClass().getComponentType();
299             if (arrayType == null || !arrayType.isAssignableFrom(type().getComponentType())) {
300                 return "Value is not an array of type: " + type();
301             }
302             return null;
303         }
304 
305         if (!type().isAssignableFrom(value.getClass())) {
306             return value + " is not an instance of " + type();
307         }
308 
309         return null;
310     }
311 
312     /**
313      * {@inheritDoc}
314      */
315     public String propertyErrorFor(Rule rule) {
316         Object realValue = rule.getProperty(this);
317         if (realValue == null && !isRequired()) {
318             return null;
319         }
320         return errorFor(realValue);
321     }
322 
323     /**
324      * {@inheritDoc}
325      */
326     public Object[][] choices() {
327         return null;
328     }
329 
330     /**
331      * {@inheritDoc}
332      */
333     public int preferredRowCount() {
334         return 1;
335     }
336 
337     /**
338      * {@inheritDoc}
339      */
340     @Override
341     public boolean equals(Object obj) {
342         if (this == obj) {
343             return true;
344         }
345         if (obj == null) {
346             return false;
347         }
348         if (obj instanceof PropertyDescriptor) {
349             return name.equals(((PropertyDescriptor<?>) obj).name());
350         }
351         return false;
352     }
353 
354     /**
355      * {@inheritDoc}
356      */
357     @Override
358     public int hashCode() {
359         return name.hashCode();
360     }
361 
362     /**
363      * {@inheritDoc}
364      */
365     @Override
366     public String toString() {
367         return "[PropertyDescriptor: name=" + name() + ", type=" + type() + ", value=" + defaultValue() + "]";
368     }
369 
370     /**
371      * @return String
372      */
373     protected String defaultAsString() {
374         if (isMultiValue()) {
375             return asDelimitedString(defaultValue(), multiValueDelimiter());
376         } else {
377             return defaultValue().toString();
378         }
379     }
380 
381     /**
382      * @param value Object
383      * @param otherValue Object
384      * @return boolean
385      */
386     @SuppressWarnings("PMD.CompareObjectsWithEquals")
387     public static final boolean areEqual(Object value, Object otherValue) {
388         if (value == otherValue) {
389             return true;
390         }
391         if (value == null) {
392             return false;
393         }
394         if (otherValue == null) {
395             return false;
396         }
397 
398         return value.equals(otherValue);
399     }
400 
401     /**
402      * @return Map<String,String>
403      */
404     public Map<String, String> attributeValuesById() {
405 
406         Map<String, String> values = new HashMap<>();
407         addAttributesTo(values);
408         return values;
409     }
410 
411     /**
412      * @param attributes Map<String,String>
413      */
414     protected void addAttributesTo(Map<String, String> attributes) {
415         attributes.put(NAME, name);
416         attributes.put(DESCRIPTION, description);
417         attributes.put(DEFAULT_VALUE, defaultAsString());
418         if (isMultiValue()) {
419             attributes.put(PropertyDescriptorFields.DELIMITER, Character.toString(multiValueDelimiter()));
420         }
421     }
422 
423 }