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.util;
042:
043: import java.lang.reflect.Array;
044: import java.util.ArrayList;
045: import java.util.HashMap;
046: import java.util.LinkedList;
047: import java.util.Vector;
048:
049: import javax.faces.FactoryFinder;
050: import javax.faces.application.Application;
051: import javax.faces.application.ApplicationFactory;
052: import javax.faces.component.UIComponent;
053: import javax.faces.component.ValueHolder;
054: import javax.faces.context.FacesContext;
055: import javax.faces.convert.Converter;
056: import javax.faces.convert.ConverterException;
057: import javax.faces.el.ValueBinding;
058:
059: /**
060: * The ConversionUtilities class provides utility method for
061: * converting values to and from Strings. Use this class if
062: * your component processes input from the user, or displays
063: * a converted value.
064: */
065:
066: public class ConversionUtilities {
067:
068: private static final String RENDERED_NULL_VALUE = "_RENDERED_NULL_VALUE_";
069: private static final boolean DEBUG = false;
070:
071: /**
072: * <p>Convert the values of a component with a
073: * single (non-list, non-array) value. Use this
074: * method if<p>
075: * <ul>
076: * <li>the component always binds the user input to
077: * a single object (e.g. a textfield component);
078: * or </li>
079: * <li>to handle the single object case when the
080: * component may bind the user input to a single
081: * object <em>or</em> to a collection of
082: * objects (e.g. a list component). Use a
083: * ValueTypeEvaluator to evaluate the value
084: * binding type. </li>
085: * </ul>
086: * @param component The component whose value is getting converted
087: * @param rawValue The submitted value of the component
088: * @param context The FacesContext of the request
089: * @throws ConverterException if the conversion fails
090: * @return An Object representing the converted value. If rawValue ==
091: * <code>null</code> return null.
092: * @see ValueTypeEvaluator
093: */
094: public static Object convertValueToObject(UIComponent component,
095: String rawValue, FacesContext context)
096: throws ConverterException {
097:
098: if (DEBUG)
099: log("convertValueToObject()");
100:
101: // Optimization based on
102: // javax.faces.convert.Converter getAsObject.
103: // It says:
104: // return null if the value to convert is
105: // null otherwise the result of the conversion
106: //
107: if (rawValue == null || !(component instanceof ValueHolder)) {
108: return rawValue;
109: }
110:
111: ValueHolder valueHolder = ((ValueHolder) component);
112:
113: Converter converter = valueHolder.getConverter();
114:
115: if (converter == null) {
116:
117: Class clazz = null;
118: // Determine the type of the component's value object
119: ValueBinding valueBinding = component
120: .getValueBinding("value"); //NOI18
121: if (valueBinding == null) {
122: Object value = valueHolder.getValue();
123: if (value == null) {
124: return rawValue;
125: }
126: clazz = value.getClass();
127: } else {
128: clazz = valueBinding.getType(context);
129: }
130:
131: // You can't register a default converter for
132: // String/Object for the whole app (as opposed to for the
133: // individual component). In this case we just
134: // return the String.
135: if (clazz == null || clazz.equals(String.class)
136: || clazz.equals(Object.class)) {
137: return rawValue;
138: }
139:
140: // Try to get a converter
141: converter = getConverterForClass(clazz);
142:
143: if (converter == null) {
144: return rawValue;
145: }
146: }
147: if (DEBUG) {
148: log("Raw value was: " + rawValue);
149: log("Converted value is: " + // NOI18N
150: converter.getAsObject(context, component, rawValue));
151: }
152: return converter.getAsObject(context, component, rawValue);
153: }
154:
155: /**
156: * <p>Convert a String array of submitted values to the appropriate
157: * type of Array for the value Object. This method assumes that
158: * the value binding for the value of the component has been
159: * determined to be an array (and as a consequence that the
160: * component implements ValueHolder).</p>
161: *
162: * <p>To evaluate the valueBinding, use the ValueTypeEvaluator
163: * class.</p>
164: * @param component The component whose submitted values are to be
165: * converted
166: * @param rawValues The submitted value of the component
167: * @param context The FacesContext of the request
168: * @see ValueTypeEvaluator
169: * @throws ConverterException if the conversion fails
170: * @return An array of converted values
171: */
172: public static Object convertValueToArray(UIComponent component,
173: String[] rawValues, FacesContext context)
174: throws ConverterException {
175:
176: if (DEBUG)
177: log("::convertValueToArray()");
178:
179: // By definition Converter returns null if the value to
180: // convert is null. Do so here.
181: //
182: if (rawValues == null) {
183: return null;
184: }
185:
186: // Get the class of the array members. We expect that the
187: // component's value binding for its value has been determined
188: // to be an array, as this is a condition of invoking this
189: // method.
190: Class clazz = null;
191:
192: // Get any converter specified by the page author
193: Converter converter = ((ValueHolder) component).getConverter();
194:
195: try {
196: // <RAVE>
197: // clazz = component.getValueBinding("value").
198: // getType(context).getComponentType(); //NOI18N
199: ValueBinding vb = component.getValueBinding("value"); //NOI18N
200: Class valueClass = vb.getType(context);
201: if (Object.class.equals(valueClass)) {
202: Object value = vb.getValue(context);
203: if (value != null)
204: valueClass = value.getClass();
205: }
206: clazz = valueClass.getComponentType();
207: // </RAVE>
208: } catch (Exception ex) {
209: // This may fail because we don't have a valuebinding (the
210: // developer may have used the binding attribute)
211:
212: Object value = ((ValueHolder) component).getValue();
213: if (value == null) {
214: // Now we're on thin ice. If there is a converter, we'll
215: // try to set this as an object array; if not, we'll just
216: // go for String.
217: if (converter != null) {
218: if (DEBUG)
219: log("\tNo class info, converter present - using object...");
220: clazz = Object.class;
221: } else {
222: if (DEBUG)
223: log("\tNo class info, no converter - using String...");
224: clazz = String.class;
225: }
226:
227: } else {
228: clazz = value.getClass().getComponentType();
229: if (DEBUG)
230: log("\tClass is " + clazz.getName());
231: }
232: }
233:
234: // If the array members are Strings, no conversion is
235: // necessary
236: if (clazz.equals(String.class)) {
237:
238: if (DEBUG) {
239: log("\tArray class is String, no conversion necessary");
240: log("\tValues are ");
241: for (int counter = 0; counter < rawValues.length; ++counter) {
242: log("\t" + rawValues[counter]);
243: }
244: }
245:
246: return rawValues;
247: }
248:
249: // We know rawValues is not null
250: //
251: int arraySize = 0;
252: arraySize = rawValues.length;
253: if (DEBUG) {
254: log("\tNumber of values is " + //NOI18N
255: String.valueOf(arraySize));
256: }
257:
258: Object valueArray = Array.newInstance(clazz, arraySize);
259:
260: // If there are no new values, return an empty array
261: if (arraySize == 0) {
262: if (DEBUG) {
263: log("\tEmpty value array, return new empty array");
264: log("\tof type " + valueArray.toString());
265: }
266: return valueArray;
267: }
268:
269: // Populate the array by converting each of the raw values
270:
271: // If there is no converter, look for the default converter
272: if (converter == null) {
273: if (DEBUG)
274: log("\tAttempt to get a default converter");
275: converter = getConverterForClass(clazz);
276: } else if (DEBUG)
277: log("\tRetrieved converter attached to component");
278:
279: int counter;
280: if (converter == null) {
281:
282: if (DEBUG)
283: log("\tNo converter found");
284:
285: if (clazz.equals(Object.class)) {
286: if (DEBUG) {
287: log("\tArray class is object, return the String array");
288: log("\tValues are\n");
289: for (counter = 0; counter < rawValues.length; ++counter) {
290: log("\n" + rawValues[counter]);
291: }
292: }
293: return rawValues;
294: }
295:
296: // Failed to deal with submitted data. Throw an
297: // exception.
298: String valueString = "";
299: for (counter = 0; counter < rawValues.length; counter++) {
300: valueString = valueString + " " + rawValues[counter]; //NOI18N
301: }
302: Object[] params = { valueString, "null Converter" };
303:
304: String message = "Could not find converter for "
305: + valueString;
306: throw new ConverterException(message);
307: }
308:
309: if (clazz.isPrimitive()) {
310: for (counter = 0; counter < arraySize; ++counter) {
311: addPrimitiveToArray(component, context, converter,
312: clazz, valueArray, counter, rawValues[counter]);
313: }
314: }
315:
316: else {
317: for (counter = 0; counter < arraySize; ++counter) {
318: Array.set(valueArray, counter, converter.getAsObject(
319: context, (UIComponent) component,
320: rawValues[counter]));
321: }
322: }
323:
324: return valueArray;
325: }
326:
327: private static void addPrimitiveToArray(UIComponent component,
328: FacesContext context, Converter converter, Class clazz,
329: Object valueArray, int arrayIndex, String rawValue) {
330:
331: Object valueObject = converter.getAsObject(context, component,
332: rawValue);
333: if (clazz.equals(Boolean.TYPE)) {
334: boolean value = ((Boolean) valueObject).booleanValue();
335: Array.setBoolean(valueArray, arrayIndex, value);
336: } else if (clazz.equals(Byte.TYPE)) {
337: byte value = ((Byte) valueObject).byteValue();
338: Array.setByte(valueArray, arrayIndex, value);
339: } else if (clazz.equals(Double.TYPE)) {
340: double value = ((Double) valueObject).doubleValue();
341: Array.setDouble(valueArray, arrayIndex, value);
342: } else if (clazz.equals(Float.TYPE)) {
343: float value = ((Float) valueObject).floatValue();
344: Array.setFloat(valueArray, arrayIndex, value);
345: } else if (clazz.equals(Integer.TYPE)) {
346: int value = ((Integer) valueObject).intValue();
347: Array.setInt(valueArray, arrayIndex, value);
348: } else if (clazz.equals(Character.TYPE)) {
349: char value = ((Character) valueObject).charValue();
350: Array.setChar(valueArray, arrayIndex, value);
351: } else if (clazz.equals(Short.TYPE)) {
352: short value = ((Short) valueObject).shortValue();
353: Array.setShort(valueArray, arrayIndex, value);
354: } else if (clazz.equals(Long.TYPE)) {
355: long value = ((Long) valueObject).longValue();
356: Array.setLong(valueArray, arrayIndex, value);
357: }
358: }
359:
360: /**
361: * <p>Convert a String array of submitted values to the appropriate
362: * type of List for the value Object. This method assumes that
363: * the value binding for the value of the component has been
364: * determined to be a subclass of java.util.List, and as a
365: * consequence, that the component implements ValueHolder.</p>
366: *
367: * <p>To evaluate the valueBinding, use the ValueTypeEvaluator
368: * class.</p>
369: * @param component The component whose submitted values are to be
370: * converted
371: * @param rawValues The submitted value of the component
372: * @param context The FacesContext of the request
373: * @see ValueTypeEvaluator
374: * @throws ConverterException if the conversion fails
375: *
376: * @return A List of converted values
377: */
378: public static Object convertValueToList(UIComponent component,
379: String[] rawValues, FacesContext context)
380: throws ConverterException {
381:
382: if (DEBUG) {
383: log("::convertValueToList()");
384: }
385:
386: // By definition Converter returns null if the value to
387: // convert is null. Do so here.
388: //
389: if (rawValues == null) {
390: return null;
391: }
392:
393: // Get the class of the array members. We expect that the
394: // component's value binding for its value has been determined
395: // to be an array, as this is a condition of invoking this
396: // method.
397: Class clazz = null;
398:
399: // Get any converter specified by the page author
400: Converter converter = ((ValueHolder) component).getConverter();
401:
402: try {
403: clazz = component.getValueBinding("value").getType(context)
404: .getComponentType(); //NOI18N
405: } catch (Exception ex) {
406: // This may fail because we don't have a valuebinding (the
407: // developer may have used the binding attribute)
408:
409: Object value = ((ValueHolder) component).getValue();
410: if (value == null) {
411: // Now we're on thin ice. If there is a converter, we'll
412: // try to set this as an object array; if not, we'll just
413: // go for String.
414: if (converter != null) {
415: if (DEBUG)
416: log("\tNo class info, converter present - using object...");
417: clazz = Object.class;
418: } else {
419: if (DEBUG)
420: log("\tNo class info, no converter - using String...");
421: clazz = String.class;
422: }
423:
424: } else {
425: clazz = value.getClass().getComponentType();
426: if (DEBUG)
427: log("\tClass is " + clazz.getName());
428: }
429: }
430:
431: java.util.List list = null;
432: try {
433: list = (java.util.List) (clazz.newInstance());
434: } catch (Throwable problem) {
435: // clazz is either abstract or an interface.
436: // we'll try a couple of reasonable List implementations
437: if (clazz.isAssignableFrom(ArrayList.class)) {
438: list = new ArrayList();
439: } else if (clazz.isAssignableFrom(LinkedList.class)) {
440: list = new LinkedList();
441: } else if (clazz.isAssignableFrom(Vector.class)) {
442: list = new Vector();
443: } else {
444: String message = "Unable to convert the value of component "
445: + //NOI18N
446: component.toString() + ". The type of the " + //NOI18N
447: "value object must be a class that can be " + //NOI18N
448: "instantiated, or it must be assignable " + //NOI18N
449: "from ArrayList, LinkedList or Vector."; //NOI18N
450: throw new ConverterException(message, problem);
451: }
452: }
453:
454: // We know rawValues is not null
455: //
456: int listSize = 0;
457: listSize = rawValues.length;
458: // If there are no new values, return an empty array
459: if (listSize == 0) {
460: if (DEBUG)
461: log("\tEmpty value array, return new empty list");
462: return list;
463: }
464:
465: // Populate the list by converting each of the raw values
466:
467: int arrayIndex;
468:
469: if (converter == null) {
470: if (DEBUG)
471: log("No converter, add the values as Strings");
472: for (arrayIndex = 0; arrayIndex < listSize; ++arrayIndex) {
473: list.add(rawValues[arrayIndex]);
474: }
475: } else {
476: if (DEBUG)
477: log("Using converter " + converter.getClass().getName());
478:
479: for (arrayIndex = 0; arrayIndex < listSize; ++arrayIndex) {
480:
481: if (DEBUG) {
482: Object converted = converter.getAsObject(context,
483: component, rawValues[arrayIndex]);
484: log("String value: " + rawValues[arrayIndex] + //NOI18N
485: " converts to : " + converted.toString()); //NOI18N
486: }
487: list.add(converter.getAsObject(context, component,
488: rawValues[arrayIndex]));
489:
490: }
491: }
492: return list;
493: }
494:
495: /**
496: * Converts an Object (which may or may not be the value of the
497: * component) to a String using the converter associated
498: * with the component. This method can be used to convert the
499: * value of the component, or the value of an Object associated
500: * with the component, such as the objects representing the
501: * options for a listbox or a checkboxgroup.
502: * @param component The component that needs to display the value
503: * as a String
504: * @param realValue The object that the component is to display
505: * @throws ConverterException if the conversion fails
506: *
507: * @return If converting the Object to a String fails
508: */
509: public static String convertValueToString(UIComponent component,
510: Object realValue) throws ConverterException {
511:
512: if (DEBUG)
513: log("convertValueToString(UIComponent, Object)");
514:
515: // The way the RI algorithm is written, it ends up returning
516: // and empty string if the realValue is null and there is no
517: // converter, and null if there is a converter (the converter
518: // is never applied). I don't think that's right, but I'm
519: // not sure what the right thing to do is. I return an empty
520: // string for now.
521:
522: if (realValue == null) {
523: return new String();
524: }
525:
526: if (realValue instanceof String) {
527: return (String) realValue;
528: }
529:
530: if (!(component instanceof ValueHolder)) {
531: return String.valueOf(realValue);
532: }
533:
534: Converter converter = ((ValueHolder) component).getConverter();
535:
536: // Case 1: no converter specified for the component. Try
537: // getting a default converter, and if that fails, invoke
538: // the .toString() method.
539: if (converter == null) {
540:
541: // if converter attribute set, try to acquire a converter
542: // using its class type. (avk note: this is the comment from
543: // the RI - not sure what it's supposed to mean)
544:
545: converter = getConverterForClass(realValue.getClass());
546:
547: // if there is no default converter available for this identifier,
548: // assume the model type to be String. Otherwise proceed to case 2.
549: if (converter == null) {
550: return String.valueOf(realValue);
551: }
552: }
553:
554: // Case 2: we have found a converter.
555: FacesContext context = FacesContext.getCurrentInstance();
556: return converter.getAsString(context, component, realValue);
557: }
558:
559: /**
560: * This method retrieves an appropriate converter based on the
561: * type of an object.
562: * @param converterClass The name of the converter class
563: * @return An instance of the appropriate converter type
564: */
565: public static Converter getConverterForClass(Class converterClass) {
566:
567: if (converterClass == null) {
568: return null;
569: }
570: try {
571: ApplicationFactory aFactory = (ApplicationFactory) FactoryFinder
572: .getFactory(FactoryFinder.APPLICATION_FACTORY);
573: Application application = aFactory.getApplication();
574: return (application.createConverter(converterClass));
575: } catch (Exception e) {
576: return (null);
577: }
578: }
579:
580: static void log(String s) {
581: System.out.println("ConversionUtilities::" + s);
582: }
583:
584: /**
585: * Return the converted value of submittedValue.
586: * If submittedValue is null, return null.
587: * If submittedValue is "", check the rendered value. If the
588: * the value that was rendered was null, return null
589: * else continue to convert.
590: */
591: public static Object convertRenderedValue(FacesContext context,
592: Object submittedValue, UIComponent component)
593: throws ConverterException {
594:
595: Converter converter = ((ValueHolder) component).getConverter();
596:
597: // If the component has a converter we can't assume that
598: // "" should be returned if "" was rendered or "" was rendered
599: // for null.
600: //
601: if (converter == null) {
602: // See if we rendered null.
603: // If we rendered null and the submitted value was ""
604: // return null
605: //
606: if (renderedNull(component)
607: && submittedValue instanceof String
608: && ((String) submittedValue).length() == 0) {
609: return null;
610: }
611: }
612: // If submittedValue is null, convertValueToObject returns null
613: // as does Converter by definition.
614: //
615: return ConversionUtilities.convertValueToObject(component,
616: (String) submittedValue, context);
617: }
618:
619: /**
620: * Record the value being rendered.
621: *
622: * @param component The component being rendered.
623: * @param value The value being rendered.
624: */
625: public static void setRenderedValue(UIComponent component,
626: Object value) {
627:
628: // First remove the attribute.
629: // Need to do this because a null value does nothing.
630: // Therefore the last value specified will remain.
631: //
632: component.getAttributes().remove(
633: ConversionUtilities.RENDERED_NULL_VALUE);
634:
635: // If the value is null, put barfs.
636: // So getRenderedValue will return null, if there is no
637: // RENDERED_NULL_VALUE property. Interpret this to mean that
638: // "null" was saved. I'd rather not but as long as the
639: // explicit property is not sought outside of these methods
640: // then it shouldn't be a problem.
641: //
642:
643: if (value == null) {
644: component.getAttributes().put(
645: ConversionUtilities.RENDERED_NULL_VALUE,
646: Boolean.TRUE);
647: }
648: }
649:
650: /**
651: * Return true if the stored rendered value on the specified
652: * component was null.
653: */
654: public static boolean renderedNull(UIComponent component) {
655: return (Boolean) component.getAttributes().get(
656: ConversionUtilities.RENDERED_NULL_VALUE) == null ? false
657: : true;
658: }
659:
660: /**
661: * Remove the stored rendered value from the specified component.
662: */
663: public static void removeRenderedValue(UIComponent component) {
664: component.getAttributes().remove(RENDERED_NULL_VALUE);
665: }
666:
667: private final static String RENDERED_TABLE_NULL_VALUES = "_RENDERED_TABLE_NULL_VALUES_";
668:
669: /**
670: * Used to preserve the rendered value when a component is
671: * used within a table. Since there is only one component
672: * instance when used in a table column the rendered value
673: * must be maintained for each "virtual" component instance
674: * for the rows in the column.
675: *
676: * @param context The current FacesContext for this request.
677: * @param component The component that is appearing in the table.
678: */
679: public static void saveRenderedValueState(FacesContext context,
680: UIComponent component) {
681:
682: boolean renderedNullValue = renderedNull(component);
683: HashMap rv = (HashMap) component.getAttributes().get(
684: RENDERED_TABLE_NULL_VALUES);
685:
686: if (rv == null) {
687: if (renderedNullValue) {
688: rv = new HashMap();
689: component.getAttributes().put(
690: RENDERED_TABLE_NULL_VALUES, rv);
691: rv.put(component.getClientId(context), null);
692: component.getAttributes().remove(RENDERED_NULL_VALUE);
693: }
694: } else if (!renderedNullValue) {
695: rv.remove(component.getClientId(context));
696: } else {
697: rv.put(component.getClientId(context), null);
698: removeRenderedValue(component);
699: }
700: }
701:
702: /**
703: * Used to restore the rendered value when a component is
704: * used within a table. Since there is only one component
705: * instance when used in a table column the rendered value
706: * must be maintained and restored for each "virtual" component
707: * instance for the rows in the column.
708: *
709: * @param context The current FacesContext for this request.
710: * @param component The component that is appearing in the table.
711: */
712: public static void restoreRenderedValueState(FacesContext context,
713: UIComponent component) {
714: HashMap rv = (HashMap) component.getAttributes().get(
715: RENDERED_TABLE_NULL_VALUES);
716: if (rv != null) {
717: if (rv.containsKey(component.getClientId(context))) {
718: setRenderedValue(component, null);
719: }
720: }
721: }
722:
723: /**
724: * Remove the storage for the "virtual" for the specified
725: * component used to save the rendered value for the "virtual"
726: * instances of this component when used in a table.
727: */
728: public static void removeSavedRenderedValueState(
729: UIComponent component) {
730: component.getAttributes().remove(RENDERED_TABLE_NULL_VALUES);
731: }
732: }
|