1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.objects.classes

File ListClass.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart7.png
64% of files have more coverage

Code metrics

130
285
45
2
881
572
125
0.44
6.33
22.5
2.78

Classes

Class Line # Actions
ListClass 53 275 0% 119 130
0.705882470.6%
ListClass.MapComparator 702 10 0% 6 6
0.666666766.7%
 

Contributing tests

This file is covered by 96 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.objects.classes;
21   
22    import java.util.ArrayList;
23    import java.util.Arrays;
24    import java.util.Collections;
25    import java.util.Comparator;
26    import java.util.HashMap;
27    import java.util.LinkedList;
28    import java.util.List;
29    import java.util.Map;
30   
31    import org.apache.commons.lang3.StringUtils;
32    import org.apache.ecs.xhtml.input;
33    import org.apache.ecs.xhtml.option;
34    import org.apache.ecs.xhtml.select;
35    import org.dom4j.Element;
36    import org.hibernate.collection.PersistentCollection;
37    import org.xwiki.model.reference.EntityReference;
38    import org.xwiki.xar.internal.property.ListXarObjectPropertySerializer;
39    import org.xwiki.xml.XMLUtils;
40   
41    import com.xpn.xwiki.XWikiContext;
42    import com.xpn.xwiki.doc.merge.MergeConfiguration;
43    import com.xpn.xwiki.doc.merge.MergeResult;
44    import com.xpn.xwiki.internal.xml.XMLAttributeValueFilter;
45    import com.xpn.xwiki.objects.BaseCollection;
46    import com.xpn.xwiki.objects.BaseProperty;
47    import com.xpn.xwiki.objects.DBStringListProperty;
48    import com.xpn.xwiki.objects.ListProperty;
49    import com.xpn.xwiki.objects.StringListProperty;
50    import com.xpn.xwiki.objects.StringProperty;
51    import com.xpn.xwiki.objects.meta.PropertyMetaClass;
52   
 
