Mercurial > hg > ismi-richfaces
comparison src/main/java/com/sun/faces/renderkit/html_basic/MenuRenderer.java @ 1:2e911857a759
(none)
author | jurzua |
---|---|
date | Wed, 29 Oct 2014 14:00:28 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
0:74df02964906 | 1:2e911857a759 |
---|---|
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 |