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