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 }