001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.myfaces.tobago.renderkit;
021    
022    import org.apache.commons.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    import org.apache.myfaces.tobago.component.ComponentUtil;
025    
026    import javax.faces.FacesException;
027    import javax.faces.component.UIComponent;
028    import javax.faces.component.UISelectMany;
029    import javax.faces.context.FacesContext;
030    import javax.faces.convert.Converter;
031    import javax.faces.convert.ConverterException;
032    import javax.faces.el.ValueBinding;
033    import java.lang.reflect.Array;
034    import java.util.ArrayList;
035    import java.util.List;
036    import java.util.Arrays;
037    
038    public class SelectManyRendererBase extends LayoutableRendererBase {
039    
040      private static final Log LOG = LogFactory.getLog(SelectManyRendererBase.class);
041    
042      public void decode(FacesContext facesContext, UIComponent component) {
043        if (ComponentUtil.isOutputOnly(component)) {
044          return;
045        }
046        if (component instanceof UISelectMany) {
047          UISelectMany uiSelectMany = (UISelectMany) component;
048    
049          String[] newValues = (String[])
050              facesContext.getExternalContext().getRequestParameterValuesMap().get(uiSelectMany.getClientId(facesContext));
051          if (LOG.isDebugEnabled()) {
052            LOG.debug("decode: key='" + component.getClientId(facesContext)
053                + "' value='" + Arrays.toString(newValues) + "'");
054            LOG.debug("size ... '" + (newValues != null ? newValues.length : -1) + "'");
055            if (newValues != null) {
056              for (String newValue : newValues) {
057                LOG.debug("newValues[i] = '" + newValue + "'");
058              }
059            }
060          }
061    
062          if (newValues == null) {
063            newValues = new String[0]; // because no selection will not submitted by browsers
064          }
065          uiSelectMany.setSubmittedValue(newValues);
066        }
067      }
068    
069      // the following is copied from myfaces shared RendererUtils
070      public Object getConvertedValue(FacesContext facesContext, UIComponent component, Object submittedValue)
071          throws ConverterException {
072    
073        if (submittedValue == null) {
074          return null;
075        } else {
076          if (!(submittedValue instanceof String[])) {
077            throw new ConverterException("Submitted value of type String[] for component : "
078                + component.getClientId(facesContext) + "expected");
079          }
080        }
081        return getConvertedUISelectManyValue(facesContext, (UISelectMany) component, (String[]) submittedValue);
082      }
083    
084      private Object getConvertedUISelectManyValue(FacesContext facesContext,
085          UISelectMany component,
086          String[] submittedValue)
087          throws ConverterException {
088        // Attention!
089        // This code is duplicated in jsfapi component package.
090        // If you change something here please do the same in the other class!
091    
092        if (submittedValue == null) {
093          throw new NullPointerException("submittedValue");
094        }
095    
096        ValueBinding vb = component.getValueBinding("value");
097        Class valueType = null;
098        Class arrayComponentType = null;
099        if (vb != null) {
100          valueType = vb.getType(facesContext);
101          if (valueType != null && valueType.isArray()) {
102            arrayComponentType = valueType.getComponentType();
103          }
104        }
105    
106        Converter converter = component.getConverter();
107        if (converter == null) {
108          if (valueType == null) {
109            // No converter, and no idea of expected type
110            // --> return the submitted String array
111            return submittedValue;
112          }
113    
114          if (List.class.isAssignableFrom(valueType)) {
115            // expected type is a List
116            // --> according to javadoc of UISelectMany we assume that the element type
117            //     is java.lang.String, and copy the String array to a new List
118            return Arrays.asList(submittedValue);
119          }
120    
121          if (arrayComponentType == null) {
122            throw new IllegalArgumentException("ValueBinding for UISelectMany must be of type List or Array");
123          }
124    
125          if (String.class.equals(arrayComponentType)) {
126            return submittedValue; //No conversion needed for String type
127          }
128          if (Object.class.equals(arrayComponentType)) {
129            return submittedValue; //No conversion for Object class
130          }
131    
132          try {
133            converter = facesContext.getApplication().createConverter(arrayComponentType);
134          } catch (FacesException e) {
135            LOG.error("No Converter for type " + arrayComponentType.getName() + " found", e);
136            return submittedValue;
137          }
138        }
139    
140        // Now, we have a converter...
141        // We determine the type of the component array after converting one of it's elements
142        if (vb != null && arrayComponentType == null
143            && valueType != null && valueType.isArray()) {
144          if (submittedValue.length > 0) {
145            arrayComponentType = converter.getAsObject(facesContext, component, submittedValue[0]).getClass();
146          }
147        }
148    
149        if (valueType == null) {
150          // ...but have no idea of expected type
151          // --> so let's convert it to an Object array
152          int len = submittedValue.length;
153          Object[] convertedValues = (Object[]) Array.newInstance(
154              arrayComponentType == null ? Object.class : arrayComponentType, len);
155          for (int i = 0; i < len; i++) {
156            convertedValues[i]
157                = converter.getAsObject(facesContext, component, submittedValue[i]);
158          }
159          return convertedValues;
160        }
161    
162        if (List.class.isAssignableFrom(valueType)) {
163          // Curious case: According to specs we should assume, that the element type
164          // of this List is java.lang.String. But there is a Converter set for this
165          // component. Because the user must know what he is doing, we will convert the values.
166          int length = submittedValue.length;
167          List<Object> list = new ArrayList<Object>(length);
168          for (int i = 0; i < length; i++) {
169            list.add(converter.getAsObject(facesContext, component, submittedValue[i]));
170          }
171          return list;
172        }
173    
174        if (arrayComponentType == null) {
175          throw new IllegalArgumentException("ValueBinding for UISelectMany must be of type List or Array");
176        }
177    
178        if (arrayComponentType.isPrimitive()) {
179          // primitive array
180          int len = submittedValue.length;
181          Object convertedValues = Array.newInstance(arrayComponentType, len);
182          for (int i = 0; i < len; i++) {
183            Array.set(convertedValues, i,
184                converter.getAsObject(facesContext, component, submittedValue[i]));
185          }
186          return convertedValues;
187        } else {
188          // Object array
189          int length = submittedValue.length;
190          List<Object> convertedValues = new ArrayList<Object>(length);
191          for (int i = 0; i < length; i++) {
192            convertedValues.add(i, converter.getAsObject(facesContext, component, submittedValue[i]));
193          }
194          return convertedValues.toArray((Object[]) Array.newInstance(arrayComponentType, length));
195        }
196      }
197    
198    }