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.io.IOException;
044: import java.util.ArrayList;
045: import java.util.Map;
046:
047: import javax.faces.application.Application;
048: import javax.faces.component.UIComponent;
049: import javax.faces.context.FacesContext;
050: import javax.faces.convert.ConverterException;
051: import javax.faces.el.ValueBinding;
052: import javax.faces.render.Renderer;
053:
054: import com.sun.rave.web.ui.util.ConversionUtilities;
055: import com.sun.rave.web.ui.component.Label;
056: import com.sun.rave.web.ui.component.ImageComponent;
057: import com.sun.rave.web.ui.component.util.Util;
058:
059: /**
060: * Superclass for Checkbox and RadioButton.
061: */
062: public class RbCbSelector extends RbCbSelectorBase {
063:
064: /**
065: * Image facet name.
066: */
067: public final static String IMAGE_FACET = "image"; //NOI18N
068:
069: // Properties to transfer from "this" to the image subcomponent
070: //
071: private final static String IMAGE_URL_PROP = "imageURL";
072: private final static String URL_PROP = "url";
073: private final static String ALT_PROP = "alt";
074:
075: /**
076: * Label facet name.
077: */
078: public final static String LABEL_FACET = "label"; //NOI18N
079:
080: // Properties to transfer from "this" to the label subcomponent
081: //
082: private final static String LABEL_PROP = "label";
083: private final static String TEXT_PROP = "text";
084: private final static String LABEL_LEVEL_PROP = "labelLevel";
085:
086: // Props for both subcomponents
087: //
088: private final static String TOOLTIP_PROP = "toolTip";
089: private final static String VISIBLE_PROP = "visible";
090: private final static String RENDERED_PROP = "rendered";
091:
092: private final static String ID_SEPARATOR = "_"; //NOI18N
093:
094: // This is the default value for selectedValue.
095: // Because of the generation and alias of "items"
096: // (Need to reconsider this inheritance)
097: // its not possible to set up a default value.
098: // If selectedValue is not set, then allow this
099: // component to behave as a boolean control.
100: // The component is selected if both "selected" and
101: // "selectedValue" are "true".
102: //
103: private final static Boolean trueSelectedValue = Boolean.TRUE;
104:
105: public RbCbSelector() {
106: super ();
107: }
108:
109: /**
110: * Implemented by subclasses in order to reflect the selection
111: * state of this component id part of a group.
112: * This method is called if the component is part of a group.
113: *
114: * @param context the context for this request.
115: * @param groupName the value of the <code>name</code> property.
116: */
117: protected void addToRequestMap(FacesContext context,
118: String groupName) {
119: }
120:
121: /**
122: * Encode the component.
123: * <p>
124: * If this component is part of a group, ensure that the initial
125: * state is reflected in the request map by calling
126: * <code>addToRequestMap</code>.
127: * </p>
128: * @param context the context for this request.
129: */
130: // Implement this here to initialize the RequestMap ArrayList
131: // of selected grouped checkboxes, so that initially selected
132: // check boxes are available on the first render cycle
133: //
134: public void encodeBegin(FacesContext context) throws IOException {
135:
136: if (context == null) {
137: throw new NullPointerException();
138: }
139: if (!isRendered()) {
140: return;
141: }
142:
143: // If the checkbox or radio button isn't valid, or
144: // not in a group or not
145: // selected, don't put it in the RequestMap.
146: //
147: String groupName = getName();
148: if (groupName == null || !isValid() || !isChecked()) {
149: return;
150: }
151:
152: addToRequestMap(context, groupName);
153:
154: String rendererType = getRendererType();
155: if (rendererType != null) {
156: getRenderer(context).encodeBegin(context, this );
157: }
158:
159: }
160:
161: /**
162: * Convert the <code>submittedValue</code> argument.
163: * <p>
164: * If there is a renderer for this component,
165: * its <code>getConvertedValue()</code> method is called and
166: * the value returned by that method is returned.
167: * </p>
168: * <p>
169: * If there is no renderer, and <code>submittedValue</code> is not
170: * an instance of <code>String[]</code> or
171: * <code>String</code> a <code>ConverterException</code> is thrown.
172: * </p>
173: * <p>
174: * The <code>submittedValue</code> indicates selected if it is
175: * <code>String[1].length() != 0</code> or
176: * <code>String.length() != 0</code>.
177: * </p>
178: * If not selected and <code>getSelectedValue()</code> returns an
179: * instance of <code>Boolean</code>, <code>Boolean.FALSE</code> is
180: * returned.
181: * </p>
182: * <p>
183: * If not selected and it's not a boolean control then an unselected
184: * value is returned appropriate for the type of the <code>selected</code>
185: * property. If the type of the <code>selected</code> property evaluates
186: * to a primitive type by virtue of a value binding the appropriate
187: * <code>MIN_VALUE</code> constant is returned. For example if the
188: * type is <code>int</code>, <code>new Integer(Integer.MIN_VALUE)</code>
189: * is returned.<br/>
190: * If the type is not a primitive value <code>""</code> is returned.
191: * </p>
192: * <p>
193: * If the control is selected
194: * <code>ConversionUtilities.convertValueToObject()</code> is called to
195: * convert <code>submittedValue</code>.
196: * </p>
197: * <p>
198: * If <code>ConversionUtilities.convertValueToObject()</code> returns
199: * <code>submittedValue</code>, the value of the
200: * <code>getSelectedValue()</code> property
201: * is returned, else the value returned by
202: * <code>ConversionUtilities.convertValueToObject()</code> is returned.
203: * </p>
204: * @param context the context of this request.
205: * @param submittedValue the submitted String value of this component.
206: */
207: public Object getConvertedValue(FacesContext context,
208: Object submittedValue) throws ConverterException {
209:
210: // First defer to the renderer.
211: //
212: Renderer renderer = getRenderer(context);
213: if (renderer != null) {
214: return renderer.getConvertedValue(context, this ,
215: submittedValue);
216: }
217: return getConvertedValue(context, this , submittedValue);
218: }
219:
220: /**
221: * Convert the <code>submittedValue</code> argument.
222: * <p>
223: * If <code>submittedValue</code> is not
224: * an instance of <code>String[]</code> or
225: * <code>String</code> a <code>ConverterException</code> is thrown.
226: * </p>
227: * <p>
228: * The <code>submittedValue</code> indicates selected if it is
229: * <code>String[1].length() != 0</code> or
230: * <code>String.length() != 0</code>.
231: * </p>
232: * If not selected and <code>getSelectedValue()</code> returns an
233: * instance of <code>Boolean</code>, <code>Boolean.FALSE</code> is
234: * returned.
235: * </p>
236: * <p>
237: * If not selected and it's not a boolean control then an unselected
238: * value is returned appropriate for the type of the <code>selected</code>
239: * property. If the type of the <code>selected</code> property evaluates
240: * to a primitive type by virtue of a value binding the appropriate
241: * <code>MIN_VALUE</code> constant is returned. For example if the
242: * type is <code>int</code>, <code>new Integer(Integer.MIN_VALUE)</code>
243: * is returned.<br/>
244: * If the type is not a primitive value <code>""</code> is returned.
245: * </p>
246: * <p>
247: * If the control is selected
248: * <code>ConversionUtilities.convertValueToObject()</code> is called to
249: * convert <code>submittedValue</code>.
250: * </p>
251: * <p>
252: * If <code>ConversionUtilities.convertValueToObject()</code> returns
253: * <code>submittedValue</code>, the value of the
254: * <code>getSelectedValue()</code> property
255: * is returned, else the value returned by
256: * <code>ConversionUtilities.convertValueToObject()</code> is returned.
257: * </p>
258: * @param context the context of this request.
259: * @param component an RbCbSelector instance.
260: * @param submittedValue the submitted String value of this component.
261: */
262: public Object getConvertedValue(FacesContext context,
263: RbCbSelector component, Object submittedValue)
264: throws ConverterException {
265:
266: // This would indicate minimally not selected
267: //
268: if (submittedValue == null) {
269: throw new ConverterException(
270: "The submitted value is null. " + //NOI18N
271: "The submitted value must be a String or String array.");//NOI18N
272: }
273:
274: // Expect a String or String[]
275: // Should be made to be just String.
276: //
277: boolean isStringArray = submittedValue instanceof String[];
278: boolean isString = submittedValue instanceof String;
279: if (!(isStringArray || isString)) {
280: throw new ConverterException(
281: "The submitted value must be a String or String array.");//NOI18N
282: }
283:
284: String rawValue = null;
285: if (isStringArray) {
286: if (((String[]) submittedValue).length > 0) {
287: rawValue = ((String[]) submittedValue)[0];
288: }
289: } else if (isString) {
290: rawValue = (String) submittedValue;
291: }
292:
293: // Need to determine if the submitted value is not checked
294: // and unchanged. If it is unchecked, rawValue == null or
295: // rawValue == "". Compare with the rendered value. If the
296: // rendered value is "" or null, then the component is unchanged
297: // and if the rendered value was not null, try and convert it.
298: //
299: boolean unselected = rawValue == null || rawValue.length() == 0;
300:
301: // If the component was unselected then we need to know if it
302: // was rendered unselected due to a value that was an empty
303: // string or null. If it is was submitted as unselected
304: // and rendered as unselected, we need the rendered value that
305: // implied unselected, since it may not null, just different
306: // than "selectedValue"
307: //
308: Object newValue = null;
309: Object selectedValue = getSelectedValue();
310: if (unselected) {
311: newValue = ConversionUtilities.convertRenderedValue(
312: context, rawValue, this );
313: // Determine the unselected value for Boolean controls
314: // if the converted value is null but the the component
315: // value wasn't rendered as null.
316: // For example if the control rendered as null, and is
317: // still unselected, then we don't want to return FALSE
318: // for a Boolean control, since it is unchanged.
319: // But if it has changed and is unselected then return
320: // the unselected value of FALSE.
321: //
322: if (!ConversionUtilities.renderedNull(component)
323: && selectedValue instanceof Boolean
324: && newValue == null) {
325:
326: // return the complement of the selectedValue
327: // Boolean value.
328: //
329: newValue = ((Boolean) selectedValue).booleanValue() ? Boolean.FALSE
330: : Boolean.TRUE;
331: }
332: return getUnselectedValue(context, component, newValue);
333: } else {
334: newValue = ConversionUtilities.convertValueToObject(
335: component, rawValue, context);
336: return newValue == rawValue ? selectedValue : newValue;
337: }
338: }
339:
340: private Object getUnselectedValue(FacesContext context,
341: UIComponent component, Object noValue) {
342:
343: // Determine the type of the component's value object
344: ValueBinding valueBinding = component.getValueBinding("value"); //NOI18N
345:
346: // If there's no value binding we don't care
347: // since the local value is an object and can support null or ""
348: //
349: if (valueBinding == null) {
350: return noValue;
351: }
352: // We have found a valuebinding.
353: Class clazz = valueBinding.getType(context);
354:
355: // Null class
356: if (clazz == null) {
357: return noValue;
358: }
359: // Pass noValue for use in primitive boolean case.
360: // If the "selectedValue" was Boolean.FALSE, unselected
361: // will be Boolean.TRUE.
362: //
363: if (clazz.isPrimitive()) {
364: return getPrimitiveUnselectedValue(clazz, noValue);
365: }
366:
367: // bail out
368: return noValue;
369: }
370:
371: private Object getPrimitiveUnselectedValue(Class clazz,
372: Object booleanUnselectedValue) {
373:
374: // it MUST be at least one of these
375: //
376: if (clazz.equals(Boolean.TYPE)) {
377: return booleanUnselectedValue;
378: } else if (clazz.equals(Byte.TYPE)) {
379: return new Integer(Byte.MIN_VALUE);
380: } else if (clazz.equals(Double.TYPE)) {
381: return new Double(Double.MIN_VALUE);
382: } else if (clazz.equals(Float.TYPE)) {
383: return new Float(Float.MIN_VALUE);
384: } else if (clazz.equals(Integer.TYPE)) {
385: return new Integer(Integer.MIN_VALUE);
386: } else if (clazz.equals(Character.TYPE)) {
387: return new Character(Character.MIN_VALUE);
388: } else if (clazz.equals(Short.TYPE)) {
389: return new Short(Short.MIN_VALUE);
390: } else {
391: // if (clazz.equals(Long.TYPE))
392: return new Long(Long.MIN_VALUE);
393: }
394: }
395:
396: /**
397: * Return the value of the <code>selectedValue</code> property.
398: * If <code>selectedValue</code> is null, then a <code>Boolean</code>
399: * true instance is returned and the control will behave as a
400: * boolean control.
401: */
402: public Object getSelectedValue() {
403: Object sv = super .getSelectedValue();
404: return sv == null ? trueSelectedValue : sv;
405: }
406:
407: // Hack to overcome introspection of "isSelected"
408: //
409: /**
410: * Return <code>true</code> if the control is checked.
411: * A control is checked when the <code>selectedValue</code> property is
412: * equal to the <code>selected</code> property.
413: */
414: public boolean isChecked() {
415: Object selectedValue = getSelectedValue();
416: Object selected = getSelected();
417: if (selectedValue == null || selected == null) {
418: return false;
419: }
420: // Need to support "selected" set to a constant String
421: // such as "true" or "false" when it is a boolean control.
422: // This does not include when selected is bound to a String
423: //
424: if (getValueBinding("selected") == null
425: && //NOI18N
426: selected instanceof String
427: && selectedValue instanceof Boolean) {
428: return selectedValue.equals(Boolean
429: .valueOf((String) selected));
430: } else {
431: return selected.equals(selectedValue);
432: }
433: }
434:
435: /**
436: * Returns a </code>UIComponent</code> that represents the label
437: * for this component.
438: * <p>
439: * If a "label" facet exists, the <code>UIComponent</code> associated
440: * with that facet is returned.<br/>
441: * If a "label" facet does not exist and the <code>label</code>
442: * property is not null, <code>createLabelComponent</code> is called
443: * to create the label component.
444: * </p>
445: */
446: public UIComponent getLabelComponent() {
447:
448: UIComponent labelComponent = getFacet(LABEL_FACET);
449: if (labelComponent != null) {
450: return labelComponent;
451: }
452: return getLabel() != null ? createLabelComponent() : null;
453: }
454:
455: /**
456: * Returns a </code>UIComponent</code> that represents the image
457: * for this component.
458: * <p>
459: * If an "image" facet exists, the <code>UIComponent</code> associated
460: * with that facet is returned.<br/>
461: * If an "image" facet does not exist and the <code>imageURL</code>
462: * property is not null, <code>createImageComponent()</code> is called
463: * to create the image component.
464: * </p>
465: */
466: public UIComponent getImageComponent() {
467: UIComponent imageComponent = getFacet(IMAGE_FACET);
468: if (imageComponent != null) {
469: return imageComponent;
470: }
471: return getImageURL() != null ? createImageComponent() : null;
472: }
473:
474: /**
475: * Create a <code>com.sun.rave.web.ui.component.Label</code> to represent
476: * the label for this component.
477: */
478: protected UIComponent createLabelComponent() {
479: Label label = new Label();
480: label.setId(getId() + ID_SEPARATOR + LABEL_FACET);
481: label.setParent(this );
482:
483: if (label != null) {
484: label.setLabeledComponent(this );
485: if (!passValueBinding(label, LABEL_PROP, TEXT_PROP)) {
486: label.setText(getLabel());
487: }
488: if (!passValueBinding(label, LABEL_LEVEL_PROP,
489: LABEL_LEVEL_PROP)) {
490: label.setLabelLevel(getLabelLevel());
491: }
492: if (!passValueBinding(label, TOOLTIP_PROP, TOOLTIP_PROP)) {
493: label.setToolTip(getToolTip());
494: }
495: /* Don's transfer these properties. They shouldn't have
496: * to be passed on unless, "rendersChildren" == false
497: *
498: if (!passValueBinding(label, VISIBLE_PROP, VISIBLE_PROP)) {
499: label.setVisible(isVisible());
500: }
501: if (!passValueBinding(label, RENDERED_PROP, RENDERED_PROP)) {
502: label.setRendered(isRendered());
503: }
504: */
505: }
506: return label;
507: }
508:
509: /**
510: * Create a </code>com.sun.rave.web.ui.component.ImageComponent</code>
511: * to represent the image for this component.
512: */
513: protected UIComponent createImageComponent() {
514:
515: ImageComponent image = new ImageComponent();
516: image.setId(getId() + ID_SEPARATOR + IMAGE_FACET);
517: image.setParent(this );
518:
519: if (image != null) {
520: if (!passValueBinding(image, IMAGE_URL_PROP, URL_PROP)) {
521: image.setUrl(getImageURL());
522: }
523: if (!passValueBinding(image, TOOLTIP_PROP, TOOLTIP_PROP)) {
524: image.setToolTip(getToolTip());
525: }
526: if (!passValueBinding(image, TOOLTIP_PROP, ALT_PROP)) {
527: image.setAlt(getToolTip());
528: }
529: /* Don's transfer these properties. They shouldn't have
530: * to be passed on unless, "rendersChildren" == false
531: *
532: if (!passValueBinding(image, VISIBLE_PROP, VISIBLE_PROP)) {
533: image.setVisible(isVisible());
534: }
535: if (!passValueBinding(image, RENDERED_PROP, RENDERED_PROP)) {
536: image.setRendered(isRendered());
537: }
538: */
539: }
540: return image;
541: }
542:
543: private boolean passValueBinding(UIComponent component,
544: String fromAttr, String toAttr) {
545:
546: ValueBinding vb = getValueBinding(fromAttr);
547: if (vb != null) {
548: component.setValueBinding(toAttr, vb);
549: }
550: return vb != null;
551: }
552: }
|