Clover Coverage Report - itracker
Coverage timestamp: Tue May 1 2012 16:42:12 CEST
64   474   43   2.67
16   182   0.67   6
24     1.79  
4    
 
 
  CustomField       Line # 73 46 31 16.7% 0.16666667
  CustomField.Type       Line # 397 14 8 33.3% 0.33333334
  CustomField.DateFormat       Line # 437 1 1 0% 0.0
  CustomField.NameComparator       Line # 449 3 3 66.7% 0.6666667
 
No Tests
 
1    /*
2    * This software was designed and created by Jason Carroll.
3    * Copyright (c) 2002, 2003, 2004 Jason Carroll.
4    * The author can be reached at jcarroll@cowsultants.com
5    * ITracker website: http://www.cowsultants.com
6    * ITracker forums: http://www.cowsultants.com/phpBB/index.php
7    *
8    * This program is free software; you can redistribute it and/or modify
9    * it only under the terms of the GNU General Public License as published by
10    * the Free Software Foundation; either version 2 of the License, or
11    * (at your option) any later version.
12    *
13    * This program is distributed in the hope that it will be useful,
14    * but WITHOUT ANY WARRANTY; without even the implied warranty of
15    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16    * GNU General Public License for more details.
17    */
18   
19    package org.itracker.model;
20   
21    import java.io.Serializable;
22    import java.text.ParseException;
23    import java.text.SimpleDateFormat;
24    import java.util.ArrayList;
25    import java.util.Comparator;
26    import java.util.Iterator;
27    import java.util.List;
28    import java.util.Locale;
29    import java.util.ResourceBundle;
30   
31    import org.apache.commons.lang.builder.CompareToBuilder;
32    import org.apache.commons.lang.builder.ToStringBuilder;
33    import org.apache.log4j.Logger;
34    import org.itracker.services.exceptions.IssueException;
35    import org.itracker.services.util.CustomFieldUtilities;
36    import org.itracker.web.taglib.FormatCustomFieldTag;
37    import org.jfree.util.Log;
38   
39    /**
40    * A custom field that can be added to an Issue.
41    *
42    * <p>
43    * Allows the user to dynamically extend the set of attributes/properties of the
44    * Issue class.
45    * </p>
46    *
47    * <p>
48    * A CustomField must be configured to be used in a Project in order to extend
49    * the attributes/properties of all Issues created for that project. A
50    * CustomField may be used in more than 1 project. (Project - CustomField is a
51    * M-N relathionship).
52    * </p>
53    *
54    * <p>
55    * A CustomField has a type, which indicates the data type of its value. <br>
56    * The special type <code>LIST</code>, allows to associate a list of string
57    * options to a CustomField, which are the enumeration of possible values for
58    * that field. <br>
59    * Each option value is represented by a CustomFieldValue instance. There's a
60    * 1-N relationship between CustomField - CustomFieldValue. A CustomFieldValue
61    * can only belong to 1 CustomField (composition).
62    * </p>
63    *
64    * <p>
65    * A value of a CustomField for a given Issue is represented by an IssueField
66    * instance. (CustomField - IssueField is a 1-N relationship).
67    * </p>
68    *
69    * @author ready
70    * @see CustomFieldValue
71    * @see IssueField
72    */
 
