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