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 }
|