73    public class CustomField extends AbstractEntity implements Comparable<Entity> {
74   
75    private static final Logger logger = Logger.getLogger(CustomField.class);
76   
77    public static final Comparator<CustomField> NAME_COMPARATOR = new NameComparator();
78    /**
79    * Dateformat able to parse datepicker generated date string (dd/MM/yyyy)
80    */
81    public static final SimpleDateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("dd/MM/yyyy");
82    /**
83    *
84    */
85    private static final long serialVersionUID = 1L;
86   
87   
88    /* Is a CustomField instance locale-specific ? */
89    // private Locale locale;
90    /**
91    * Field value data type.
92    */
93    private Type type;
94   
95    /**
96    * Display format to use if <code>fieldType</code> is a Date.
97    *
98    * TODO: use type-safe enum CustomField.DateFormat
99    */
100    private String dateFormat;
101   
102    /**
103    * Whether this field is mandatory or optional. PENDING: this should be
104    * specified when the field is used in a project!
105    */
106    private boolean required;
107   
108    /**
109    * List of options for a field of type <code>LIST</code>.
110    *
111    * <p>
112    * This is the enumeration of possible values for the field.
113    * </p>
114    *
115    * Note: this field used to be named <code>values</code> is iTracker 2.
116    *
117    * <p>
118    * PENDING: There's no way to use this as a list of proposed values,
119    * allowing the user to enter a value that's not in this list.
120    * </p>
121    */
122    private List<CustomFieldValue> options = new ArrayList<CustomFieldValue>();
123   
124    /**
125    * Whether the options of a field of type List should be sorted by their
126    * name rather than by {@link CustomFieldValue#getSortOrder() }.
127    */
128    private boolean sortOptionsByName;
129   
130    /*
131    * This class used to have a <code>fields</code> attribute, which was a
132    * Collection<IssueField>. This has been removed because the association
133    * CustomField - IssueField doesn't need to be navigatable in this
134    * direction.
135    */
136   
137    /**
138    * Default constructor (required by Hibernate).
139    *
140    * <p>
141    * PENDING: should be <code>private</code> so that it can only be used by
142    * Hibernate, to ensure that the fields which form an instance's identity
143    * are always initialized/never <tt>null</tt>.
144    * </p>
145    */
 
146  3 toggle public CustomField() {
147    }
148   
 
149  0 toggle @Deprecated
150    public CustomField(String name, Type type) {
151    // setName(name);
152  0 setFieldType(type);
153    }
154   
155    // /**
156    // *
157    // * @deprecated this can not be in the entity, replace by Utility or service.
158    // * @return
159    // */
160    // public String getName() {
161    // return name;
162    // }
163    //
164    // public void setName(String name) {
165    // this.name = name;
166    // }
167   
 
168  0 toggle public Type getFieldType() {
169  0 return type;
170    }
171   
 
172  1 toggle public void setFieldType(Type type) {
173  1 this.type = type;
174    }
175   
 
176  0 toggle public String getDateFormat() {
177  0 return dateFormat;
178    }
179   
 
180  1 toggle public void setDateFormat(String dateFormat) {
181  1 this.dateFormat = dateFormat;
182    }
183   
 
184  0 toggle public boolean isRequired() {
185  0 return required;
186    }
187   
 
188  1 toggle public void setRequired(boolean required) {
189  1 this.required = required;
190    }
191   
 
192  1 toggle public List<CustomFieldValue> getOptions() {
193  1 return options;
194    }
195   
 
196  0 toggle public void setOptions(List<CustomFieldValue> options) {
197  0 this.options = options;
198    }
199   
200    /**
201    * Adds a new option value/name to the custom field.
202    *
203    * <p>
204    * New options are put at the end of the list even if they should be sorted.
205    * <br>
206    * This method is mainly used to build a new custom field so it can be saved
207    * later.
208    * </p>
209    *
210    * @param value
211    * the option value
212    * @param label
213    * the label/name for the new option
214    *
215    * @deprecated this can not be in the entity, replace by Utility or service.
216    */
 
217  2 toggle public void addOption(String value, String label) {
218  2 this.options.add(new CustomFieldValue(this, value));
219    }
220   
221    /**
222    * Returns the name for a particular option value.
223    *
224    * @deprecated this can not be in the entity, replace by Utility or service.
225    * FIXME: Don't know, this seems not to be working. Removed use from {@link FormatCustomFieldTag}
226    * @param optionValue
227    * the value to lookup the name for
228    * @return the localized name for the supplied value
229    */
 
230  0 toggle public String getOptionNameByValue(String optionValue) {
231  0 final Iterator<CustomFieldValue> iter = this.options.iterator();
232   
233  0 if (logger.isDebugEnabled()) {
234  0 logger.warn("getOptionNameByValue: called deprecated api", new RuntimeException());
235    }
236  0 while (iter.hasNext()) {
237  0 CustomFieldValue option = iter.next();
238   
239  0 if (option.getValue().equalsIgnoreCase(optionValue)) {
240  0 return CustomFieldUtilities.getCustomFieldOptionName(option, null);
241    }
242    }
243  0 return "";
244    }
245   
 
246  0 toggle public boolean isSortOptionsByName() {
247  0 return sortOptionsByName;
248    }
249   
250    /**
251    * @param sortOptionsByName
252    */
 
253  1 toggle public void setSortOptionsByName(boolean sortOptionsByName) {
254  1 this.sortOptionsByName = sortOptionsByName;
255    }
256   
257    // /**
258    // * Sets this custom fields names based on the supplied local string.
259    // *
260    // * @deprecated this can not be in the entity, replace by Utility or service.
261    // * @param locale
262    // * the name of the locale to use for the names
263    // */
264    // public void setLabels(String locale) {
265    // Locale loc = ITrackerResources.getLocale(locale);
266    // setLabels(loc);
267    // }
268   
269    // /**
270    // * Sets this custom fields names based on the supplied locale.
271    // * @deprecated this can not be in the entity, replace by Utility or service.
272    // * @param locale
273    // * the locale to use for the names
274    // */
275    // public void setLabels(Locale locale) {
276    // setName(CustomFieldUtilities.getCustomFieldName(getId(), locale));
277    //
278    // final Iterator<CustomFieldValue> iter = this.getOptions().iterator();
279    //
280    // while (iter.hasNext()) {
281    // CustomFieldValue option = iter.next();
282    //
283    // option.setName(CustomFieldUtilities.getCustomFieldOptionName(
284    // this.getId(), option.getId(), locale));
285    // }
286    //
287    // if (isSortOptionsByName()) {
288    // // Specify ordering other than the natural ordering of
289    // // CustomFieldValue.
290    // Collections.sort(this.getOptions(), CustomFieldValue.NAME_COMPARATOR);
291    // } else {
292    // Collections.sort(this.getOptions(), CustomFieldValue.SORT_ORDER_COMPARATOR);
293    // }
294    // }
295   
 
296  0 toggle @Override
297    public String toString() {
298   
299  0 return new ToStringBuilder(this)
300    .append("id", getId())
301    .append("type", getFieldType())
302    .append("sortOptionsByName", isSortOptionsByName()).toString();
303    }
304   
305    //
306    // public int compareTo(CustomField other) {
307    // return this.name.compareTo(other.name);
308    // }
309    //
310    // @Override
311    // public boolean equals(Object obj) {
312    // if (this == obj) {
313    // return true;
314    // }
315    //
316    // if (obj instanceof CustomField) {
317    // final CustomField other = (CustomField)obj;
318    //
319    // return this.name.equals(other.name);
320    // }
321    // return false;
322    // }
323    //
324    // @Override
325    // public int hashCode() {
326    // return this.name.hashCode();
327    // }
328    //
329    /**
330    * Checks if the given value is assignable to this custom field.
331    *
332    * @param value
333    * custom field data
334    * @param locale
335    * @param bundle
336    * @throws IssueException
337    * if it isn't
338    * @see IssueField#setValue(String, Locale, ResourceBundle)
339    */
 
340  0 toggle public void checkAssignable(String value, Locale locale,
341    ResourceBundle bundle) throws IssueException {
342   
343   
344  0 if (this.isRequired() && (value == null || value.trim().length() == 0)) {
345  0 throw new IssueException("Value is required.", IssueException.TYPE_CF_REQ_FIELD);
346    }
347   
348  0 switch (this.type) {
349   
350  0 case INTEGER:
351  0 try {
352  0 Integer.parseInt(value);
353    } catch (NumberFormatException nfe) {
354  0 throw new IssueException("Invalid integer.",
355    IssueException.TYPE_CF_PARSE_NUM);
356    }
357  0 break;
358   
359  0 case DATE:
360  0 if (this.dateFormat != CustomFieldUtilities.DATE_FORMAT_UNKNOWN) {
361  0 SimpleDateFormat format =
362    // DEFAULT_DATE_FORMAT;
363    new SimpleDateFormat(bundle
364    .getString("itracker.dateformat." + this.dateFormat),
365    locale);
366   
367  0 try {
368  0 format.parse(value);
369    } catch (ParseException ex) {
370  0 throw new IssueException("Invalid date format.",
371    IssueException.TYPE_CF_PARSE_DATE);
372    }
373    }
374  0 break;
375   
376  0 case LIST:
377  0 Iterator<CustomFieldValue> it = getOptions().iterator();
378  0 while (it.hasNext()) {
379  0 CustomFieldValue customFieldValue = (CustomFieldValue) it
380    .next();
381  0 if (customFieldValue.getValue().equalsIgnoreCase(value)) {
382  0 return;
383    }
384    }
385  0 if (Log.isDebugEnabled()) {
386  0 Log.debug("checkAssignable: could not assign value to custom field values: " + value + ", " + getOptions());
387    }
388  0 throw new IssueException("Invalid value.", IssueException.TYPE_CF_INVALID_LIST_OPTION);
389  0 default:
390    // Value is OK
391    }
392    }
393   
394    /**
395    * Enumeration of possible data types.
396    */
 
397    public static enum Type implements IntCodeEnum<Type> {
398   
399    STRING(1), INTEGER(2), DATE(3), LIST(4);
400   
401    private final int code;
402   
 
403  4 toggle private Type(int code) {
404  4 this.code = code;
405    }
406   
 
407  0 toggle public int getCode() {
408  0 return code;
409    }
410   
 
411  0 toggle public Type fromCode(int code) {
412  0 return Type.valueOf(code);
413    }
414   
 
415  1 toggle public static Type valueOf(int code) {
416  1 switch (code) {
417  0 case 1:
418  0 return STRING;
419  1 case 2:
420  1 return INTEGER;
421  0 case 3:
422  0 return DATE;
423  0 case 4:
424  0 return LIST;
425  0 default:
426  0 throw new IllegalArgumentException("Unknown code : " + code);
427    }
428    }
429   
430    }
431   
432    /**
433    * Date format for fields of type DATE.
434    *
435    * PENDING: consider replacing the DATE Type with these 3 new data types.
436    */
 
437    public static enum DateFormat {
438   
439    DATE_TIME("full"), DATE("dateonly"), TIME("timeonly");
440   
441    final String code;
442   
 
443  0 toggle DateFormat(String code) {
444  0 this.code = code;
445    }
446   
447    }
448   
 
449    public static final class NameComparator implements Comparator<CustomField>, Serializable {
450    /**
451    *
452    */
453    private static final long serialVersionUID = 1L;
454   
455    private final Locale locale;
456   
 
457  1 toggle public NameComparator() {
458  1 this(null);
459    }
 
460  1 toggle public NameComparator(Locale locale) {
461  1 this.locale = locale;
462    }
463   
 
464  0 toggle public int compare(CustomField o1, CustomField o2) {
465  0 return new CompareToBuilder().append(
466    CustomFieldUtilities.getCustomFieldName(o1.getId(), locale),
467    CustomFieldUtilities.getCustomFieldName(o2.getId(), locale))
468    .append(o1.getId(), o2.getId())
469    .toComparison();
470    }
471   
472    }
473   
474    }