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