53    public abstract class ListClass extends PropertyClass
54    {
55    /**
56    * @since 6.2RC1
57    */
58    public static final String DISPLAYTYPE_INPUT = "input";
59   
60    /**
61    * @since 6.2RC1
62    */
63    public static final String DISPLAYTYPE_RADIO = "radio";
64   
65    /**
66    * @since 6.2RC1
67    */
68    public static final String DISPLAYTYPE_CHECKBOX = "checkbox";
69   
70    /**
71    * @since 6.2RC1
72    */
73    public static final String DISPLAYTYPE_SELECT = "select";
74   
75    /**
76    * Default separator/delimiter to use to split or join a list stored as a string. Not to be confused with
77    * {@link #getSeparator()} and {@link #getSeparator()} which are used only for UI view and edit operations.
78    *
79    * @since 7.0M2
80    */
81    public static final String DEFAULT_SEPARATOR = "|";
82   
83    /**
84    * Used to escape a separator character inside a string serialized list item.
85    *
86    * @since 7.0M2
87    */
88    public static final char SEPARATOR_ESCAPE = '\\';
89   
90    private static final String XCLASSNAME = "list";
91   
92    /**
93    * Regex used to split lists stored in a string. Supports escaped separators inside values. The individually
94    * regex-escaped separators string needs to be passed as parameter.
95    */
96    private static final String LIST_ITEM_SEPARATOR_REGEX_FORMAT = "(?<!\\\\)[%s]";
97   
98    /**
99    * Regex used to unescape separators inside the actual values of the list. The individually regex-escaped separators
100    * string needs to be passed as parameter.
101    */
102    private static final String ESCAPED_SEPARATORS_REGEX_FORMAT = "\\%s([%s])";
103   
104    /**
105    * Regex used to find unescaped separators in a list item's value. Regex-escaped separators string needs to be
106    * passed as parameter.
107    */
108    private static final String UNESCAPED_SEPARATORS_REGEX_FORMAT = "([%s])";
109   
110    /**
111    * Replacement string used to escaped a separator found by the String.replace regex.
112    */
113    private static final String UNESCAPED_SEPARATOR_REPLACEMENT = String.format("\\%s$1", SEPARATOR_ESCAPE);
114   
 
115  10285 toggle public ListClass(String name, String prettyname, PropertyMetaClass wclass)
116    {
117  10285 super(name, prettyname, wclass);
118  10285 setRelationalStorage(false);
119  10285 setDisplayType(DISPLAYTYPE_SELECT);
120  10285 setMultiSelect(false);
121  10285 setSize(1);
122  10285 setSeparator(" ");
123  10285 setCache(false);
124    }
125   
 
126  0 toggle public ListClass(PropertyMetaClass wclass)
127    {
128  0 this(XCLASSNAME, "List", wclass);
129    }
130   
 
131  0 toggle public ListClass()
132    {
133  0 this(null);
134    }
135   
136    /**
137    * @return a string of separator characters used to split/deserialize an input string coming from the UI (filled by
138    * the user) that represents a serialized list
139    * @see #displayEdit(StringBuffer, String, String, BaseCollection, XWikiContext)
140    * @see #fromString(String)
141    */
 
142  272 toggle public String getSeparators()
143    {
144  272 String separators = getStringValue("separators");
145  272 if (separators == null || separators.equals("")) {
146  17 separators = "|,";
147    }
148  272 return separators;
149    }
150   
151    /**
152    * @param separators a string of characters used to split/deserialize an input string coming from the UI (filled by
153    * the user) that represents a serialized list
154    */
 
155  8099 toggle public void setSeparators(String separators)
156    {
157  8099 setStringValue("separators", separators);
158    }
159   
 
160  947 toggle public String getDisplayType()
161    {
162  947 return getStringValue("displayType");
163    }
164   
 
165  11830 toggle public void setDisplayType(String type)
166    {
167  11830 setStringValue("displayType", type);
168    }
169   
 
170  705 toggle public String getSort()
171    {
172  705 return getStringValue("sort");
173    }
174   
 
175  3 toggle public void setSort(String sort)
176    {
177  3 setStringValue("sort", sort);
178    }
179   
 
180  300 toggle public int getSize()
181    {
182  300 return getIntValue("size");
183    }
184   
 
185  13690 toggle public void setSize(int size)
186    {
187  13690 setIntValue("size", size);
188    }
189   
 
190  195 toggle public boolean isCache()
191    {
192  195 return (getIntValue("cache") == 1);
193    }
194   
 
195  10285 toggle public void setCache(boolean cache)
196    {
197  10285 setIntValue("cache", cache ? 1 : 0);
198    }
199   
 
200  50457 toggle public boolean isMultiSelect()
201    {
202  50456 return (getIntValue("multiSelect") == 1);
203    }
204   
 
205  12130 toggle public void setMultiSelect(boolean multiSelect)
206    {
207  12130 setIntValue("multiSelect", multiSelect ? 1 : 0);
208    }
209   
 
210  25294 toggle public boolean isRelationalStorage()
211    {
212  25294 return (getIntValue("relationalStorage") == 1);
213    }
214   
 
215  11862 toggle public void setRelationalStorage(boolean storage)
216    {
217  11819 setIntValue("relationalStorage", storage ? 1 : 0);
218    }
219   
 
220  46 toggle public boolean isPicker()
221    {
222  46 return (getIntValue("picker") == 1);
223    }
224   
 
225  1072 toggle public void setPicker(boolean picker)
226    {
227  1072 setIntValue("picker", picker ? 1 : 0);
228    }
229   
230    /**
231    * @return a string (usually just 1 character long) used to join this list's items when displaying it in the UI in
232    * view mode.
233    * @see #displayView(StringBuffer, String, String, BaseCollection, XWikiContext)
234    */
 
235  118 toggle public String getSeparator()
236    {
237  118 return getStringValue("separator");
238    }
239   
240    /**
241    * @param separator a string (usually just 1 character long) used to join this list's items when displaying it in
242    * the UI in view mode.
243    */
 
244  10362 toggle public void setSeparator(String separator)
245    {
246  10362 setStringValue("separator", separator);
247    }
248   
249    /**
250    * Convenience method, using {@value #DEFAULT_SEPARATOR} as separator and parsing key=value items.
251    *
252    * @param value the string holding a serialized list
253    * @return the list that was stored in the input string
254    * @see #getListFromString(String, String, boolean)
255    */
 
256  565 toggle public static List<String> getListFromString(String value)
257    {
258  565 return getListFromString(value, null, true);
259    }
260   
261    /**
262    * @param value the string holding a serialized list
263    * @param separators the separator characters (given as a string) used to delimit the list's items inside the input
264    * string. These separators can also be present, in escaped ({@value #SEPARATOR_ESCAPE}) form, inside
265    * list items
266    * @param withMap set to true if the list's values contain map entries (key=value pairs) that should also be parsed.
267    * Only the keys are extracted from such list items
268    * @return the list that was stored in the input string
269    */
 
270  1648 toggle public static List<String> getListFromString(String value, String separators, boolean withMap)
271    {
272  1648 List<String> list = new ArrayList<String>();
273  1648 if (value == null) {
274  0 return list;
275    }
276  1648 if (separators == null) {
277  565 separators = DEFAULT_SEPARATOR;
278    }
279   
280    // Escape the list of separators individually to be safely used in regexes.
281  1648 String regexEscapedSeparatorsRegexPart =
282    SEPARATOR_ESCAPE + StringUtils.join(separators.toCharArray(), SEPARATOR_ESCAPE);
283   
284  1648 String escapedSeparatorsRegex =
285    String.format(ESCAPED_SEPARATORS_REGEX_FORMAT, SEPARATOR_ESCAPE, regexEscapedSeparatorsRegexPart);
286   
287    // Split the values and process each list item.
288  1648 String listItemSeparatorRegex =
289    String.format(LIST_ITEM_SEPARATOR_REGEX_FORMAT, regexEscapedSeparatorsRegexPart);
290  1648 String[] elements = value.split(listItemSeparatorRegex);
291  1648 for (String element : elements) {
292    // Adjacent separators are treated as one separator.
293  2506 if (StringUtils.isBlank(element)) {
294  700 continue;
295    }
296   
297    // Unescape any escaped separator in the individual list item.
298  1806 String unescapedElement = element.replaceAll(escapedSeparatorsRegex, "$1");
299  1806 String item = unescapedElement;
300   
301    // Check if it is a map entry, i.e. "key=value"
302  1806 if (withMap && (unescapedElement.indexOf('=') != -1)) {
303    // Get just the key, ignore the value/label.
304  33 item = StringUtils.split(unescapedElement, '=')[0];
305    }
306   
307    // Ignore empty items.
308  1806 if (StringUtils.isNotBlank(item.trim())) {
309  1806 list.add(item);
310    }
311    }
312   
313  1648 return list;
314    }
315   
316    /**
317    * Convenience method, using {@value #DEFAULT_SEPARATOR} as separator.
318    *
319    * @param list the list to serialize
320    * @return a string representing a serialized list, delimited by the first separator character (from the ones inside
321    * the separators string). Separators inside list items are safely escaped ({@value #SEPARATOR_ESCAPE}).
322    * @see #getStringFromList(List, String)
323    */
 
324  2 toggle public static String getStringFromList(List<String> list)
325    {
326  2 return getStringFromList(list, null);
327    }
328   
329    /**
330    * @param list the list to serialize
331    * @param separators the separator characters (given as a string) used when the list was populated with values. The
332    * list's items can contain these separators in plain/unescaped form. The first separator character will
333    * be used to join the list in the output.
334    * @return a string representing a serialized list, delimited by the first separator character (from the ones inside
335    * the separators string). Separators inside list items are safely escaped ({@value #SEPARATOR_ESCAPE}).
336    */
 
337  776 toggle public static String getStringFromList(List<String> list, String separators)
338    {
339  776 if ((list instanceof PersistentCollection) && (!((PersistentCollection) list).wasInitialized())) {
340  0 return "";
341    }
342   
343  776 if (separators == null) {
344  2 separators = DEFAULT_SEPARATOR;
345    }
346   
347    // Escape the list of separators individually to be safely used in regexes.
348  776 String regexEscapedSeparatorsRegexPart =
349    SEPARATOR_ESCAPE + StringUtils.join(separators.toCharArray(), SEPARATOR_ESCAPE);
350   
351  776 String unescapedSeparatorsRegex =
352    String.format(UNESCAPED_SEPARATORS_REGEX_FORMAT, regexEscapedSeparatorsRegexPart);
353   
354  776 List<String> escapedValues = new ArrayList<String>();
355  776 for (String value : list) {
356  236 String escapedValue = value.replaceAll(unescapedSeparatorsRegex, UNESCAPED_SEPARATOR_REPLACEMENT);
357  236 escapedValues.add(escapedValue);
358    }
359   
360    // Use the first separator to join the list.
361  776 return StringUtils.join(escapedValues, separators.charAt(0));
362    }
363   
 
364  2693 toggle public static Map<String, ListItem> getMapFromString(String value)
365    {
366  2693 Map<String, ListItem> map = new HashMap<String, ListItem>();
367  2693 if (value == null) {
368  0 return map;
369    }
370   
371  2693 String val = StringUtils.replace(value, SEPARATOR_ESCAPE + DEFAULT_SEPARATOR, "%PIPE%");
372  2693 String[] result = StringUtils.split(val, "|");
373  2693 for (String element2 : result) {
374  6739 String element = StringUtils.replace(element2, "%PIPE%", DEFAULT_SEPARATOR);
375  6739 if (element.indexOf('=') != -1) {
376  166 String[] data = StringUtils.split(element, "=");
377  166 map.put(data[0], new ListItem(data[0], data[1]));
378    } else {
379  6573 map.put(element, new ListItem(element, element));
380    }
381    }
382  2693 return map;
383    }
384   
385    /**
386    * Used in {@link #displayEdit(StringBuffer, String, String, BaseCollection, XWikiContext)}.
387    *
388    * @param property a property to be used in an form input
389    * @return the text value to be used in an form input. If a {@link ListProperty} is passed, the list's separators
390    * defined by {@link #getSeparators()} are escaped for each list item and the items are joined by the first
391    * separator
392    * @see #getStringFromList(List, String)
393    */
 
394  29 toggle public String toFormString(BaseProperty property)
395    {
396  29 String result;
397  29 if (property instanceof ListProperty) {
398  29 ListProperty listProperty = (ListProperty) property;
399  29 result = ListClass.getStringFromList(listProperty.getList(), getSeparators());
400    } else {
401  0 result = property.toText();
402    }
403  29 return result;
404    }
405   
 
406  25204 toggle @Override
407    public BaseProperty newProperty()
408    {
409  25204 BaseProperty lprop;
410   
411  25203 if (isRelationalStorage() && isMultiSelect()) {
412  304 lprop = new DBStringListProperty();
413  24896 } else if (isMultiSelect()) {
414  99 lprop = new StringListProperty();
415    } else {
416  24797 lprop = new StringProperty();
417    }
418   
419  25202 return lprop;
420    }
421   
 
422  24887 toggle @Override
423    public BaseProperty fromString(String value)
424    {
425  24887 BaseProperty prop = newProperty();
426  24885 if (isMultiSelect()) {
427  217 ((ListProperty) prop).setList(getListFromString(value, getSeparators(), false));
428    } else {
429  24667 prop.setValue(value);
430    }
431  24885 return prop;
432    }
433   
 
434  107 toggle @Override
435    public BaseProperty fromStringArray(String[] strings)
436    {
437  107 if (!isMultiSelect()) {
438  78 return fromString(strings[0]);
439    }
440  29 BaseProperty prop = newProperty();
441  29 if (prop instanceof StringProperty) {
442  0 return fromString(strings[0]);
443    }
444   
445  29 List<String> list = new ArrayList<String>();
446   
447  29 if (strings.length == 0) {
448  0 return prop;
449    }
450   
451  29 if ((strings.length == 1) && (getDisplayType().equals(DISPLAYTYPE_INPUT) || isMultiSelect())) {
452  25 ((ListProperty) prop).setList(getListFromString(strings[0], getSeparators(), false));
453  25 return prop;
454    }
455   
456    // If Multiselect and multiple results
457  4 for (String item : strings) {
458  15 if (!item.trim().equals("")) {
459  5 list.add(item);
460    }
461    }
462   
463    // setList will copy the list, so this call must be made last.
464  4 ((ListProperty) prop).setList(list);
465   
466  4 return prop;
467    }
468   
 
469  0 toggle @Override
470    public BaseProperty newPropertyfromXML(Element ppcel)
471    {
472  0 if (!isMultiSelect()) {
473  0 return super.newPropertyfromXML(ppcel);
474    }
475   
476  0 @SuppressWarnings("unchecked")
477    List<Element> elist = ppcel.elements(ListXarObjectPropertySerializer.ELEMENT_VALUE);
478  0 BaseProperty lprop = newProperty();
479   
480  0 if (lprop instanceof ListProperty) {
481  0 List<String> llist = ((ListProperty) lprop).getList();
482  0 for (int i = 0; i < elist.size(); i++) {
483  0 Element el = elist.get(i);
484  0 llist.add(el.getText());
485    }
486    } else {
487  0 for (int i = 0; i < elist.size(); i++) {
488  0 Element el = elist.get(i);
489  0 ((StringProperty) lprop).setValue(el.getText());
490    }
491    }
492  0 return lprop;
493    }
494   
495    /**
496    * Search for an internationalizable display text for the current value. The search process is:
497    * <ol>
498    * <li>let V = the internal value of the option, used as the "value" attribute of the {@code <option>} element, and
499    * D = the displayed value</li>
500    * <li>if a message with the key {@code <fieldFullName>_<V>} exists, return it as D</li>
501    * <li>else, if a message with the key {@code option_<fieldName>_<V>} exists, return it as D</li>
502    * <li>else, if a message with the key {@code option_<V>} exists, return it as D</li>
503    * <li>else, D can be specified in the values parameter of the property by using V=D</li>
504    * <li>else return V</li>
505    * </ol>
506    *
507    * @param value The internal value.
508    * @param name The name of the ListProperty.
509    * @param map The value=name mapping specified in the "values" parameter of the property.
510    * @param context The request context.
511    * @return The text that should be displayed, representing a human-understandable name for the internal value.
512    */
 
513  1101 toggle protected String getDisplayValue(String value, String name, Map<String, ListItem> map, XWikiContext context)
514    {
515  1101 ListItem item = map.get(value);
516  1101 String displayValue;
517  1101 if (item == null) {
518  112 displayValue = value;
519    } else {
520  989 displayValue = item.getValue();
521    }
522  1101 if ((context == null) || (context.getWiki() == null)) {
523  12 return displayValue;
524    }
525  1089 String msgname = getFieldFullName() + "_" + value;
526  1089 String newresult = localizePlain(msgname);
527  1089 if (newresult == null) {
528  987 msgname = "option_" + name + "_" + value;
529  987 newresult = localizePlain(msgname);
530  987 if (newresult == null) {
531  987 msgname = "option_" + value;
532  987 newresult = localizePlain(msgname);
533  987 if (newresult == null) {
534  987 newresult = displayValue;
535    }
536    }
537    }
538  1089 return newresult;
539    }
540   
541    /**
542    * Search for an internationalizable display text for the current value. The value can be either a simple string, or
543    * a value=name pair selected from the database.
544    *
545    * @see #getDisplayValue(String, String, Map, XWikiContext)
546    * @param rawvalue The internal value, or a value=name pair.
547    * @param name The name of the ListProperty.
548    * @param map The value=name mapping specified in the "values" parameter of the property.
549    * @param context The request context.
550    * @return The text that should be displayed, representing a human-understandable name for the internal value.
551    */
 
552  51 toggle protected String getDisplayValue(Object rawvalue, String name, Map<String, ListItem> map, XWikiContext context)
553    {
554  51 if (rawvalue == null) {
555  0 return "";
556    }
557  51 if (rawvalue instanceof Object[]) {
558  0 return ((Object[]) rawvalue)[1].toString();
559    }
560  51 return getDisplayValue(rawvalue.toString(), name, map, context);
561    }
562   
563    /**
564    * If the list is populated with value=name pairs selected from the database, then return only the value. Otherwise,
565    * it is a simple value.
566    *
567    * @param rawvalue
568    * @return The list value
569    */
 
570  987 toggle protected String getElementValue(Object rawvalue)
571    {
572  987 if (rawvalue == null) {
573  0 return "";
574    }
575  987 if (rawvalue instanceof Object[]) {
576  0 return ((Object[]) rawvalue)[0].toString();
577    }
578  987 return rawvalue.toString();
579    }
580   
 
581  1 toggle @Override
582    public void displayHidden(StringBuffer buffer, String name, String prefix, BaseCollection object,
583    XWikiContext context)
584    {
585  1 input input = new input();
586  1 input.setAttributeFilter(new XMLAttributeValueFilter());
587  1 BaseProperty prop = (BaseProperty) object.safeget(name);
588  1 if (prop != null) {
589  1 input.setValue(prop.toText());
590    }
591   
592  1 input.setType("hidden");
593  1 input.setName(prefix + name);
594  1 input.setID(prefix + name);
595  1 buffer.append(input.toString());
596    }
597   
 
598  71 toggle @Override
599    public void displayView(StringBuffer buffer, String name, String prefix, BaseCollection object, XWikiContext context)
600    {
601  71 List<String> selectlist;
602  71 String separator = getSeparator();
603  71 BaseProperty prop = (BaseProperty) object.safeget(name);
604  71 Map<String, ListItem> map = getMap(context);
605   
606    // Skip unset values.
607  71 if (prop == null) {
608  6 return;
609    }
610   
611  65 if (prop instanceof ListProperty) {
612  28 selectlist = ((ListProperty) prop).getList();
613  28 List<String> newlist = new ArrayList<String>();
614  28 for (String value : selectlist) {
615  24 newlist.add(getDisplayValue(value, name, map, context));
616    }
617  28 buffer.append(StringUtils.join(newlist, separator));
618    } else {
619  37 buffer.append(getDisplayValue(prop.getValue(), name, map, context));
620    }
621    }
622   
 
623  0 toggle @Override
624    public void displayEdit(StringBuffer buffer, String name, String prefix, BaseCollection object, XWikiContext context)
625    {
626  0 if (getDisplayType().equals(DISPLAYTYPE_INPUT)) {
627  0 input input = new input();
628  0 input.setAttributeFilter(new XMLAttributeValueFilter());
629  0 BaseProperty prop = (BaseProperty) object.safeget(name);
630  0 if (prop != null) {
631  0 input.setValue(this.toFormString(prop));
632    }
633  0 input.setType("text");
634  0 input.setSize(getSize());
635  0 input.setName(prefix + name);
636  0 input.setID(prefix + name);
637  0 input.setDisabled(isDisabled());
638  0 buffer.append(input.toString());
639  0 } else if (getDisplayType().equals(DISPLAYTYPE_RADIO) || getDisplayType().equals(DISPLAYTYPE_CHECKBOX)) {
640  0 displayRadioEdit(buffer, name, prefix, object, context);
641    } else {
642  0 displaySelectEdit(buffer, name, prefix, object, context);
643    }
644   
645  0 if (!getDisplayType().equals(DISPLAYTYPE_INPUT)) {
646  0 org.apache.ecs.xhtml.input hidden = new input(input.hidden, prefix + name, "");
647  0 hidden.setAttributeFilter(new XMLAttributeValueFilter());
648  0 buffer.append(hidden);
649    }
650    }
651   
 
652  2 toggle protected void displayRadioEdit(StringBuffer buffer, String name, String prefix, BaseCollection object,
653    XWikiContext context)
654    {
655  2 List<String> list = getList(context);
656  2 Map<String, ListItem> map = getMap(context);
657   
658  2 BaseProperty prop = (BaseProperty) object.safeget(name);
659  2 List<String> selectlist = toList(prop);
660   
661    // Add the selected values that are not in the predefined list.
662  2 for (String item : selectlist) {
663    // The empty value means no selection when it's not in the predefined list. Both the radio and the checkbox
664    // input support empty selection (unlike the select input which automatically selects the first value when
665    // single selection is on) so we don't have to generate a radio/checkbox for the empty value.
666  2 if (!StringUtils.isEmpty(item) && !list.contains(item)) {
667  0 list.add(item);
668    }
669    }
670   
671    // Add options from Set
672  2 int count = 0;
673  2 for (Object rawvalue : list) {
674  6 String value = getElementValue(rawvalue);
675  6 String display = XMLUtils.escape(getDisplayValue(rawvalue, name, map, context));
676  6 input radio =
677  6 new input((getDisplayType().equals(DISPLAYTYPE_RADIO) && !isMultiSelect()) ? input.radio
678    : input.checkbox, prefix + name, value);
679  6 radio.setAttributeFilter(new XMLAttributeValueFilter());
680  6 radio.setID("xwiki-form-" + name + "-" + object.getNumber() + "-" + count);
681  6 radio.setDisabled(isDisabled());
682   
683  6 if (selectlist.contains(value)) {
684  2 radio.setChecked(true);
685    }
686  6 radio.addElement(display);
687   
688  6 buffer.append("<label class=\"xwiki-form-listclass\" for=\"xwiki-form-" + XMLUtils.escape(name) + "-"
689    + object.getNumber() + "-" + count++ + "\">");
690  6 buffer.append(radio.toString());
691  6 buffer.append("</label>");
692    }
693   
694    // We need a hidden input with an empty value to be able to clear the selected values when no value is selected
695    // from the above radio/checkbox buttons.
696  2 org.apache.ecs.xhtml.input hidden = new input(input.hidden, prefix + name, "");
697  2 hidden.setAttributeFilter(new XMLAttributeValueFilter());
698  2 hidden.setDisabled(isDisabled());
699  2 buffer.append(hidden);
700    }
701   
 
702    protected class MapComparator implements Comparator<String>
703    {
704    protected Map<String, ListItem> map;
705   
 
706  18 toggle public MapComparator(Map<String, ListItem> map)
707    {
708  18 this.map = map;
709    }
710   
 
711  99 toggle @Override
712    public int compare(String o1, String o2)
713    {
714  99 ListItem s1 = this.map.get(o1);
715  99 ListItem s2 = this.map.get(o2);
716   
717  99 if ((s1 == null) && (s2 == null)) {
718  0 return 0;
719    }
720   
721  99 if (s1 == null) {
722  0 return -1;
723    }
724   
725  99 if (s2 == null) {
726  0 return 1;
727    }
728   
729  99 return s1.getValue().compareTo(s2.getValue());
730    }
731    }
732   
 
733  253 toggle protected void displaySelectEdit(StringBuffer buffer, String name, String prefix, BaseCollection object,
734    XWikiContext context)
735    {
736  253 select select = new select(prefix + name, 1);
737  253 select.setAttributeFilter(new XMLAttributeValueFilter());
738  253 select.setMultiple(isMultiSelect());
739  253 select.setSize(getSize());
740  253 select.setName(prefix + name);
741  253 select.setID(prefix + name);
742  253 select.setDisabled(isDisabled());
743   
744  253 List<String> list = getList(context);
745  253 Map<String, ListItem> map = getMap(context);
746   
747  253 String sort = getSort();
748  253 if (!"none".equals(sort)) {
749  219 if ("id".equals(sort)) {
750  0 Collections.sort(list);
751    }
752  219 if ("value".equals(sort)) {
753  18 Collections.sort(list, new MapComparator(map));
754    }
755    }
756   
757  253 List<String> selectlist = toList((BaseProperty) object.safeget(name));
758   
759    // Add the selected values that are not in the predefined list.
760  253 for (String item : selectlist) {
761  192 if (!list.contains(item)) {
762  51 list.add(item);
763    }
764    }
765   
766    // Add options from Set
767  253 for (String rawvalue : list) {
768  981 String value = getElementValue(rawvalue);
769  981 String display = getDisplayValue(rawvalue, name, map, context);
770  981 option option = new option(display, value);
771  981 option.setAttributeFilter(new XMLAttributeValueFilter());
772  981 option.addElement(XMLUtils.escape(display));
773  981 if (selectlist.contains(value)) {
774  192 option.setSelected(true);
775    }
776  981 select.addElement(option);
777    }
778   
779  253 buffer.append(select.toString());
780    }
781   
782    public abstract List<String> getList(XWikiContext context);
783   
784    public abstract Map<String, ListItem> getMap(XWikiContext context);
785   
786    /**
787    * {@link ListClass} does not produce only {@link ListProperty}s and this method allows to access the value as
788    * {@link List} whatever property is actually storing it.
789    * <p>
790    * There is no guarantees the returned {@link List} will be modifiable.
791    *
792    * @param property the property created by this class
793    * @return the {@link List} representation of this property
794    * @since 6.2M1
795    */
 
796  255 toggle public List<String> toList(BaseProperty<?> property)
797    {
798  255 List<String> list;
799   
800  255 if (property == null) {
801  40 list = Collections.emptyList();
802  215 } else if (property instanceof ListProperty) {
803  24 list = ((ListProperty) property).getList();
804    } else {
805  191 list = Arrays.asList(String.valueOf(property.getValue()));
806    }
807   
808  255 return list;
809    }
810   
811    /**
812    * Set the passed {@link List} into the passed property.
813    *
814    * @param property the property to modify
815    * @param list the list to set
816    * @since 6.2M1
817    */
 
818  0 toggle public void fromList(BaseProperty<?> property, List<String> list)
819    {
820  0 if (property instanceof ListProperty) {
821  0 ((ListProperty) property).setList(list);
822    } else {
823  0 property.setValue(list == null || list.isEmpty() ? null : list.get(0));
824    }
825    }
826   
 
827  0 toggle @Override
828    public <T extends EntityReference> void mergeProperty(BaseProperty<T> currentProperty,
829    BaseProperty<T> previousProperty, BaseProperty<T> newProperty, MergeConfiguration configuration,
830    XWikiContext context, MergeResult mergeResult)
831    {
832    // If it's not a multiselect then we don't have any special merge to do. We keep default StringProperty behavior
833  0 if (isMultiSelect()) {
834    // If not a free input assume it's not an ordered list
835  0 if (!DISPLAYTYPE_INPUT.equals(getDisplayType()) && currentProperty instanceof ListProperty) {
836  0 mergeNotOrderedListProperty(currentProperty, previousProperty, newProperty, configuration, context,
837    mergeResult);
838   
839  0 return;
840    }
841    }
842   
843    // Fallback on default ListProperty merging
844  0 super.mergeProperty(currentProperty, previousProperty, newProperty, configuration, context, mergeResult);
845    }
846   
 
847  0 toggle protected <T extends EntityReference> void mergeNotOrderedListProperty(BaseProperty<T> currentProperty,
848    BaseProperty<T> previousProperty, BaseProperty<T> newProperty, MergeConfiguration configuration,
849    XWikiContext context, MergeResult mergeResult)
850    {
851  0 List<String> currentList = new LinkedList<String>(toList(currentProperty));
852  0 List<String> previousList = toList(previousProperty);
853  0 List<String> newList = toList(newProperty);
854   
855    // Remove elements to remove
856  0 if (previousList != null) {
857  0 for (String element : previousList) {
858  0 if (newList == null || !newList.contains(element)) {
859  0 currentList.remove(element);
860  0 mergeResult.setModified(true);
861    }
862    }
863    }
864   
865    // Add missing elements
866  0 if (newList != null) {
867  0 for (String element : newList) {
868  0 if ((previousList == null || !previousList.contains(element))) {
869  0 if (!currentList.contains(element)) {
870  0 currentList.add(element);
871  0 mergeResult.setModified(true);
872    }
873    }
874    }
875    }
876   
877  0 fromList(currentProperty, currentList);
878   
879  0 return;
880    }
881    }