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