1
|
1 /*
|
|
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
|
3 *
|
|
4 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
|
|
5 *
|
|
6 * The contents of this file are subject to the terms of either the GNU
|
|
7 * General Public License Version 2 only ("GPL") or the Common Development
|
|
8 * and Distribution License("CDDL") (collectively, the "License"). You
|
|
9 * may not use this file except in compliance with the License. You can
|
|
10 * obtain a copy of the License at
|
|
11 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
|
|
12 * or packager/legal/LICENSE.txt. See the License for the specific
|
|
13 * language governing permissions and limitations under the License.
|
|
14 *
|
|
15 * When distributing the software, include this License Header Notice in each
|
|
16 * file and include the License file at packager/legal/LICENSE.txt.
|
|
17 *
|
|
18 * GPL Classpath Exception:
|
|
19 * Oracle designates this particular file as subject to the "Classpath"
|
|
20 * exception as provided by Oracle in the GPL Version 2 section of the License
|
|
21 * file that accompanied this code.
|
|
22 *
|
|
23 * Modifications:
|
|
24 * If applicable, add the following below the License Header, with the fields
|
|
25 * enclosed by brackets [] replaced by your own identifying information:
|
|
26 * "Portions Copyright [year] [name of copyright owner]"
|
|
27 *
|
|
28 * Contributor(s):
|
|
29 * If you wish your version of this file to be governed by only the CDDL or
|
|
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
|
|
31 * elects to include this software in this distribution under the [CDDL or GPL
|
|
32 * Version 2] license." If you don't indicate a single choice of license, a
|
|
33 * recipient has the option to distribute your version of this file under
|
|
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
|
|
35 * its licensees as provided above. However, if you add GPL Version 2 code
|
|
36 * and therefore, elected the GPL Version 2 license, then the option applies
|
|
37 * only if the new code is made subject to such option by the copyright
|
|
38 * holder.
|
|
39 */
|
|
40
|
|
41 /*
|
|
42 * (C) Copyright International Business Machines Corp., 2001,2002
|
|
43 * The source code for this program is not published or otherwise
|
|
44 * divested of its trade secrets, irrespective of what has been
|
|
45 * deposited with the U. S. Copyright Office.
|
|
46 */
|
|
47
|
|
48 // MenuRenderer.java
|
|
49
|
|
50 package com.sun.faces.renderkit.html_basic;
|
|
51
|
|
52 import java.io.IOException;
|
|
53 import java.lang.reflect.Array;
|
|
54 import java.lang.reflect.Method;
|
|
55 import java.lang.reflect.Modifier;
|
|
56 import java.util.ArrayList;
|
|
57 import java.util.Arrays;
|
|
58 import java.util.Collection;
|
|
59 import java.util.Iterator;
|
|
60 import java.util.Map;
|
|
61 import java.util.Set;
|
|
62 import java.util.HashSet;
|
|
63 import java.util.SortedSet;
|
|
64 import java.util.TreeSet;
|
|
65 import java.util.Queue;
|
|
66 import java.util.LinkedList;
|
|
67 import java.util.logging.Level;
|
|
68
|
|
69 import javax.el.ELException;
|
|
70 import javax.el.ValueExpression;
|
|
71 import javax.el.ExpressionFactory;
|
|
72 import javax.faces.component.UIComponent;
|
|
73 import javax.faces.component.UISelectMany;
|
|
74 import javax.faces.component.UISelectOne;
|
|
75 import javax.faces.component.ValueHolder;
|
|
76 import javax.faces.context.FacesContext;
|
|
77 import javax.faces.context.ResponseWriter;
|
|
78 import javax.faces.convert.Converter;
|
|
79 import javax.faces.convert.ConverterException;
|
|
80 import javax.faces.model.SelectItem;
|
|
81 import javax.faces.model.SelectItemGroup;
|
|
82 import javax.faces.FacesException;
|
|
83
|
|
84 import org.apache.commons.lang.StringUtils;
|
|
85
|
|
86 import com.sun.faces.RIConstants;
|
|
87 import com.sun.faces.io.FastStringWriter;
|
|
88 import com.sun.faces.renderkit.Attribute;
|
|
89 import com.sun.faces.renderkit.AttributeManager;
|
|
90 import com.sun.faces.renderkit.RenderKitUtils;
|
|
91 import com.sun.faces.util.MessageUtils;
|
|
92 import com.sun.faces.util.Util;
|
|
93 import com.sun.faces.util.RequestStateManager;
|
|
94 import com.sun.faces.util.ReflectionUtils;
|
|
95
|
|
96 /**
|
|
97 * <B>MenuRenderer</B> is a class that renders the current value of
|
|
98 * <code>UISelectOne<code> or <code>UISelectMany<code> component as a list of
|
|
99 * menu options.
|
|
100 */
|
|
101
|
|
102 public class MenuRenderer extends HtmlBasicInputRenderer {
|
|
103
|
|
104
|
|
105 private static final Attribute[] ATTRIBUTES =
|
|
106 AttributeManager.getAttributes(AttributeManager.Key.SELECTMANYMENU);
|
|
107
|
|
108
|
|
109 // ---------------------------------------------------------- Public Methods
|
|
110
|
|
111
|
|
112 public Object convertSelectManyValue(FacesContext context,
|
|
113 UISelectMany uiSelectMany,
|
|
114 String[] newValues)
|
|
115 throws ConverterException {
|
|
116
|
|
117 // if we have no local value, try to get the valueExpression.
|
|
118 ValueExpression valueExpression =
|
|
119 uiSelectMany.getValueExpression("value");
|
|
120
|
|
121 Object result = newValues; // default case, set local value
|
|
122 boolean throwException = false;
|
|
123
|
|
124 // If we have a ValueExpression
|
|
125 if (null != valueExpression) {
|
|
126 Class modelType = valueExpression.getType(context.getELContext());
|
|
127 // Does the valueExpression resolve properly to something with
|
|
128 // a type?
|
|
129 if (modelType != null) {
|
|
130 result = convertSelectManyValuesForModel(context,
|
|
131 uiSelectMany,
|
|
132 modelType,
|
|
133 newValues);
|
|
134 }
|
|
135 // If it could not be converted, as a fall back try the type of
|
|
136 // the valueExpression's current value covering some edge cases such
|
|
137 // as where the current value came from a Map.
|
|
138 if(result == null) {
|
|
139 Object value = valueExpression.getValue(context.getELContext());
|
|
140 if(value != null) {
|
|
141 result = convertSelectManyValuesForModel(context,
|
|
142 uiSelectMany,
|
|
143 value.getClass(),
|
|
144 newValues);
|
|
145 }
|
|
146 }
|
|
147 if(result == null) {
|
|
148 throwException = true;
|
|
149 }
|
|
150 } else {
|
|
151 // No ValueExpression, just use Object array.
|
|
152 result = convertSelectManyValues(context, uiSelectMany,
|
|
153 Object[].class,
|
|
154 newValues);
|
|
155 }
|
|
156 if (throwException) {
|
|
157 StringBuffer values = new StringBuffer();
|
|
158 if (null != newValues) {
|
|
159 for (int i = 0; i < newValues.length; i++) {
|
|
160 if (i == 0) {
|
|
161 values.append(newValues[i]);
|
|
162 } else {
|
|
163 values.append(' ').append(newValues[i]);
|
|
164 }
|
|
165 }
|
|
166 }
|
|
167 Object[] params = {
|
|
168 values.toString(),
|
|
169 valueExpression.getExpressionString()
|
|
170 };
|
|
171 throw new ConverterException
|
|
172 (MessageUtils.getExceptionMessage(MessageUtils.CONVERSION_ERROR_MESSAGE_ID,
|
|
173 params));
|
|
174 }
|
|
175
|
|
176 // At this point, result is ready to be set as the value
|
|
177 if (logger.isLoggable(Level.FINE)) {
|
|
178 logger.fine("SelectMany Component " + uiSelectMany.getId() +
|
|
179 " convertedValues " + result);
|
|
180 }
|
|
181 return result;
|
|
182
|
|
183 }
|
|
184
|
|
185
|
|
186 public Object convertSelectOneValue(FacesContext context,
|
|
187 UISelectOne uiSelectOne,
|
|
188 String newValue)
|
|
189 throws ConverterException {
|
|
190
|
|
191 if (RIConstants.NO_VALUE.equals(newValue)) {
|
|
192 return null;
|
|
193 }
|
|
194 if (newValue == null) {
|
|
195 if (logger.isLoggable(Level.FINE)) {
|
|
196 logger.fine("No conversion necessary for SelectOne Component "
|
|
197 + uiSelectOne.getId()
|
|
198 + " since the new value is null ");
|
|
199 }
|
|
200 return null;
|
|
201 }
|
|
202
|
|
203 Object convertedValue =
|
|
204 super.getConvertedValue(context, uiSelectOne, newValue);
|
|
205 if (logger.isLoggable(Level.FINE)) {
|
|
206 logger.fine("SelectOne Component " + uiSelectOne.getId() +
|
|
207 " convertedValue " + convertedValue);
|
|
208 }
|
|
209 return convertedValue;
|
|
210
|
|
211 }
|
|
212
|
|
213 @Override
|
|
214 public void decode(FacesContext context, UIComponent component) {
|
|
215
|
|
216 rendererParamsNotNull(context, component);
|
|
217
|
|
218 if (!shouldDecode(component)) {
|
|
219 return;
|
|
220 }
|
|
221
|
|
222 String clientId = decodeBehaviors(context, component);
|
|
223
|
|
224 if (clientId == null) {
|
|
225 clientId = component.getClientId(context);
|
|
226 }
|
|
227 assert(clientId != null);
|
|
228 // currently we assume the model type to be of type string or
|
|
229 // convertible to string and localized by the application.
|
|
230 if (component instanceof UISelectMany) {
|
|
231 Map<String, String[]> requestParameterValuesMap =
|
|
232 context.getExternalContext().
|
|
233 getRequestParameterValuesMap();
|
|
234 if (requestParameterValuesMap.containsKey(clientId)) {
|
|
235 String newValues[] = requestParameterValuesMap.
|
|
236 get(clientId);
|
|
237 setSubmittedValue(component, newValues);
|
|
238 if (logger.isLoggable(Level.FINE)) {
|
|
239 logger.fine("submitted values for UISelectMany component "
|
|
240 +
|
|
241 component.getId()
|
|
242 + " after decoding "
|
|
243 + Arrays.toString(newValues));
|
|
244 }
|
|
245 } else {
|
|
246 // Use the empty array, not null, to distinguish
|
|
247 // between an deselected UISelectMany and a disabled one
|
|
248 setSubmittedValue(component, new String[0]);
|
|
249 if (logger.isLoggable(Level.FINE)) {
|
|
250 logger.fine("Set empty array for UISelectMany component " +
|
|
251 component.getId() + " after decoding ");
|
|
252 }
|
|
253 }
|
|
254 } else {
|
|
255 // this is a UISelectOne
|
|
256 Map<String, String> requestParameterMap =
|
|
257 context.getExternalContext().
|
|
258 getRequestParameterMap();
|
|
259 if (requestParameterMap.containsKey(clientId)) {
|
|
260 String newValue = requestParameterMap.get(clientId);
|
|
261 setSubmittedValue(component, newValue);
|
|
262 if (logger.isLoggable(Level.FINE)) {
|
|
263 logger.fine("submitted value for UISelectOne component "
|
|
264 +
|
|
265 component.getId()
|
|
266 + " after decoding "
|
|
267 + newValue);
|
|
268 }
|
|
269
|
|
270 } else {
|
|
271 // there is no value, but this is different from a null
|
|
272 // value.
|
|
273 setSubmittedValue(component, RIConstants.NO_VALUE);
|
|
274 }
|
|
275 }
|
|
276
|
|
277 }
|
|
278
|
|
279
|
|
280 @Override
|
|
281 public void encodeBegin(FacesContext context, UIComponent component)
|
|
282 throws IOException {
|
|
283
|
|
284 rendererParamsNotNull(context, component);
|
|
285
|
|
286 }
|
|
287
|
|
288
|
|
289 @Override
|
|
290 public void encodeEnd(FacesContext context, UIComponent component)
|
|
291 throws IOException {
|
|
292
|
|
293 rendererParamsNotNull(context, component);
|
|
294
|
|
295 if (!shouldEncode(component)) {
|
|
296 return;
|
|
297 }
|
|
298
|
|
299 renderSelect(context, component);
|
|
300
|
|
301 }
|
|
302
|
|
303
|
|
304 @Override
|
|
305 public Object getConvertedValue(FacesContext context, UIComponent component,
|
|
306 Object submittedValue)
|
|
307 throws ConverterException {
|
|
308
|
|
309 if (component instanceof UISelectMany) {
|
|
310 // need to set the 'TARGET_COMPONENT_ATTRIBUTE_NAME' request attr so the
|
|
311 // coerce-value call in the jsf-api UISelectMany.matchValue will work
|
|
312 // (need a better way to determine the currently processing UIComponent ...)
|
|
313 RequestStateManager.set(context,
|
|
314 RequestStateManager.TARGET_COMPONENT_ATTRIBUTE_NAME,
|
|
315 component);
|
|
316 return convertSelectManyValue(context,
|
|
317 ((UISelectMany) component),
|
|
318 (String[]) submittedValue);
|
|
319 } else {
|
|
320 return convertSelectOneValue(context,
|
|
321 ((UISelectOne) component),
|
|
322 (String) submittedValue);
|
|
323 }
|
|
324
|
|
325 }
|
|
326
|
|
327 // ------------------------------------------------------- Protected Methods
|
|
328
|
|
329
|
|
330 /*
|
|
331 * Converts the provided string array and places them into the correct provided model type.
|
|
332 */
|
|
333 protected Object convertSelectManyValuesForModel(FacesContext context,
|
|
334 UISelectMany uiSelectMany,
|
|
335 Class modelType,
|
|
336 String[] newValues) {
|
|
337
|
|
338 if (modelType.isArray()) {
|
|
339 return convertSelectManyValues(context,
|
|
340 uiSelectMany,
|
|
341 modelType,
|
|
342 newValues);
|
|
343 } else if (Collection.class.isAssignableFrom(modelType)) {
|
|
344 Object[] values = (Object[]) convertSelectManyValues(context,
|
|
345 uiSelectMany,
|
|
346 Object[].class,
|
|
347 newValues);
|
|
348
|
|
349 Collection targetCollection = null;
|
|
350
|
|
351 // see if the collectionType hint is available, if so, use that
|
|
352 Object collectionTypeHint = uiSelectMany.getAttributes().get("collectionType");
|
|
353 if (collectionTypeHint != null) {
|
|
354 targetCollection = createCollectionFromHint(collectionTypeHint);
|
|
355 } else {
|
|
356 // try to get a new Collection to store the values based
|
|
357 // by trying to create a clone
|
|
358 Collection currentValue = (Collection) uiSelectMany.getValue();
|
|
359 if (currentValue != null) {
|
|
360 targetCollection = cloneValue(currentValue);
|
|
361 }
|
|
362
|
|
363 // No cloned instance so if the modelType happens to represent a
|
|
364 // concrete type (probably not the norm) try to reflect a
|
|
365 // no-argument constructor and invoke if available.
|
|
366 if (targetCollection == null) {
|
|
367 //noinspection unchecked
|
|
368 targetCollection =
|
|
369 createCollection(currentValue, modelType);
|
|
370 }
|
|
371
|
|
372 // No suitable instance to work with, make our best guess
|
|
373 // based on the type.
|
|
374 if (targetCollection == null) {
|
|
375 //noinspection unchecked
|
|
376 targetCollection = bestGuess(modelType, values.length);
|
|
377 }
|
|
378 }
|
|
379
|
|
380 //noinspection ManualArrayToCollectionCopy
|
|
381 for (Object v : values) {
|
|
382 //noinspection unchecked
|
|
383 targetCollection.add(v);
|
|
384 }
|
|
385
|
|
386 return targetCollection;
|
|
387 } else if (Object.class.equals(modelType)) {
|
|
388 return convertSelectManyValues(context,
|
|
389 uiSelectMany,
|
|
390 Object[].class,
|
|
391 newValues);
|
|
392 } else {
|
|
393 throw new FacesException("Target model Type is no a Collection or Array");
|
|
394 }
|
|
395
|
|
396 }
|
|
397
|
|
398
|
|
399
|
|
400
|
|
401 protected Object convertSelectManyValues(FacesContext context,
|
|
402 UISelectMany uiSelectMany,
|
|
403 Class arrayClass,
|
|
404 String[] newValues)
|
|
405 throws ConverterException {
|
|
406
|
|
407 Object result;
|
|
408 Converter converter;
|
|
409 int len = (null != newValues ? newValues.length : 0);
|
|
410
|
|
411 Class elementType = arrayClass.getComponentType();
|
|
412
|
|
413 // Optimization: If the elementType is String, we don't need
|
|
414 // conversion. Just return newValues.
|
|
415 if (elementType.equals(String.class)) {
|
|
416 return newValues;
|
|
417 }
|
|
418
|
|
419 try {
|
|
420 result = Array.newInstance(elementType, len);
|
|
421 } catch (Exception e) {
|
|
422 throw new ConverterException(e);
|
|
423 }
|
|
424
|
|
425 // bail out now if we have no new values, returning our
|
|
426 // oh-so-useful zero-length array.
|
|
427 if (null == newValues) {
|
|
428 return result;
|
|
429 }
|
|
430
|
|
431 // obtain a converter.
|
|
432
|
|
433 // attached converter takes priority
|
|
434 if (null == (converter = uiSelectMany.getConverter())) {
|
|
435 // Otherwise, look for a by-type converter
|
|
436 if (null == (converter = Util.getConverterForClass(elementType,
|
|
437 context))) {
|
|
438 // if that fails, and the attached values are of Object type,
|
|
439 // we don't need conversion.
|
|
440 if (elementType.equals(Object.class)) {
|
|
441 return newValues;
|
|
442 }
|
|
443 StringBuffer valueStr = new StringBuffer();
|
|
444 for (int i = 0; i < len; i++) {
|
|
445 if (i == 0) {
|
|
446 valueStr.append(newValues[i]);
|
|
447 } else {
|
|
448 valueStr.append(' ').append(newValues[i]);
|
|
449 }
|
|
450 }
|
|
451 Object[] params = {
|
|
452 valueStr.toString(),
|
|
453 "null Converter"
|
|
454 };
|
|
455
|
|
456 throw new ConverterException(MessageUtils.getExceptionMessage(
|
|
457 MessageUtils.CONVERSION_ERROR_MESSAGE_ID, params));
|
|
458 }
|
|
459 }
|
|
460
|
|
461 assert(null != result);
|
|
462 if (elementType.isPrimitive()) {
|
|
463 for (int i = 0; i < len; i++) {
|
|
464 if (elementType.equals(Boolean.TYPE)) {
|
|
465 Array.setBoolean(result, i,
|
|
466 ((Boolean) converter.getAsObject(context,
|
|
467 uiSelectMany,
|
|
468 newValues[i])));
|
|
469 } else if (elementType.equals(Byte.TYPE)) {
|
|
470 Array.setByte(result, i,
|
|
471 ((Byte) converter.getAsObject(context,
|
|
472 uiSelectMany,
|
|
473 newValues[i])));
|
|
474 } else if (elementType.equals(Double.TYPE)) {
|
|
475 Array.setDouble(result, i,
|
|
476 ((Double) converter.getAsObject(context,
|
|
477 uiSelectMany,
|
|
478 newValues[i])));
|
|
479 } else if (elementType.equals(Float.TYPE)) {
|
|
480 Array.setFloat(result, i,
|
|
481 ((Float) converter.getAsObject(context,
|
|
482 uiSelectMany,
|
|
483 newValues[i])));
|
|
484 } else if (elementType.equals(Integer.TYPE)) {
|
|
485 Array.setInt(result, i,
|
|
486 ((Integer) converter.getAsObject(context,
|
|
487 uiSelectMany,
|
|
488 newValues[i])));
|
|
489 } else if (elementType.equals(Character.TYPE)) {
|
|
490 Array.setChar(result, i,
|
|
491 ((Character) converter.getAsObject(context,
|
|
492 uiSelectMany,
|
|
493 newValues[i])));
|
|
494 } else if (elementType.equals(Short.TYPE)) {
|
|
495 Array.setShort(result, i,
|
|
496 ((Short) converter.getAsObject(context,
|
|
497 uiSelectMany,
|
|
498 newValues[i])));
|
|
499 } else if (elementType.equals(Long.TYPE)) {
|
|
500 Array.setLong(result, i,
|
|
501 ((Long) converter.getAsObject(context,
|
|
502 uiSelectMany,
|
|
503 newValues[i])));
|
|
504 }
|
|
505 }
|
|
506 } else {
|
|
507 for (int i = 0; i < len; i++) {
|
|
508 if (logger.isLoggable(Level.FINE)) {
|
|
509 Object converted = converter.getAsObject(context,
|
|
510 uiSelectMany,
|
|
511 newValues[i]);
|
|
512 logger.fine("String value: " + newValues[i] +
|
|
513 " converts to : " + converted);
|
|
514 }
|
|
515 Array.set(result, i, converter.getAsObject(context,
|
|
516 uiSelectMany,
|
|
517 newValues[i]));
|
|
518 }
|
|
519 }
|
|
520 return result;
|
|
521
|
|
522 }
|
|
523
|
|
524
|
|
525 protected boolean renderOption(FacesContext context,
|
|
526 UIComponent component,
|
|
527 Converter converter,
|
|
528 SelectItem curItem,
|
|
529 Object currentSelections,
|
|
530 Object[] submittedValues,
|
|
531 OptionComponentInfo optionInfo) throws IOException {
|
|
532
|
|
533 Object valuesArray;
|
|
534 Object itemValue;
|
|
535 String valueString = getFormattedValue(context, component,
|
|
536 curItem.getValue(), converter);
|
|
537 boolean containsValue;
|
|
538 if (submittedValues != null) {
|
|
539 containsValue = containsaValue(submittedValues);
|
|
540 if (containsValue) {
|
|
541 valuesArray = submittedValues;
|
|
542 itemValue = valueString;
|
|
543 } else {
|
|
544 valuesArray = currentSelections;
|
|
545 itemValue = curItem.getValue();
|
|
546 }
|
|
547 } else {
|
|
548 valuesArray = currentSelections;
|
|
549 itemValue = curItem.getValue();
|
|
550 }
|
|
551
|
|
552 boolean isSelected = isSelected(context, component, itemValue, valuesArray, converter);
|
|
553 if (optionInfo.isHideNoSelection()
|
|
554 && curItem.isNoSelectionOption()
|
|
555 && currentSelections != null
|
|
556 && !isSelected) {
|
|
557 return false;
|
|
558 }
|
|
559
|
|
560 ResponseWriter writer = context.getResponseWriter();
|
|
561 assert (writer != null);
|
|
562 writer.writeText("\t", component, null);
|
|
563 writer.startElement("option", component);
|
|
564 writer.writeAttribute("value", valueString, "value");
|
|
565
|
|
566 if (isSelected) {
|
|
567 writer.writeAttribute("selected", true, "selected");
|
|
568 }
|
|
569
|
|
570 // if the component is disabled, "disabled" attribute would be rendered
|
|
571 // on "select" tag, so don't render "disabled" on every option.
|
|
572 if ((!optionInfo.isDisabled()) && curItem.isDisabled()) {
|
|
573 writer.writeAttribute("disabled", true, "disabled");
|
|
574 }
|
|
575
|
|
576
|
|
577 //jurzua
|
|
578 if(StringUtils.isNotEmpty(curItem.getStyle())){
|
|
579 writer.writeAttribute("style", curItem.getStyle(), curItem.getStyle());
|
|
580 }
|
|
581
|
|
582
|
|
583 String labelClass;
|
|
584 if (optionInfo.isDisabled() || curItem.isDisabled()) {
|
|
585 labelClass = optionInfo.getDisabledClass();
|
|
586 } else {
|
|
587 labelClass = optionInfo.getEnabledClass();
|
|
588 }
|
|
589 if (labelClass != null) {
|
|
590 writer.writeAttribute("class", labelClass, "labelClass");
|
|
591 }
|
|
592
|
|
593 if (curItem.isEscape()) {
|
|
594 String label = curItem.getLabel();
|
|
595 if (label == null) {
|
|
596 label = valueString;
|
|
597 }
|
|
598 writer.writeText(label, component, "label");
|
|
599 } else {
|
|
600 writer.write(curItem.getLabel());
|
|
601 }
|
|
602 writer.endElement("option");
|
|
603 writer.writeText("\n", component, null);
|
|
604 return true;
|
|
605 }
|
|
606
|
|
607
|
|
608 protected void writeDefaultSize(ResponseWriter writer, int itemCount)
|
|
609 throws IOException {
|
|
610
|
|
611 // if size is not specified default to 1.
|
|
612 writer.writeAttribute("size", "1", "size");
|
|
613
|
|
614 }
|
|
615
|
|
616
|
|
617 protected boolean containsaValue(Object valueArray) {
|
|
618
|
|
619 if (null != valueArray) {
|
|
620 int len = Array.getLength(valueArray);
|
|
621 for (int i = 0; i < len; i++) {
|
|
622 Object value = Array.get(valueArray, i);
|
|
623 if (value != null && !(value.equals(RIConstants.NO_VALUE))) {
|
|
624 return true;
|
|
625 }
|
|
626 }
|
|
627 }
|
|
628 return false;
|
|
629
|
|
630 }
|
|
631
|
|
632
|
|
633 protected Object getCurrentSelectedValues(UIComponent component) {
|
|
634
|
|
635 if (component instanceof UISelectMany) {
|
|
636 UISelectMany select = (UISelectMany) component;
|
|
637 Object value = select.getValue();
|
|
638 if (value == null) {
|
|
639 return null;
|
|
640 } else if (value instanceof Collection) {
|
|
641 return ((Collection) value).toArray();
|
|
642 } else if (value.getClass().isArray()) {
|
|
643 if (Array.getLength(value) == 0) {
|
|
644 return null;
|
|
645 }
|
|
646 } else if (!value.getClass().isArray()) {
|
|
647 logger.warning(
|
|
648 "The UISelectMany value should be an array or a collection type, the actual type is " +
|
|
649 value.getClass().getName());
|
|
650 }
|
|
651
|
|
652 return value;
|
|
653 }
|
|
654
|
|
655 UISelectOne select = (UISelectOne) component;
|
|
656 Object val = select.getValue();
|
|
657 if (val != null) {
|
|
658 return new Object[] { val };
|
|
659 }
|
|
660 return null;
|
|
661
|
|
662 }
|
|
663
|
|
664
|
|
665 // To derive a selectOne type component from this, override
|
|
666 // these methods.
|
|
667 protected String getMultipleText(UIComponent component) {
|
|
668
|
|
669 if (component instanceof UISelectMany) {
|
|
670 return " multiple ";
|
|
671 }
|
|
672 return "";
|
|
673
|
|
674 }
|
|
675
|
|
676 protected Object[] getSubmittedSelectedValues(UIComponent component) {
|
|
677
|
|
678 if (component instanceof UISelectMany) {
|
|
679 UISelectMany select = (UISelectMany) component;
|
|
680 return (Object[]) select.getSubmittedValue();
|
|
681 }
|
|
682
|
|
683 UISelectOne select = (UISelectOne) component;
|
|
684 Object val = select.getSubmittedValue();
|
|
685 if (val != null) {
|
|
686 return new Object[] { val };
|
|
687 }
|
|
688 return null;
|
|
689
|
|
690 }
|
|
691
|
|
692
|
|
693 protected boolean isSelected(FacesContext context,
|
|
694 UIComponent component,
|
|
695 Object itemValue,
|
|
696 Object valueArray,
|
|
697 Converter converter) {
|
|
698
|
|
699 if (itemValue == null && valueArray == null) {
|
|
700 return true;
|
|
701 }
|
|
702 if (null != valueArray) {
|
|
703 if (!valueArray.getClass().isArray()) {
|
|
704 logger.warning("valueArray is not an array, the actual type is " +
|
|
705 valueArray.getClass());
|
|
706 return valueArray.equals(itemValue);
|
|
707 }
|
|
708 int len = Array.getLength(valueArray);
|
|
709 for (int i = 0; i < len; i++) {
|
|
710 Object value = Array.get(valueArray, i);
|
|
711 if (value == null && itemValue == null) {
|
|
712 return true;
|
|
713 } else {
|
|
714 if ((value == null) ^ (itemValue == null)) {
|
|
715 continue;
|
|
716 }
|
|
717 Object compareValue;
|
|
718 if (converter == null) {
|
|
719 compareValue = coerceToModelType(context,
|
|
720 itemValue,
|
|
721 value.getClass());
|
|
722 } else {
|
|
723 compareValue = itemValue;
|
|
724 if (compareValue instanceof String && !(value instanceof String)) {
|
|
725 // type mismatch between the time and the value we're
|
|
726 // comparing. Invoke the Converter.
|
|
727 compareValue = converter.getAsObject(context,
|
|
728 component,
|
|
729 (String) compareValue);
|
|
730 }
|
|
731 }
|
|
732
|
|
733 if (value.equals(compareValue)) {
|
|
734 return (true);
|
|
735 }
|
|
736 }
|
|
737 }
|
|
738 }
|
|
739 return false;
|
|
740
|
|
741 }
|
|
742
|
|
743
|
|
744 protected int renderOptions(FacesContext context,
|
|
745 UIComponent component,
|
|
746 Iterator<SelectItem> items)
|
|
747 throws IOException {
|
|
748
|
|
749 ResponseWriter writer = context.getResponseWriter();
|
|
750 assert(writer != null);
|
|
751
|
|
752 Converter converter = null;
|
|
753 if(component instanceof ValueHolder) {
|
|
754 converter = ((ValueHolder)component).getConverter();
|
|
755 }
|
|
756 int count = 0;
|
|
757 Object currentSelections = getCurrentSelectedValues(component);
|
|
758 Object[] submittedValues = getSubmittedSelectedValues(component);
|
|
759 Map<String,Object> attributes = component.getAttributes();
|
|
760 boolean componentDisabled = Util.componentIsDisabled(component);
|
|
761
|
|
762 OptionComponentInfo optionInfo =
|
|
763 new OptionComponentInfo((String) attributes.get("disabledClass"),
|
|
764 (String) attributes.get("enabledClass"),
|
|
765 componentDisabled,
|
|
766 isHideNoSelection(component));
|
|
767 RequestStateManager.set(context,
|
|
768 RequestStateManager.TARGET_COMPONENT_ATTRIBUTE_NAME,
|
|
769 component);
|
|
770 while (items.hasNext()) {
|
|
771 SelectItem item = items.next();
|
|
772
|
|
773 if (item instanceof SelectItemGroup) {
|
|
774 // render OPTGROUP
|
|
775 writer.startElement("optgroup", component);
|
|
776 writer.writeAttribute("label", item.getLabel(), "label");
|
|
777
|
|
778 // if the component is disabled, "disabled" attribute would be rendered
|
|
779 // on "select" tag, so don't render "disabled" on every option.
|
|
780 if ((!componentDisabled) && item.isDisabled()) {
|
|
781 writer.writeAttribute("disabled", true, "disabled");
|
|
782 }
|
|
783 count++;
|
|
784 // render options of this group.
|
|
785 SelectItem[] itemsArray =
|
|
786 ((SelectItemGroup) item).getSelectItems();
|
|
787 for (int i = 0; i < itemsArray.length; ++i) {
|
|
788 if (renderOption(context,
|
|
789 component,
|
|
790 converter,
|
|
791 itemsArray[i],
|
|
792 currentSelections,
|
|
793 submittedValues,
|
|
794 optionInfo)) {
|
|
795 count++;
|
|
796 }
|
|
797 }
|
|
798 writer.endElement("optgroup");
|
|
799 } else {
|
|
800 if (renderOption(context,
|
|
801 component,
|
|
802 converter,
|
|
803 item,
|
|
804 currentSelections,
|
|
805 submittedValues,
|
|
806 optionInfo)) {
|
|
807 count ++;
|
|
808 }
|
|
809 }
|
|
810 }
|
|
811
|
|
812 return count;
|
|
813
|
|
814 }
|
|
815
|
|
816
|
|
817 // Render the "select" portion..
|
|
818 //
|
|
819 protected void renderSelect(FacesContext context,
|
|
820 UIComponent component) throws IOException {
|
|
821
|
|
822 ResponseWriter writer = context.getResponseWriter();
|
|
823 assert(writer != null);
|
|
824
|
|
825 if (logger.isLoggable(Level.FINER)) {
|
|
826 logger.log(Level.FINER, "Rendering 'select'");
|
|
827 }
|
|
828 writer.startElement("select", component);
|
|
829 writeIdAttributeIfNecessary(context, writer, component);
|
|
830 writer.writeAttribute("name", component.getClientId(context),
|
|
831 "clientId");
|
|
832 // render styleClass attribute if present.
|
|
833 String styleClass;
|
|
834 if (null !=
|
|
835 (styleClass =
|
|
836 (String) component.getAttributes().get("styleClass"))) {
|
|
837 writer.writeAttribute("class", styleClass, "styleClass");
|
|
838 }
|
|
839 if (!getMultipleText(component).equals("")) {
|
|
840 writer.writeAttribute("multiple", true, "multiple");
|
|
841 }
|
|
842
|
|
843 // Determine how many option(s) we need to render, and update
|
|
844 // the component's "size" attribute accordingly; The "size"
|
|
845 // attribute will be rendered as one of the "pass thru" attributes
|
|
846 Iterator<SelectItem> items = RenderKitUtils.getSelectItems(context, component);
|
|
847
|
|
848 // render the options to a buffer now so that we can determine
|
|
849 // the size
|
|
850 FastStringWriter bufferedWriter = new FastStringWriter(128);
|
|
851 context.setResponseWriter(writer.cloneWithWriter(bufferedWriter));
|
|
852 int count = renderOptions(context, component, items);
|
|
853 context.setResponseWriter(writer);
|
|
854 // If "size" is *not* set explicitly, we have to default it correctly
|
|
855 Integer size = (Integer) component.getAttributes().get("size");
|
|
856 if (size == null || size == Integer.MIN_VALUE) {
|
|
857 size = count;
|
|
858 }
|
|
859 writeDefaultSize(writer, size);
|
|
860
|
|
861 RenderKitUtils.renderPassThruAttributes(context,
|
|
862 writer,
|
|
863 component,
|
|
864 ATTRIBUTES,
|
|
865 getNonOnChangeBehaviors(component));
|
|
866 RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer,
|
|
867 component);
|
|
868
|
|
869 RenderKitUtils.renderOnchange(context, component, false);
|
|
870
|
|
871 // Now, write the buffered option content
|
|
872 writer.write(bufferedWriter.toString());
|
|
873
|
|
874 writer.endElement("select");
|
|
875
|
|
876 }
|
|
877
|
|
878 protected Object coerceToModelType(FacesContext ctx,
|
|
879 Object value,
|
|
880 Class itemValueType) {
|
|
881
|
|
882 Object newValue;
|
|
883 try {
|
|
884 ExpressionFactory ef = ctx.getApplication().getExpressionFactory();
|
|
885 newValue = ef.coerceToType(value, itemValueType);
|
|
886 } catch (ELException ele) {
|
|
887 newValue = value;
|
|
888 } catch (IllegalArgumentException iae) {
|
|
889 // If coerceToType fails, per the docs it should throw
|
|
890 // an ELException, however, GF 9.0 and 9.0u1 will throw
|
|
891 // an IllegalArgumentException instead (see GF issue 1527).
|
|
892 newValue = value;
|
|
893 }
|
|
894
|
|
895 return newValue;
|
|
896
|
|
897 }
|
|
898
|
|
899
|
|
900 /**
|
|
901 * @param collection a Collection instance
|
|
902 *
|
|
903 * @return a new <code>Collection</code> instance or null if the instance
|
|
904 * cannot be created
|
|
905 */
|
|
906 protected Collection createCollection(Collection collection,
|
|
907 Class<? extends Collection> fallBackType) {
|
|
908
|
|
909 Class<? extends Collection> lookupClass =
|
|
910 ((collection != null) ? collection.getClass() : fallBackType);
|
|
911
|
|
912 if (!lookupClass.isInterface()
|
|
913 && !Modifier.isAbstract(lookupClass.getModifiers())) {
|
|
914 try {
|
|
915 return lookupClass.newInstance();
|
|
916 } catch (Exception e) {
|
|
917 if (logger.isLoggable(Level.SEVERE)) {
|
|
918 logger.log(Level.SEVERE,
|
|
919 "Unable to create new Collection instance for type "
|
|
920 + lookupClass.getName(),
|
|
921 e);
|
|
922 }
|
|
923 }
|
|
924 }
|
|
925
|
|
926 return null;
|
|
927
|
|
928 }
|
|
929
|
|
930
|
|
931 /**
|
|
932 * <p>
|
|
933 * Utility method to invoke the the <code>clone</code> method on the provided
|
|
934 * value.
|
|
935 * </p>
|
|
936 *
|
|
937 * @param value the value to clone
|
|
938 * @return the result of invoking <code>clone()</code> or <code>null</code>
|
|
939 * if the value could not be cloned or does not implement the
|
|
940 * {@link Cloneable} interface
|
|
941 */
|
|
942 protected Collection cloneValue(Object value) {
|
|
943
|
|
944 if (value instanceof Cloneable) {
|
|
945 // even though Clonable marks an instance of a Class as being
|
|
946 // safe to call .clone(), .clone() by default is protected.
|
|
947 // The Collection classes that do implement Clonable do so at variable
|
|
948 // locations within the class hierarchy, so we're stuck having to
|
|
949 // use reflection.
|
|
950 Method clone =
|
|
951 ReflectionUtils.lookupMethod(value.getClass(), "clone");
|
|
952 if (clone != null) {
|
|
953 try {
|
|
954 Collection c = (Collection) clone.invoke(value);
|
|
955 c.clear();
|
|
956 return c;
|
|
957 } catch (Exception e) {
|
|
958 if (logger.isLoggable(Level.SEVERE)) {
|
|
959 logger.log(Level.SEVERE,
|
|
960 "Unable to clone collection type: {0}",
|
|
961 value.getClass().getName());
|
|
962 logger.log(Level.SEVERE, e.toString(), e);
|
|
963 }
|
|
964 }
|
|
965 } else {
|
|
966 // no public clone method
|
|
967 if (logger.isLoggable(Level.FINE)) {
|
|
968 logger.log(Level.FINE,
|
|
969 "Type {0} implements Cloneable, but has no public clone method.",
|
|
970 value.getClass().getName());
|
|
971 }
|
|
972 }
|
|
973 }
|
|
974
|
|
975 return null;
|
|
976
|
|
977 }
|
|
978
|
|
979
|
|
980 /**
|
|
981 * @param type the target model type
|
|
982 * @param initialSize the initial size of the <code>Collection</code>
|
|
983 * @return a <code>Collection</code> instance that best matches
|
|
984 * <code>type</code>
|
|
985 */
|
|
986 protected Collection bestGuess(Class<? extends Collection> type,
|
|
987 int initialSize) {
|
|
988
|
|
989 if (SortedSet.class.isAssignableFrom(type)) {
|
|
990 return new TreeSet();
|
|
991 } else if (Queue.class.isAssignableFrom(type)) {
|
|
992 return new LinkedList();
|
|
993 } else if (Set.class.isAssignableFrom(type)) {
|
|
994 return new HashSet(initialSize);
|
|
995 } else {
|
|
996 // this covers the where type is List or Collection
|
|
997 return new ArrayList(initialSize);
|
|
998 }
|
|
999
|
|
1000 }
|
|
1001
|
|
1002
|
|
1003 /**
|
|
1004 * <p>
|
|
1005 * Create a collection from the provided hint.
|
|
1006 * @param collectionTypeHint the Collection type as either a String or Class
|
|
1007 * @return a new Collection instance
|
|
1008 */
|
|
1009 protected Collection createCollectionFromHint(Object collectionTypeHint) {
|
|
1010
|
|
1011 Class<? extends Collection> collectionType;
|
|
1012 if (collectionTypeHint instanceof Class) {
|
|
1013 //noinspection unchecked
|
|
1014 collectionType = (Class<? extends Collection>) collectionTypeHint;
|
|
1015 } else if (collectionTypeHint instanceof String) {
|
|
1016 try {
|
|
1017 //noinspection unchecked
|
|
1018 collectionType = Util.loadClass((String) collectionTypeHint,
|
|
1019 this);
|
|
1020 } catch (ClassNotFoundException cnfe) {
|
|
1021 throw new FacesException(cnfe);
|
|
1022 }
|
|
1023 } else {
|
|
1024 // RELEASE_PENDING (i18n)
|
|
1025 throw new FacesException(
|
|
1026 "'collectionType' should resolve to type String or Class. Found: "
|
|
1027 + collectionTypeHint.getClass().getName());
|
|
1028 }
|
|
1029
|
|
1030 Collection c = createCollection(null, collectionType);
|
|
1031 if (c == null) {
|
|
1032 // RELEASE_PENDING (i18n)
|
|
1033 throw new FacesException("Unable to create collection type " + collectionType);
|
|
1034 }
|
|
1035 return c;
|
|
1036
|
|
1037 }
|
|
1038
|
|
1039
|
|
1040 protected boolean isHideNoSelection(UIComponent component) {
|
|
1041
|
|
1042 Object result = component.getAttributes().get("hideNoSelectionOption");
|
|
1043 return ((result != null) ? (Boolean) result : false);
|
|
1044
|
|
1045 }
|
|
1046
|
|
1047 } // end of class MenuRenderer
|