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