AbstractProperty.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.rule.properties;
005 
006 import java.util.HashMap;
007 import java.util.Map;
008 
009 import net.sourceforge.pmd.PropertyDescriptor;
010 import net.sourceforge.pmd.Rule;
011 import net.sourceforge.pmd.util.StringUtil;
012 
013 /**
014  *
015  @author Brian Remedios
016  */
017 public abstract class AbstractProperty<T> implements PropertyDescriptor<T> {
018 
019   private final String  name;
020   private final String  description;
021   private final T     defaultValue;
022   private final boolean   isRequired;
023   private final float    uiOrder;
024 
025   private static final char DELIMITER = '|';
026 
027   /**
028    * Constructor for AbstractPMDProperty.
029    @param theName String
030    @param theDescription String
031    @param theDefault Object
032    @param theUIOrder float
033    @throws IllegalArgumentException
034    */
035   protected AbstractProperty(String theName, String theDescription, T theDefault, float theUIOrder) {
036     name = checkNotEmpty(theName, "name");
037     description = checkNotEmpty(theDescription, "description");
038     defaultValue = theDefault;
039     isRequired = false;  // TODO - do we need this?
040     uiOrder = checkPositive(theUIOrder, "UI order");
041   }
042 
043   /**
044    @param arg String
045    @param argId String
046    @return String
047    @throws IllegalArgumentException
048    */
049   private static String checkNotEmpty(String arg, String argId) {
050 
051     if (StringUtil.isEmpty(arg)) {
052       throw new IllegalArgumentException("Property attribute '" + argId + "' cannot be null or blank");
053     }
054 
055     return arg;
056   }
057 
058   /**
059    @param arg float
060    @param argId String
061    @return float
062    @throws IllegalArgumentException
063    */
064   private static float checkPositive(float arg, String argId) {
065     if (arg < 0) {
066       throw new IllegalArgumentException("Property attribute " + argId + "' must be zero or positive");
067     }
068     return arg;
069   }
070 
071   /**
072          * {@inheritDoc}
073    */
074   public char multiValueDelimiter() {
075     return DELIMITER;
076   }
077 
078   /**
079          * {@inheritDoc}
080    */
081   public String name() {
082     return name;
083   }
084 
085   /**
086          * {@inheritDoc}
087    */
088   public String description() {
089     return description;
090   }
091 
092   /**
093          * {@inheritDoc}
094    */
095   public T defaultValue() {
096     return defaultValue;
097   }
098 
099   /**
100    * Method defaultHasNullValue.
101    @return boolean
102    */
103   protected boolean defaultHasNullValue() {
104 
105     if (defaultValue == null) {
106       return true;
107     }
108 
109     if (isMultiValue() && isArray(defaultValue)) {
110       Object[] defaults = (Object[])defaultValue;
111       for (Object default1 : defaults) {
112         if (default1 == null) { return true}
113       }
114     }
115 
116     return false;
117   }
118 
119   /**
120          * {@inheritDoc}
121    */
122   public boolean isMultiValue() {
123     return false;
124   }
125 
126   /**
127          * {@inheritDoc}
128    */
129   public boolean isRequired() {
130     return isRequired;
131   }
132 
133   /**
134          * {@inheritDoc}
135    */
136   public float uiOrder() {
137     return uiOrder;
138   }
139 
140   /**
141    * Return the value as a string that can be easily recognized and parsed
142    * when we see it again.
143    *
144    @param value Object
145    @return String
146    */
147   protected String asString(Object value) {
148     return value == null "" : value.toString();
149   }
150 
151   /**
152          * {@inheritDoc}
153    */
154   public String asDelimitedString(T values) {
155       return asDelimitedString(values, multiValueDelimiter());
156   }
157 
158   /**
159    * Return the specified values as a single string using the delimiter.
160    @param values Object
161    @param delimiter char
162    @return String
163    @see net.sourceforge.pmd.PropertyDescriptor#asDelimitedString(Object)
164    */
165   public String asDelimitedString(T values, char delimiter) {
166     if (values == null) {
167         return "";
168     }
169 
170     if (values instanceof Object[]) {
171       Object[] valueSet = (Object[])values;
172       if (valueSet.length == 0) {
173           return "";
174       }
175       if (valueSet.length == 1) {
176           return asString(valueSet[0]);
177       }
178 
179       StringBuilder sb = new StringBuilder();
180       sb.append(asString(valueSet[0]));
181       for (int i=1; i<valueSet.length; i++) {
182         sb.append(delimiter);
183         sb.append(asString(valueSet[i]));
184       }
185       return sb.toString();
186       }
187 
188     return asString(values);
189   }
190 
191   /**
192          * {@inheritDoc}
193    */
194   public int compareTo(PropertyDescriptor<?> otherProperty) {
195     float otherOrder = otherProperty.uiOrder();
196     return (int) (otherOrder - uiOrder);
197   }
198 
199   /**
200          * {@inheritDoc}
201    */
202   public String errorFor(Object value) {
203 
204     String typeError = typeErrorFor(value);
205     if (typeError != null) {
206         return typeError;
207     }
208     return isMultiValue() ?
209       valuesErrorFor(value:
210       valueErrorFor(value);
211   }
212 
213   /**
214    @param value Object
215    @return String
216    */
217   protected String valueErrorFor(Object value) {
218 
219     if (value == null) {
220       if (defaultHasNullValue()) {
221         return null;
222       }
223       return "missing value";
224     }
225     return null;
226   }
227 
228   /**
229    @param value Object
230    @return String
231    */
232   protected String valuesErrorFor(Object value) {
233 
234     if (!isArray(value)) {
235       return "multiple values expected";
236     }
237 
238     Object[] values = (Object[])value;
239 
240     String err = null;
241     for (Object value2 : values) {
242       err = valueErrorFor(value2);
243       if (err != null) { return err; }
244     }
245 
246     return null;
247   }
248 
249   /**
250    @param value Object
251    @return boolean
252    */
253   protected static boolean isArray(Object value) {
254     return value != null && value.getClass().getComponentType() != null;
255   }
256 
257   /**
258    @param value Object
259    @return String
260    */
261   protected String typeErrorFor(Object value) {
262 
263     if (value == null && !isRequired) {
264         return null;
265     }
266 
267     if (isMultiValue()) {
268       if (!isArray(value)) {
269         return "Value is not an array of type: " + type();
270       }
271 
272       Class<?> arrayType = value.getClass().getComponentType();
273       if (arrayType == null || !arrayType.isAssignableFrom(type().getComponentType())) {
274         return "Value is not an array of type: " + type();
275       }
276       return null;
277     }
278 
279     if (!type().isAssignableFrom(value.getClass())) {
280       return value + " is not an instance of " + type();
281     }
282 
283     return null;
284   }
285 
286   /**
287          * {@inheritDoc}
288    */
289   public String propertyErrorFor(Rule rule) {
290       Object realValue = rule.getProperty(this);
291     if (realValue == null && !isRequired()) {
292         return null;
293     }
294     return errorFor(realValue);
295   }
296 
297   /**
298          * {@inheritDoc}
299    */
300   public Object[][] choices() {
301     return null;
302   }
303 
304   /**
305          * {@inheritDoc}
306    */
307   public int preferredRowCount() {
308     return 1;
309   }
310 
311   /**
312          * {@inheritDoc}
313    */
314   @Override
315   public boolean equals(Object obj) {
316       if (this == obj) {
317     return true;
318       }
319       if (obj == null) {
320     return false;
321       }
322       if (obj instanceof PropertyDescriptor) {
323     return name.equals(((PropertyDescriptor<?>)obj).name());
324       }
325       return false;
326   }
327 
328   /**
329          * {@inheritDoc}
330    */
331   @Override
332   public int hashCode() {
333       return name.hashCode();
334   }
335 
336   /**
337          * {@inheritDoc}
338    */
339   @Override
340   public String toString() {
341       return "[PropertyDescriptor: name=" + name() ", type=" + type() ", defaultValue=" + defaultValue() "]";
342   }
343 
344   /**
345    @return String
346    */
347   protected abstract String defaultAsString();
348 
349   /**
350    @param value Object
351    @param otherValue Object
352    @return boolean
353    */
354   @SuppressWarnings("PMD.CompareObjectsWithEquals")
355   public static final boolean areEqual(Object value, Object otherValue) {
356     if (value == otherValue) {
357         return true;
358     }
359     if (value == null) {
360         return false;
361     }
362     if (otherValue == null) {
363         return false;
364     }
365 
366     return value.equals(otherValue);
367   }
368 
369   /**
370    @return Map<String,String>
371    */
372   public Map<String, String> attributeValuesById() {
373 
374     Map<String, String> values = new HashMap<String, String>();
375     addAttributesTo(values);
376     return values;
377   }
378 
379   /**
380    @param attributes Map<String,String>
381    */
382   protected void addAttributesTo(Map<String, String> attributes) {
383     attributes.put("description", description);
384     attributes.put("default", defaultAsString());
385   }
386 
387 }