001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package com.sun.rave.web.ui.component;
042:
043: import com.sun.rave.web.ui.model.OptionTitle;
044: import com.sun.rave.web.ui.util.ThemeUtilities;
045: import java.beans.Beans;
046: import java.lang.reflect.Array;
047: import java.util.ArrayList;
048: import java.util.Collection;
049: import java.util.Iterator;
050: import java.util.Map;
051: import java.util.Properties;
052:
053: import javax.faces.FacesException;
054: import javax.faces.application.FacesMessage;
055: import javax.faces.component.UIComponent;
056: import javax.faces.context.FacesContext;
057: import javax.faces.convert.ConverterException;
058: import com.sun.rave.web.ui.model.Option;
059: import com.sun.rave.web.ui.model.OptionGroup;
060: import com.sun.rave.web.ui.model.Separator;
061: import com.sun.rave.web.ui.model.list.EndGroup;
062: import com.sun.rave.web.ui.model.list.ListItem;
063: import com.sun.rave.web.ui.model.list.StartGroup;
064: import com.sun.rave.web.ui.util.ConversionUtilities;
065: import com.sun.rave.web.ui.util.MessageUtil;
066: import com.sun.rave.web.ui.util.ValueType;
067: import com.sun.rave.web.ui.component.util.Util;
068: import javax.faces.component.ValueHolder;
069:
070: /**
071: *
072: * @author avk
073: */
074: public class ListSelector extends ListSelectorBase implements
075: ListManager {
076:
077: // If true, debugging statements are printed to stdout
078: private static final boolean DEBUG = false;
079:
080: // Holds the options for this component
081: protected ArrayList listItems = null;
082: private int separatorLength = 0;
083:
084: private static final String READONLY_ID = "_readOnly"; //NOI18N
085: private static final String LABEL_ID = "_label"; //NOI18N
086: private static final String READONLY_FACET = "readOnly"; //NOI18N
087: private static final String LABEL_FACET = "label";
088: public static final String VALUE_ID = "_list_value"; //NOI18N
089: public static final String LIST_ID = "_list"; //NOI18N
090:
091: /** Creates a new instance of ListSelector */
092: public ListSelector() {
093: }
094:
095: /**
096: * Check that this component has a valuebinding that matches the
097: * value of the "multiple" attribute.
098: * @param context The FacesContext of the request
099: */
100: public void checkSelectionModel(FacesContext context) {
101:
102: if (DEBUG) {
103: log("checkSelectionModel()"); //NOI18N
104: log("\tComponent multiple = "
105: + String.valueOf(isMultiple())); //NOI18N
106: log("\tValueType "
107: + valueTypeEvaluator.getValueType().toString()); //NOI18N
108: }
109:
110: if (isMultiple()
111: && valueTypeEvaluator.getValueType() != ValueType.ARRAY) {
112:
113: if (DEBUG)
114: log("\tMultiple selection enabled for non-array value");
115: Object[] params = { toString() };
116: String msg = MessageUtil.getMessage(
117: "com.sun.rave.web.ui.resources.LogMessages", //NOI18N
118: "Selector.multipleError", //NOI18N
119: params);
120: throw new RuntimeException(msg);
121: }
122: return;
123: }
124:
125: /**
126: * Retrieve an Iterator of ListSelector.ListItem, to be used by the
127: * renderer.
128: * @return an Iterator over {@link ListItem}.
129: */
130: public Iterator getListItems(FacesContext context,
131: boolean rulerAtEnd) throws FacesException {
132:
133: if (DEBUG)
134: log("getListItems()");
135:
136: listItems = new ArrayList();
137: separatorLength = 0;
138:
139: // Retrieve the current selections. If there are selected
140: // objects, mark the corresponding items as selected.
141: processOptions(getOptions());
142:
143: processSelections();
144:
145: return listItems.iterator();
146: }
147:
148: /**
149: * Retrieve an Iterator of ListSelector.ListItem, to be used when
150: * evaluting the list items. If the list items are needed by the
151: * renderer, use getListItems(context, rulerAtEnd) instead.
152: * @return an Iterator over {@link ListItem}.
153: */
154: public Iterator getListItems() throws FacesException {
155:
156: if (DEBUG)
157: log("getListItems()");
158: if (listItems != null) {
159: return listItems.iterator();
160: }
161:
162: listItems = new ArrayList();
163: processOptions(getOptions());
164: return listItems.iterator();
165: }
166:
167: /**
168: * This method resets the options. Use this only if you need to
169: * add or remove options after the component has been rendered once.
170: public void resetOptions() {
171: listItems = null;
172: }
173: */
174:
175: public int getSeparatorLength() {
176: return separatorLength;
177: }
178:
179: /**
180: * Processes the component's SelectItems. Constructs an ArrayList
181: * of Selector.Options.
182: *
183: * <ul>
184: * <li>General algorithm copied from the RI, except that I modified
185: * the class casts for readability. I don't think the algorithm is
186: * correct though, need to verify. </li>
187: * <li>The list of allowed data types must match the spec. </li>
188: * <li>This code will have to be replaced when switching
189: * to Selection.</li>
190: * </ul>
191: */
192:
193: protected Option[] getOptions() {
194:
195: Option[] options = null;
196: Object optionsObject = getItems();
197:
198: // TODO - add some error reporting...
199:
200: if (optionsObject instanceof Option[]) {
201: options = (Option[]) optionsObject;
202: } else if (optionsObject instanceof Collection) {
203: Object[] objects = ((Collection) optionsObject).toArray();
204: if (objects == null || objects.length == 0) {
205: options = new Option[0];
206: }
207:
208: int numObjects = objects.length;
209: options = new Option[numObjects];
210: for (int counter = 0; counter < numObjects; ++counter) {
211: options[counter] = (Option) objects[counter];
212: }
213: } else if (optionsObject instanceof Map) {
214: Collection itemsCollection = ((Map) optionsObject).values();
215: Option[] newOptions = new Option[itemsCollection.size()];
216: options = (Option[]) itemsCollection.toArray(newOptions);
217: }
218: // The items attribute has not been specified
219: else {
220: // do nothing
221: options = new Option[0];
222: }
223: return options;
224: }
225:
226: protected void processOptions(Option[] options) {
227:
228: if (DEBUG)
229: log("processOptions()");
230: int length = options.length;
231:
232: for (int counter = 0; counter < length; ++counter) {
233:
234: if (options[counter] instanceof OptionGroup) {
235:
236: OptionGroup selectionGroup = (OptionGroup) options[counter];
237: String groupLabel = selectionGroup.getLabel();
238:
239: if (DEBUG) {
240: log("\tFound SelectionGroup"); //NOI18N
241: log("\tLabel is " + groupLabel); //NOI18N
242: }
243:
244: // <RAVE>
245: // if((groupLabel.length() * 1.5) > separatorLength) {
246: // </RAVE>
247: if (groupLabel != null
248: && (groupLabel.length() * 1.5) > separatorLength) {
249: // FIXME - needs to be dependent on the
250: // browser if not the OS... ARRGGH.
251: separatorLength = (int) (groupLabel.length() * 1.5);
252: }
253:
254: listItems.add(new StartGroup(groupLabel));
255: processOptions(selectionGroup.getOptions());
256: listItems.add(new EndGroup());
257: } else if (options[counter] instanceof Separator) {
258: listItems.add(options[counter]);
259: } else {
260: listItems.add(createListItem(options[counter]));
261: }
262: }
263: }
264:
265: /**
266: * Retrieve the current selections and compare them with the list
267: * items.
268: */
269: protected void processSelections() {
270:
271: if (DEBUG)
272: log("processSelections()");
273:
274: // For the "immediate" case:
275: Object value = getSubmittedValue();
276:
277: if (value != null) {
278:
279: if (DEBUG)
280: log("Found submitted value");
281:
282: if (value instanceof String[]) {
283:
284: if (DEBUG)
285: log("found submitted value (string array)");
286:
287: String[] obj = (String[]) value;
288: ArrayList list = new ArrayList(obj.length);
289: for (int counter = 0; counter < obj.length; ++counter) {
290: list.add(obj[counter]);
291: if (DEBUG)
292: log("\tAdded " + obj[counter]);
293: }
294: markSelectedListItems(list, false);
295: return;
296: }
297:
298: throw new IllegalArgumentException(
299: "Illegal submitted value"); //NOI18N
300: }
301:
302: // For the first time and "non-immediate" case:
303: if (DEBUG)
304: log("No submitted values, use actual value");
305:
306: // Covers List cases
307: if (valueTypeEvaluator.getValueType() == ValueType.NONE
308: || valueTypeEvaluator.getValueType() == ValueType.INVALID) {
309: if (DEBUG)
310: log("\tNo value");
311: markSelectedListItems(new ArrayList(), true);
312: return;
313: }
314:
315: value = getValue();
316:
317: if (DEBUG) {
318: if (value == null)
319: log("\t actual value is null"); //NOI18N
320: else
321: log("\t actual value is of type " + //NOI18N
322: value.getClass().getName());
323: }
324: if (value == null) {
325: if (DEBUG)
326: log("\tNo value");
327: markSelectedListItems(new ArrayList(), true);
328: return;
329: }
330:
331: // Covers List cases
332: /*
333: if(valueTypeEvaluator.getValueType() == ValueType.LIST) {
334: if(DEBUG) log("found actual value (list)");
335:
336: Object[] params = { toString() };
337: String msg =
338: ThemeUtilities.getTheme(FacesContext.getCurrentInstance()).
339: getMessage("ListSelector.multipleError", params); //NOI18N
340: throw new IllegalArgumentException(msg);
341:
342: //markSelectedOptions((java.util.List)value, true);
343: //return;
344: }
345: */
346:
347: ArrayList list = new ArrayList();
348:
349: // Covers Object array
350: if (valueTypeEvaluator.getValueType() == ValueType.ARRAY) {
351:
352: int length = Array.getLength(value);
353: for (int counter = 0; counter < length; ++counter) {
354: list.add(Array.get(value, counter));
355: if (DEBUG)
356: log(String.valueOf(Array.get(value, counter)));
357: }
358: markSelectedListItems(list, true);
359: return;
360: }
361:
362: // Covers Object array
363: list.add(value);
364: if (DEBUG)
365: log("\tAdded object " + String.valueOf(value));
366: markSelectedListItems(list, true);
367: return;
368: }
369:
370: /**
371: * Marks options corresponding to objects listed as values of this
372: * components as selected.
373: * @param list A list representation of the selected values
374: * @param processed If true, compare the values object by
375: * object (this is done if we compare the value of the object with
376: * with the list items). If false, perform a string comparison of
377: * the string representation of the submitted value of the
378: * component with the string representation of the value from the
379: * list items (this is done if we compare the submitted values
380: * with the list items). */
381: protected void markSelectedListItems(java.util.List list,
382: boolean processed) {
383:
384: if (DEBUG)
385: log("markSelectedListItems()");
386:
387: ListItem option = null;
388: Object nextItem = null;
389: Iterator items = listItems.iterator();
390: Iterator selected = null;
391:
392: while (items.hasNext()) {
393: nextItem = items.next();
394: // If the next item is a selection group, we continue.
395: // Need to check this with the guidelines, perhaps
396: // you can select options too...
397: if (!(nextItem instanceof ListItem)) {
398: continue;
399: }
400:
401: option = (ListItem) nextItem;
402:
403: // By default, the option will not be marked as selected
404: option.setSelected(false);
405:
406: if (DEBUG) {
407: log("\tItem value: " + option.getValue()); //NOI18N
408: log("\tItem type: " + //NOI18N
409: option.getValueObject().getClass().getName());
410: }
411:
412: // There are no more selected items, continue with the
413: // next option
414: if (list.isEmpty()) {
415: if (DEBUG)
416: log("No more selected items"); //NOI18N
417: continue;
418: }
419:
420: // There are still selected items to account for
421: selected = list.iterator();
422: while (selected.hasNext()) {
423: if (processed) {
424: Object o = selected.next();
425: if (DEBUG) {
426: log("\tSelected object value: " + //NOI18N
427: String.valueOf(o));
428: log("\tSelected object type: " + //NOI18N
429: o.getClass().getName());
430: }
431: if (option.getValueObject().equals(o)) {
432: if (DEBUG) {
433: log("\tFound a match: " + //NOI18N
434: String.valueOf(o));
435: }
436: option.setSelected(true);
437: list.remove(o);
438: break;
439: }
440: } else {
441: String s = (String) selected.next();
442: if (s.equals(option.getValue())) {
443: if (DEBUG) {
444: log("\tFound a match: " + s); //NOI18N
445: }
446: option.setSelected(true);
447: list.remove(s);
448: break;
449: }
450: }
451: }
452: }
453:
454: // At this point the selected list should be empty.
455: if (!list.isEmpty() && !Beans.isDesignTime()) {
456: String msg = MessageUtil.getMessage(
457: "com.sun.rave.web.ui.resources.LogMessages", //NOI18N
458: "List.badValue",
459: new Object[] { getClientId(FacesContext
460: .getCurrentInstance()) });
461: //throw new FacesException(msg);
462: log(msg);
463: }
464: }
465:
466: /* Add an option to the list */
467: protected ListItem createListItem(Option si) {
468:
469: if (DEBUG)
470: log("createListItem()");
471:
472: String label = si.getLabel();
473:
474: String valueString = ConversionUtilities.convertValueToString(
475: this , si.getValue());
476:
477: if (label == null)
478: label = valueString;
479:
480: if ((label.length() * 1.5) > separatorLength) {
481: separatorLength = (int) (label.length() * 1.5);
482: }
483:
484: ListItem listItem = new ListItem(si.getValue(), label, si
485: .getDescription(), si.isDisabled());
486:
487: listItem.setValue(valueString);
488: if (si instanceof OptionTitle)
489: listItem.setTitle(true);
490: return listItem;
491:
492: }
493:
494: // Labels
495: /**
496: * Return a component that implements the label for this ListSelector.
497: * If a facet named <code>label</code> is found
498: * that component is returned. Otherwise a <code>Label</code> component
499: * is returned. It is assigned the id</br>
500: * <code>getId() + "_label"</code></br>
501: * <p>
502: * If the facet is not defined then the returned <code>Label</code>
503: * component is re-intialized every time this method is called.
504: * </p>
505: *
506: * @return a label component for this ListSelector
507: */
508: public UIComponent getLabelComponent() {
509:
510: if (DEBUG)
511: log("getLabelComponent()");
512:
513: String id = getId();
514:
515: // Check if the page author has defined a label facet
516: UIComponent labelComponent = getFacet(LABEL_FACET); //NOI18N
517:
518: // If the page author has not defined a label facet,
519: // check if the page author specified a label.
520: if (labelComponent == null && getLabel() != null
521: && getLabel().length() > 0) {
522: labelComponent = createLabel(getLabel()); //NOI18N
523: } else if (DEBUG) {
524: log("\tFound facet."); //NOI18N
525: }
526:
527: return labelComponent;
528: }
529:
530: // Readonly value
531: public UIComponent getReadOnlyValueComponent() {
532:
533: if (DEBUG)
534: log("getListLabelComponent()");
535:
536: String id = getId();
537:
538: //<RAVE>
539: FacesContext context = FacesContext.getCurrentInstance();
540: String readOnlyValue = getValueAsReadOnly(context);
541:
542: //Check if the page author has defined a readonly facet,
543: //or if we've stored one
544: UIComponent textComponent = getFacet(READONLY_FACET); //NOI18N
545:
546: if (textComponent == null) {
547: textComponent = createText(readOnlyValue); //NOI18N
548: } else {
549: if (DEBUG) {
550: log("\tFound facet."); //NOI18N
551: }
552: modifyReadOnlyTextComponent(textComponent, readOnlyValue);
553: }
554: //</RAVE>
555:
556: return textComponent;
557: }
558:
559: /**
560: * Get the value (the object representing the selection(s)) of this
561: * component as a String array.
562: *
563: * @param context The FacesContext of the request
564: */
565: public String[] getValueAsStringArray(FacesContext context) {
566:
567: String[] values = null;
568:
569: Object value = getSubmittedValue();
570: if (value != null) {
571: if (value instanceof String[]) {
572: return (String[]) value;
573: } else if (value instanceof String) {
574: values = new String[1];
575: values[0] = (String) value;
576: return values;
577: }
578: }
579:
580: value = getValue();
581: if (value == null) {
582: return new String[0];
583: }
584:
585: // No submitted value found - look for
586:
587: if (valueTypeEvaluator.getValueType() == ValueType.NONE) {
588: return new String[0];
589: }
590:
591: if (valueTypeEvaluator.getValueType() == ValueType.INVALID) {
592: return new String[0];
593: }
594:
595: int counter = 0;
596:
597: if (valueTypeEvaluator.getValueType() == ValueType.LIST) {
598:
599: java.util.List list = (java.util.List) value;
600: counter = list.size();
601: values = new String[counter];
602:
603: Iterator valueIterator = ((java.util.List) value)
604: .iterator();
605: String valueString = null;
606:
607: while (valueIterator.hasNext()) {
608: valueString = ConversionUtilities.convertValueToString(
609: this , valueIterator.next());
610: values[counter] = valueString;
611: counter++;
612: }
613: } else if (valueTypeEvaluator.getValueType() == ValueType.ARRAY) {
614:
615: counter = Array.getLength(value);
616: values = new String[counter];
617: Object valueObject = null;
618: String valueString = null;
619:
620: for (int i = 0; i < counter; ++i) {
621: valueObject = Array.get(value, i);
622: valueString = ConversionUtilities.convertValueToString(
623: this , valueObject);
624: values[i] = valueString;
625: }
626: } else if (valueTypeEvaluator.getValueType() == ValueType.OBJECT) {
627:
628: values = new String[1];
629: values[0] = ConversionUtilities.convertValueToString(this ,
630: value);
631: }
632:
633: return values;
634: }
635:
636: private UIComponent createLabel(String labelString) {
637:
638: if (DEBUG)
639: log("createLabel()");
640:
641: // If we find a label, define a component and add it to the
642: // children, unless it has been added in a previous cycle
643: // (the component is being redisplayed).
644:
645: if (labelString == null || labelString.length() < 1) {
646: if (DEBUG)
647: log("\tNo label");
648: return null;
649: } else if (DEBUG) {
650: log("\tLabel is " + labelString); //NOI18N
651: }
652:
653: Label label = new Label();
654: label.setId(getId().concat(LABEL_ID));
655: label.setLabelLevel(getLabelLevel());
656: label.setText(labelString);
657: label.setLabeledComponent(this );
658: if (DEBUG)
659: log("Id of component is " + label.getId());
660: // <RAVE>
661: // this.getFacets().put(LABEL_FACET, label);
662: if (!Beans.isDesignTime())
663: this .getFacets().put(LABEL_FACET, label);
664: // </RAVE>
665: return label;
666: }
667:
668: private UIComponent createText(String string) {
669:
670: if (DEBUG)
671: log("createText()");
672:
673: // <RAVE>
674: // define a component, bring it up to date, and add it as a facet
675: StaticText text = new StaticText();
676: modifyReadOnlyTextComponent(text, string);
677:
678: // this.getFacets().put(READONLY_FACET, text);
679: if (!Beans.isDesignTime()) {
680: this .getFacets().put(READONLY_FACET, text);
681: }
682: // </RAVE>
683: return text;
684: }
685:
686: //<RAVE>
687: //modify readOnlyComponent with up-to-date data
688: private void modifyReadOnlyTextComponent(UIComponent text,
689: String readOnlyValue) {
690: if (readOnlyValue == null) {
691: readOnlyValue = new String();
692: }
693: if (text instanceof StaticText) {
694: ((StaticText) text).setText(readOnlyValue);
695: } else if (text instanceof ValueHolder) {
696: ((ValueHolder) text).setValue(readOnlyValue);
697: }
698: text.setId(getId().concat(READONLY_ID));
699: }
700:
701: //</RAVE>
702:
703: public String getPrimaryElementID(FacesContext context) {
704: if (getFacet(LABEL_FACET) == null) {
705: return getClientId(context);
706: }
707: return this .getClientId(context).concat(LIST_ID);
708: }
709:
710: // remove me when the interface method goes.
711: /**
712: * Return a string suitable for displaying the value in read only mode.
713: * The default is to separate the list values with a comma.
714: *
715: * @param context The FacesContext
716: * @throws javax.faces.FacesException If the list items cannot be processed
717: */
718: public String getValueAsReadOnly(FacesContext context,
719: String separator) {
720: return "FIX ME!";
721: }
722:
723: public boolean mainListSubmits() {
724: return true;
725: }
726: }
|