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 java.lang.reflect.Array;
044: import java.util.Iterator;
045: import javax.faces.component.StateHolder;
046: import javax.faces.component.UIComponent;
047: import javax.faces.context.FacesContext;
048: import javax.faces.convert.ConverterException;
049: import javax.faces.event.ValueChangeEvent;
050:
051: import com.sun.rave.web.ui.model.OptionTitle;
052: import com.sun.rave.web.ui.util.ConversionUtilities;
053: import com.sun.rave.web.ui.util.MessageUtil;
054: import com.sun.rave.web.ui.util.ValueType;
055: import com.sun.rave.web.ui.util.ValueTypeEvaluator;
056:
057: /**
058: *
059: * @author avk
060: */
061:
062: public class Selector extends SelectorBase implements SelectorManager {
063:
064: // If true, debugging statements are printed to stdout
065: private static final boolean DEBUG = false;
066:
067: /**
068: * Read only separator string
069: */
070: private static final String READ_ONLY_SEPARATOR = ", "; //NOI18N
071:
072: private boolean multiple;
073:
074: // Holds the ValueType of this component
075: protected ValueTypeEvaluator valueTypeEvaluator = null;
076:
077: public Selector() {
078: valueTypeEvaluator = new ValueTypeEvaluator(this );
079: }
080:
081: /**
082: *
083: * <p>Return a flag indicating whether this component is responsible
084: * for rendering its child components. The default implementation
085: * in {@link UIComponentBase#getRendersChildren} tries to find the
086: * renderer for this component. If it does, it calls {@link
087: * Renderer#getRendersChildren} and returns the result. If it
088: * doesn't, it returns false. As of version 1.2 of the JavaServer
089: * Faces Specification, component authors are encouraged to return
090: * <code>true</code> from this method and rely on {@link
091: * UIComponentBase#encodeChildren}.</p>
092: */
093: public boolean getRendersChildren() {
094: return true;
095: }
096:
097: /**
098: * Retrieve the value of this component (the "selected" property) as an
099: * object. This method is invoked by the JSF engine during the validation
100: * phase. The JSF default behaviour is for components to defer the
101: * conversion and validation to the renderer, but for the Selector based
102: * components, the renderers do not share as much functionality as the
103: * components do, so it is more efficient to do it here.
104: * @param context The FacesContext of the request
105: * @param submittedValue The submitted value of the component
106: */
107:
108: public Object getConvertedValue(FacesContext context,
109: Object submittedValue) throws ConverterException {
110: return getConvertedValue(this , valueTypeEvaluator, context,
111: submittedValue);
112: }
113:
114: /**
115: * Retrieve the value of this component (the "selected" property) as an
116: * object. This method is invoked by the JSF engine during the validation
117: * phase. The JSF default behaviour is for components to defer the
118: * conversion and validation to the renderer, but for the Selector based
119: * components, the renderers do not share as much functionality as the
120: * components do, so it is more efficient to do it here.
121: * @param component The component whose value to convert
122: * @param context The FacesContext of the request
123: * @param submittedValue The submitted value of the component
124: */
125: private Object getConvertedValue(UIComponent component,
126: ValueTypeEvaluator valueTypeEvaluator,
127: FacesContext context, Object submittedValue)
128:
129: throws ConverterException {
130:
131: if (DEBUG)
132: log("getConvertedValue()", component);
133:
134: if (!(submittedValue instanceof String[])) {
135: Object[] args = { component.getClass().getName() };
136: String msg = MessageUtil.getMessage(
137: "com.sun.rave.web.ui.resources.LogMessages", //NOI18N
138: "Selector.invalidSubmittedValue", args); //NOI18N
139:
140: throw new ConverterException(msg);
141: }
142:
143: String[] rawValues = (String[]) submittedValue;
144:
145: // This should never happen
146: //
147: if (rawValues.length == 1
148: && OptionTitle.NONESELECTED.equals(rawValues[0])) {
149: Object[] args = { OptionTitle.NONESELECTED };
150: String msg = MessageUtil.getMessage(
151: "com.sun.rave.web.ui.resources.LogMessages", //NOI18N
152: "Selector.invalidSubmittedValue", args); //NOI18N
153:
154: throw new ConverterException(msg);
155: }
156:
157: // If there are no elements in rawValue nothing was submitted.
158: // If null was rendered, return null
159: //
160: if (rawValues.length == 0) {
161: if (DEBUG)
162: log("\t no values submitted, we return null", component);
163: if (ConversionUtilities.renderedNull(component)) {
164: return null;
165: }
166: }
167:
168: // Why does getAttributes.get("multiple") not work?
169: if (((SelectorManager) component).isMultiple()) {
170: if (DEBUG)
171: log("\tComponent accepts multiple values", component);
172:
173: if (valueTypeEvaluator.getValueType() == ValueType.ARRAY) {
174: if (DEBUG)
175: log("\tComponent value is an array", component);
176: return ConversionUtilities.convertValueToArray(
177: component, rawValues, context);
178: }
179: // This case is not supported yet!
180: else if (valueTypeEvaluator.getValueType() == ValueType.LIST) {
181: if (DEBUG)
182: log("\tComponent value is a list", component);
183: return ConversionUtilities.convertValueToList(
184: component, rawValues, context);
185: } else {
186: if (DEBUG)
187: log(
188: "\tMultiple selection enabled for non-array value",
189: component);
190: Object[] params = { component.getClass().getName() };
191: String msg = MessageUtil.getMessage(
192: "com.sun.rave.web.ui.resources.LogMessages", //NOI18N
193: "Selector.multipleError", //NOI18N
194: params);
195: throw new ConverterException(msg);
196: }
197: }
198:
199: if (DEBUG)
200: log("\tComponent value is an object", component);
201:
202: // Not sure if this case is taken care of consistently
203: // Need to formulate the possible states for the
204: // submitted value and what they mean.
205: //
206: // This can overwrite an unchanged value property
207: // with null when it was originally empty string.
208: /*
209: if(rawValues[0].length() == 0) {
210: if(DEBUG) log("\t empty string submitted, return null", component);
211: return null;
212: }
213: */
214:
215: String cv = rawValues.length == 0 ? "" : rawValues[0];
216:
217: if (valueTypeEvaluator.getValueType() == ValueType.NONE) {
218: if (DEBUG)
219: log("\t valuetype == none, return rawValue", component);
220: return cv;
221: }
222:
223: if (DEBUG)
224: log("\t Convert the thing...", component);
225: return ConversionUtilities.convertValueToObject(component, cv,
226: context);
227: }
228:
229: /**
230: * Return a string suitable for displaying the value in read only mode.
231: * The default is to separate the list values with a comma.
232: *
233: * @param context The FacesContext
234: * @throws javax.faces.FacesException If the list items cannot be processed
235: */
236: // AVK - instead of doing this here, I think we
237: // should set the value to be displayed when we get the readOnly
238: // child component. It would be a good idea to separate the listItems
239: // processing for the renderer - where we have to reprocess the items
240: // every time, from other times, when this may not be necessary.
241: // I note that although this code has been refactored by Rick, my
242: // original code already did this so the fault is wtih me.
243: protected String getValueAsReadOnly(FacesContext context) {
244:
245: // The comma format READ_ONLY_SEPARATOR should be part of the theme
246: // and/or configurable by the application
247: //
248: return getValueAsString(context, READ_ONLY_SEPARATOR, true);
249: }
250:
251: /**
252: * Get the value (the object representing the selection(s)) of this
253: * component as a String. If the component allows multiple selections,
254: * the strings corresponding to the individual options are separated by
255: * spaces.
256: * @param context The FacesContext of the request
257: * @param separator A String separator between the values
258:
259: public String getValueAsString(FacesContext context, String separator) {
260: return getValueAsString(context, separator, false);
261: }
262: */
263: /**
264: * Get the value (the object representing the selection(s)) of this
265: * component as a String. If the component allows multiple selections,
266: * the strings corresponding to the individual options are separated
267: * by the separator argument. If readOnly is true, leading and
268: * and trailing separators are omitted.
269: * If readOnly is false the formatted String is suitable for decoding
270: * by ListRendererBase.decode.
271: *
272: * @param context The FacesContext of the request
273: * @param separator A String separator between the values
274: * @param readOnly A readonly formatted String, no leading or trailing
275: * separator string.
276: */
277: private String getValueAsString(FacesContext context,
278: String separator, boolean readOnly) {
279:
280: // Need to distinguish null value from an empty string
281: // value. See the end of this method for empty string
282: // value formatting
283: //
284: Object value = getValue();
285: if (value == null) {
286: return new String();
287: }
288:
289: if (valueTypeEvaluator.getValueType() == ValueType.NONE) {
290: return new String();
291: }
292:
293: if (valueTypeEvaluator.getValueType() == ValueType.INVALID) {
294: return new String();
295: }
296:
297: // Multiple selections
298: //
299: // The format should be the same as that returned
300: // from the javascript which always has a leading
301: // and terminating separator. And suitable for decoding
302: // by ListRendererBase.decode
303: //
304: if (valueTypeEvaluator.getValueType() == ValueType.LIST) {
305:
306: StringBuffer valueBuffer = new StringBuffer(256);
307:
308: java.util.List list = (java.util.List) value;
309: Iterator valueIterator = ((java.util.List) value)
310: .iterator();
311: String valueString = null;
312:
313: // Leading delimiter
314: //
315: if (!readOnly && valueIterator.hasNext()) {
316: valueBuffer.append(separator);
317: }
318:
319: while (valueIterator.hasNext()) {
320: valueString = ConversionUtilities.convertValueToString(
321: this , valueIterator.next());
322: valueBuffer.append(valueString);
323: // Add terminating delimiter
324: //
325: if (!readOnly || (readOnly && valueIterator.hasNext())) {
326: valueBuffer.append(separator);
327: }
328: }
329: return valueBuffer.toString();
330: }
331:
332: if (valueTypeEvaluator.getValueType() == ValueType.ARRAY) {
333:
334: StringBuffer valueBuffer = new StringBuffer(256);
335:
336: int length = Array.getLength(value);
337: Object valueObject = null;
338: String valueString = null;
339:
340: if (!readOnly && length != 0) {
341: valueBuffer.append(separator);
342: }
343: for (int counter = 0; counter < length; ++counter) {
344: valueObject = Array.get(value, counter);
345: valueString = ConversionUtilities.convertValueToString(
346: this , valueObject);
347: valueBuffer.append(valueString);
348: // Add terminating delimiter
349: //
350: if (!readOnly || (readOnly && counter < length - 1)) {
351: valueBuffer.append(separator);
352: }
353: }
354: return valueBuffer.toString();
355: }
356:
357: // Empty string looks like '<sep><sep>' or if separator == "|"
358: // it'll be "||"
359: //
360: String cv = ConversionUtilities.convertValueToString(this ,
361: value);
362: if (readOnly) {
363: return cv;
364: } else {
365: StringBuffer sb = new StringBuffer(64);
366: return sb.append(separator).append(cv).append(separator)
367: .toString();
368: }
369: }
370:
371: public int getLabelLevel() {
372:
373: int labelLevel = super .getLabelLevel();
374: if (labelLevel < 1 || labelLevel > 3) {
375: labelLevel = 2;
376: super .setLabelLevel(labelLevel);
377: }
378: return labelLevel;
379: }
380:
381: /**
382: * Getter for property multiple.
383: * @return Value of property multiple.
384: */
385: public boolean isMultiple() {
386:
387: return this .multiple;
388: }
389:
390: /**
391: * Setter for property multiple.
392: * @param multiple New value of property multiple.
393: */
394: public void setMultiple(boolean multiple) {
395: if (this .multiple != multiple) {
396: valueTypeEvaluator.reset();
397: this .multiple = multiple;
398: }
399: }
400:
401: /**
402: * Public method toString()
403: * @return A String representation of this component
404: */
405: public String toString() {
406: String string = this .getClass().getName();
407: return string;
408: }
409:
410: /**
411: * private method for development time error detecting
412: */
413: static void log(String s, Object o) {
414: System.out.println(o.getClass().getName() + "::" + s); //NOI18N
415: }
416:
417: /**
418: * private method for development time error detecting
419: */
420: void log(String s) {
421: System.out.println(this .getClass().getName() + "::" + s); //NOI18N
422: }
423:
424: /**
425: * <p>Return <code>true</code> if the new value is different from the
426: * previous value.</p>
427: *
428: * This only implements a compareValues for value if it is an Array.
429: * If value is not an Array, defer to super.compareValues.
430: * The assumption is that the ordering of the elements
431: * between the previous value and the new value is determined
432: * in the same manner.
433: *
434: * Another assumption is that the two object arguments
435: * are of the same type, both arrays of both not arrays.
436: *
437: * @param previous old value of this component (if any)
438: * @param value new value of this component (if any)
439: */
440: protected boolean compareValues(Object previous, Object value) {
441:
442: // Let super take care of null cases
443: //
444: if (previous == null || value == null) {
445: return super .compareValues(previous, value);
446: }
447: if (value instanceof Object[]) {
448: // If the lengths aren't equal return true
449: //
450: int length = Array.getLength(value);
451: if (Array.getLength(previous) != length) {
452: return true;
453: }
454: // Each element at index "i" in previous must be equal to the
455: // elementa at index "i" in value.
456: //
457: for (int i = 0; i < length; ++i) {
458:
459: Object newValue = Array.get(value, i);
460: Object prevValue = Array.get(previous, i);
461:
462: // This is probably not necessary since
463: // an Option's value cannot be null
464: //
465: if (newValue == null) {
466: if (prevValue == null) {
467: continue;
468: } else {
469: return true;
470: }
471: }
472: if (prevValue == null) {
473: return true;
474: }
475:
476: if (!prevValue.equals(newValue)) {
477: return true;
478: }
479: }
480: return false;
481: }
482: return super .compareValues(previous, value);
483: }
484:
485: public void setSelected(Object selected) {
486: super.setSelected(selected);
487: valueTypeEvaluator.reset();
488: }
489: }
|