View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package javax.faces.component;
20  
21  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
22  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFListener;
23  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
24  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
25  
26  import javax.el.ValueExpression;
27  import javax.faces.application.FacesMessage;
28  import javax.faces.application.ProjectStage;
29  import javax.faces.context.ExternalContext;
30  import javax.faces.context.FacesContext;
31  import javax.faces.convert.Converter;
32  import javax.faces.convert.ConverterException;
33  import javax.faces.el.EvaluationException;
34  import javax.faces.el.MethodBinding;
35  import javax.faces.event.AbortProcessingException;
36  import javax.faces.event.ExceptionQueuedEvent;
37  import javax.faces.event.ExceptionQueuedEventContext;
38  import javax.faces.event.FacesEvent;
39  import javax.faces.event.PhaseId;
40  import javax.faces.event.PostValidateEvent;
41  import javax.faces.event.PreValidateEvent;
42  import javax.faces.event.ValueChangeEvent;
43  import javax.faces.event.ValueChangeListener;
44  import javax.faces.render.Renderer;
45  import javax.faces.validator.Validator;
46  import javax.faces.webapp.FacesServlet;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.Collection;
50  import java.util.HashMap;
51  import java.util.LinkedList;
52  import java.util.List;
53  import java.util.Map;
54  
55  /**
56   * UICommand is a base abstraction for components that implement ActionSource.
57   * <p>
58   * See the javadoc for this class in the <a href="http://java.sun.com/j2ee/javaserverfaces/1.2/docs/api/index.html">JSF
59   * Specification</a> for further details.
60   * <p>
61   */
62  @JSFComponent(defaultRendererType = "javax.faces.Text")
63  public class UIInput extends UIOutput implements EditableValueHolder
64  {
65      public static final String COMPONENT_TYPE = "javax.faces.Input";
66      public static final String COMPONENT_FAMILY = "javax.faces.Input";
67  
68      public static final String CONVERSION_MESSAGE_ID = "javax.faces.component.UIInput.CONVERSION";
69      public static final String REQUIRED_MESSAGE_ID = "javax.faces.component.UIInput.REQUIRED";
70      public static final String UPDATE_MESSAGE_ID = "javax.faces.component.UIInput.UPDATE";
71  
72      /**
73       * Force validation on empty fields (By default is auto, which means it is only 
74       * enabled when Bean Validation binaries are available on the current classpath).
75       */
76      @JSFWebConfigParam(defaultValue="auto", expectedValues="auto, true, false", since="2.0", group="validation")
77      public static final String VALIDATE_EMPTY_FIELDS_PARAM_NAME = "javax.faces.VALIDATE_EMPTY_FIELDS";
78      
79      /** 
80       * Submitted values are decoded as null values instead empty strings.
81       * 
82       * <p>Note this param is ignored for components extending from UISelectOne/UISelectMany.</p>
83       **/
84      @JSFWebConfigParam(defaultValue="false", expectedValues="true, false", since="2.0", group="validation")
85      private static final String EMPTY_VALUES_AS_NULL_PARAM_NAME
86              = "javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL";
87  
88      // our own, cached key
89      private static final String MYFACES_EMPTY_VALUES_AS_NULL_PARAM_NAME =
90        "org.apache.myfaces.UIInput.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL";
91      
92      /**
93       * Extended debug info is stored under this key in the request
94       * map for every UIInput component when in Development mode.
95       * ATTENTION: this constant is duplicate in org.apache.myfaces.renderkit.ErrorPageWriter
96       */
97      private static final String DEBUG_INFO_KEY = "org.apache.myfaces.debug.DEBUG_INFO";
98  
99      private static final Validator[] EMPTY_VALIDATOR_ARRAY = new Validator[0];
100     
101     private _DeltaList<Validator> _validatorList;
102 
103     /**
104      * Construct an instance of the UIInput.
105      */
106     public UIInput()
107     {
108         setRendererType("javax.faces.Text");
109     }
110 
111     @Override
112     public String getFamily()
113     {
114         return COMPONENT_FAMILY;
115     }
116 
117     /**
118      * Store the specified object as the "local value" of this component. The value-binding named "value" (if any) is
119      * ignored; the object is only stored locally on this component. During the "update model" phase, if there is a
120      * value-binding named "value" then this local value will be stored via that value-binding and the "local value"
121      * reset to null.
122      */
123     @Override
124     public void setValue(Object value)
125     {
126         FacesContext facesContext = getFacesContext();
127         if (facesContext != null && facesContext.isProjectStage(ProjectStage.Development))
128         {
129             // extended debug-info when in Development mode
130             _createFieldDebugInfo(facesContext, "localValue",
131                     getLocalValue(), value, 1);
132         }
133         setLocalValueSet(true);
134         super.setValue(value);
135     }
136     
137     /**
138      * Return the current value of this component.
139      * <p>
140      * If a submitted value has been converted but not yet pushed into the
141      * model, then return that locally-cached value (see isLocalValueSet).
142      * <p>
143      * Otherwise, evaluate an EL expression to fetch a value from the model. 
144      */
145     public Object getValue()
146     {
147         if (isLocalValueSet())
148         {
149             return super.getLocalValue();
150         }
151         return super.getValue();
152     }
153 
154     /**
155      * Set the "submitted value" of this component from the relevant data in the current servlet request object.
156      * <p>
157      * If this component is not rendered, then do nothing; no output would have been sent to the client so no input is
158      * expected.
159      * <p>
160      * Invoke the inherited functionality, which typically invokes the renderer associated with this component to
161      * extract and set this component's "submitted value".
162      * <p>
163      * If this component is marked "immediate", then immediately apply validation to the submitted value found. On
164      * error, call context method "renderResponse" which will force processing to leap to the "render
165      * response" phase as soon as the "decode" step has completed for all other components.
166      */
167     @Override
168     public void processDecodes(FacesContext context)
169     {
170         if (context == null)
171         {
172             throw new NullPointerException("context");
173         }
174         try
175         {
176             setCachedFacesContext(context);
177             pushComponentToEL(context, this);
178             if (!isRendered())
179             {
180                 return;
181             }
182         }
183         finally
184         {
185             setCachedFacesContext(null);
186             popComponentFromEL(context);
187         }
188         super.processDecodes(context);
189         try
190         {
191             setCachedFacesContext(context);
192             pushComponentToEL(context, this);
193             if (isImmediate())
194             {
195                 //Pre validation event dispatch for component
196                 context.getApplication().publishEvent(context,  PreValidateEvent.class, getClass(), this);
197                 try
198                 {
199                     validate(context);
200                 }
201                 catch (RuntimeException e)
202                 {
203                     context.renderResponse();
204                     throw e;
205                 }
206                 finally
207                 {
208                     context.getApplication().publishEvent(context,  PostValidateEvent.class, getClass(), this);
209                 }
210                 if (!isValid())
211                 {
212                     context.renderResponse();
213                 }
214             }
215         }
216         finally
217         {
218             setCachedFacesContext(null);
219             popComponentFromEL(context);
220         }
221     }
222 
223     @Override
224     public void processValidators(FacesContext context)
225     {
226         if (context == null)
227         {
228             throw new NullPointerException("context");
229         }
230         try
231         {
232             setCachedFacesContext(context);
233             pushComponentToEL(context, this);
234             if (!isRendered())
235             {
236                 return;
237             }
238         }
239         finally
240         {
241             setCachedFacesContext(null);
242             popComponentFromEL(context);
243         }
244 
245         //super.processValidators(context);
246         
247         // Call the processValidators() method of all facets and children of this UIComponent, in the order
248         // determined by a call to getFacetsAndChildren().
249         int facetCount = getFacetCount();
250         if (facetCount > 0)
251         {
252             for (UIComponent facet : getFacets().values())
253             {
254                 facet.processValidators(context);
255             }
256         }
257 
258         for (int i = 0, childCount = getChildCount(); i < childCount; i++)
259         {
260             UIComponent child = getChildren().get(i);
261             child.processValidators(context);
262         }
263 
264         try
265         {
266             setCachedFacesContext(context);
267             pushComponentToEL(context, this);
268             if (!isImmediate())
269             {
270                 //Pre validation event dispatch for component
271                 context.getApplication().publishEvent(context,  PreValidateEvent.class, getClass(), this);
272                 try
273                 {
274                     validate(context);
275                 }
276                 catch (RuntimeException e)
277                 {
278                     context.renderResponse();
279                     throw e;
280                 }
281                 finally
282                 {
283                     context.getApplication().publishEvent(context,  PostValidateEvent.class, getClass(), this);
284                 }
285                 if (!isValid())
286                 {
287                     context.validationFailed();
288                     context.renderResponse();
289                 }
290             }
291         }
292         finally
293         {
294             setCachedFacesContext(null);
295             popComponentFromEL(context);
296         }
297     }
298 
299     @Override
300     public void processUpdates(FacesContext context)
301     {
302         if (context == null)
303         {
304             throw new NullPointerException("context");
305         }
306         try
307         {
308             setCachedFacesContext(context);
309             pushComponentToEL(context, this);
310             if (!isRendered())
311             {
312                 return;
313             }
314         }
315         finally
316         {
317             setCachedFacesContext(null);
318             popComponentFromEL(context);
319         }
320         super.processUpdates(context);
321 
322         try
323         {
324             setCachedFacesContext(context);
325             pushComponentToEL(context, this);
326             try
327             {
328                 updateModel(context);
329             }
330             catch (RuntimeException e)
331             {
332                 context.renderResponse();
333                 throw e;
334             }
335             if (!isValid())
336             {
337                 context.renderResponse();
338             }
339         }
340         finally
341         {
342             setCachedFacesContext(null);
343             popComponentFromEL(context);
344         }
345     }
346 
347     @Override
348     public void decode(FacesContext context)
349     {
350         // We (re)set to valid, so that component automatically gets (re)validated
351         setValid(true);
352         super.decode(context);
353     }
354 
355     @Override
356     public void broadcast(FacesEvent event) throws AbortProcessingException
357     {
358         // invoke standard listeners attached to this component first
359         super.broadcast(event);
360 
361         // Check if the event is applicable for ValueChangeListener
362         if (event instanceof ValueChangeEvent)
363         {
364             // invoke the single listener defined directly on the component
365             MethodBinding valueChangeListenerBinding = getValueChangeListener();
366             if (valueChangeListenerBinding != null)
367             {
368                 try
369                 {
370                     valueChangeListenerBinding.invoke(getFacesContext(), new Object[] { event });
371                 }
372                 catch (EvaluationException e)
373                 {
374                     Throwable cause = e.getCause();
375                     if (cause != null && cause instanceof AbortProcessingException)
376                     {
377                         throw (AbortProcessingException) cause;
378                     }
379                     else
380                     {
381                         throw e;
382                     }
383                 }
384             }
385         }
386     }
387 
388     public void updateModel(FacesContext context)
389     {
390         if (!isValid())
391         {
392             return;
393         }
394         if (!isLocalValueSet())
395         {
396             return;
397         }
398         ValueExpression expression = getValueExpression("value");
399         if (expression == null)
400         {
401             return;
402         }
403 
404         try
405         {
406             expression.setValue(context.getELContext(), getLocalValue());
407             setValue(null);
408             setLocalValueSet(false);
409         }
410         catch (Exception e)
411         {
412             // Enqueue an error message
413             //context.getExternalContext().log(e.getMessage(), e);
414             
415             // Create a FacesMessage with the id UPDATE_MESSAGE_ID
416             FacesMessage facesMessage = _MessageUtils.getMessage(context,
417                     context.getViewRoot().getLocale(), FacesMessage.SEVERITY_ERROR, UPDATE_MESSAGE_ID,
418                     new Object[] { _MessageUtils.getLabel(context, this) });
419             
420             // create an UpdateModelException and enqueue it since 
421             // we are not allowed to throw it directly here
422             // spec javadoc: The exception must not be re-thrown. This enables tree traversal to 
423             // continue for this lifecycle phase, as in all the other lifecycle phases.
424             UpdateModelException updateModelException = new UpdateModelException(facesMessage, e);
425             ExceptionQueuedEventContext exceptionQueuedContext 
426                     = new ExceptionQueuedEventContext(context, updateModelException, this, PhaseId.UPDATE_MODEL_VALUES);
427             
428             // spec javadoc says we should call context.getExceptionHandler().processEvent(exceptionQueuedContext),
429             // which is not just syntactically wrong, but also stupid!!
430             context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, exceptionQueuedContext);
431             
432             // Set the valid property of this UIInput to false
433             setValid(false);
434         }
435     }
436 
437     protected void validateValue(FacesContext context, Object convertedValue)
438     {
439         if (!isValid())
440         {
441             return;
442         }
443 
444         // If our value is empty, check the required property
445         boolean isEmpty = isEmpty(convertedValue); 
446 
447         if (isRequired() && isEmpty)
448         {
449             if (getRequiredMessage() != null)
450             {
451                 String requiredMessage = getRequiredMessage();
452                 context.addMessage(this.getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR,
453                     requiredMessage, requiredMessage));
454             }
455             else
456             {
457                 _MessageUtils.addErrorMessage(context, this, REQUIRED_MESSAGE_ID,
458                     new Object[] { _MessageUtils.getLabel(context, this) });
459             }
460             setValid(false);
461             return;
462         }
463 
464         if (!isEmpty || shouldValidateEmptyFields(context))
465         {
466             _ComponentUtils.callValidators(context, this, convertedValue);
467         }
468     }
469     
470     /**
471      * Checks if the <code>validate()</code> should interpret an empty
472      * submitted value should be handle as <code>NULL</code>
473      * 
474      * @return a (cached) boolean to identify the interpretation as null
475      */
476     private boolean shouldInterpretEmptyStringSubmittedValuesAsNull(FacesContext context)
477     {
478         ExternalContext ec = context.getExternalContext();
479         Boolean interpretEmptyStringAsNull
480                 = (Boolean)ec.getApplicationMap().get(MYFACES_EMPTY_VALUES_AS_NULL_PARAM_NAME);
481 
482         // not yet cached...
483         if (interpretEmptyStringAsNull == null)
484         {
485             // parses the web.xml to get the "javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL" value
486             String param = ec.getInitParameter(EMPTY_VALUES_AS_NULL_PARAM_NAME);
487 
488             // evaluate the param
489             interpretEmptyStringAsNull = "true".equalsIgnoreCase(param);
490 
491             // cache the parsed value
492             ec.getApplicationMap().put(MYFACES_EMPTY_VALUES_AS_NULL_PARAM_NAME, interpretEmptyStringAsNull);
493         }
494 
495         return interpretEmptyStringAsNull;
496     }
497 
498     /**
499      * <p>Return <code>true</code> if the value is an empty <code>String</code>.</p>
500      */
501     private boolean isEmptyString(Object value)
502     {
503         return ((value instanceof String) && (((String) value).length() == 0));
504     }
505 
506 
507     private boolean shouldValidateEmptyFields(FacesContext context)
508     {
509         ExternalContext ec = context.getExternalContext();
510         Boolean validateEmptyFields = (Boolean) ec.getApplicationMap().get(VALIDATE_EMPTY_FIELDS_PARAM_NAME);
511 
512         if (validateEmptyFields == null)
513         {
514              String param = ec.getInitParameter(VALIDATE_EMPTY_FIELDS_PARAM_NAME);
515 
516              // null means the same as auto.
517              if (param == null)
518              {
519                  param = "auto";
520              }
521              else
522              {
523                  // The environment variables are case insensitive.
524                  param = param.toLowerCase();
525              }
526 
527              if (param.equals("auto") && _ExternalSpecifications.isBeanValidationAvailable())
528              {
529                  validateEmptyFields = true;
530              }
531              else if (param.equals("true"))
532              {
533                  validateEmptyFields = true;
534              }
535              else
536              {
537                  validateEmptyFields = false;
538              }
539 
540              // cache the parsed value
541              ec.getApplicationMap().put(VALIDATE_EMPTY_FIELDS_PARAM_NAME, validateEmptyFields);
542         }
543 
544         return validateEmptyFields;
545     }
546 
547     /**
548      * Determine whether the new value is valid, and queue a ValueChangeEvent if necessary.
549      * <p>
550      * The "submitted value" is converted to the necessary type; conversion failure is reported as an error and
551      * validation processing terminates for this component. See documentation for method getConvertedValue for details
552      * on the conversion process.
553      * <p>
554      * Any validators attached to this component are then run, passing the converted value.
555      * <p>
556      * The old value of this component is then fetched (possibly involving the evaluation of a value-binding expression,
557      * ie invoking a method on a user object). The old value is compared to the new validated value, and if they are
558      * different then a ValueChangeEvent is queued for later processing.
559      * <p>
560      * On successful completion of this method:
561      * <ul>
562      * <li>isValid() is true
563      * <li>isLocalValueSet() is true
564      * <li>submittedValue is reset to null
565      * <li>a ValueChangeEvent is queued if the new value != old value
566      * </ul>
567      */
568     public void validate(FacesContext context)
569     {
570         if (context == null)
571         {
572             throw new NullPointerException("context");
573         }
574 
575         Object submittedValue = getSubmittedValue();
576         if (submittedValue == null)
577         {
578             return;
579         }
580 
581         // Begin new JSF 2.0 requirement (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL)
582         if (shouldInterpretEmptyStringSubmittedValuesAsNull(context) && isEmptyString(submittedValue))
583         {   
584             // -= matzew = setSubmittedValue(null) is wrong, see:
585             // https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=671
586             setSubmittedValue(null);
587             submittedValue = null;
588         }
589         // End new JSF 2.0 requirement (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL)
590 
591         Object convertedValue;
592         try
593         {
594             convertedValue = getConvertedValue(context, submittedValue);
595         }
596         catch (ConverterException e)
597         {
598             String converterMessage = getConverterMessage();
599             if (converterMessage != null)
600             {
601                 context.addMessage(getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR,
602                         converterMessage, converterMessage));
603             }
604             else
605             {
606                 FacesMessage facesMessage = e.getFacesMessage();
607                 if (facesMessage != null)
608                 {
609                     context.addMessage(getClientId(context), facesMessage);
610                 }
611                 else
612                 {
613                     _MessageUtils.addErrorMessage(context, this, CONVERSION_MESSAGE_ID,
614                             new Object[] { _MessageUtils.getLabel(context, this) });
615                 }
616             }
617             setValid(false);
618             return;
619         }
620 
621         validateValue(context, convertedValue);
622 
623         if (!isValid())
624         {
625             return;
626         }
627 
628         Object previousValue = getValue();
629         setValue(convertedValue);
630         setSubmittedValue(null);
631         if (compareValues(previousValue, convertedValue))
632         {
633             queueEvent(new ValueChangeEvent(this, previousValue, convertedValue));
634         }
635     }
636 
637     /**
638      * Convert the provided object to the desired value.
639      * <p>
640      * If there is a renderer for this component, then call the renderer's getConvertedValue method. While this can of
641      * course be implemented in any way the renderer desires, it typically performs exactly the same processing that
642      * this method would have done anyway (ie that described below for the no-renderer case).
643      * <p>
644      * Otherwise:
645      * <ul>
646      * <li>If the submittedValue is not a String then just return the submittedValue unconverted.
647      * <li>If there is no "value" value-binding then just return the submittedValue unconverted.
648      * <li>Use introspection to determine the type of the target property specified by the value-binding, and then use
649      * Application.createConverter to find a converter that can map from String to the required type. Apply the
650      * converter to the submittedValue and return the result.
651      * </ul>
652      */
653     protected Object getConvertedValue(FacesContext context, Object submittedValue) throws ConverterException
654     {
655         Renderer renderer = getRenderer(context);
656         if (renderer != null)
657         {
658             return renderer.getConvertedValue(context, this, submittedValue);
659         }
660         else if (submittedValue instanceof String)
661         {
662             Converter converter = _SharedRendererUtils.findUIOutputConverter(context, this);
663             if (converter != null)
664             {
665                 return converter.getAsObject(context, this, (String) submittedValue);
666             }
667         }
668         return submittedValue;
669     }
670 
671     protected boolean compareValues(Object previous, Object value)
672     {
673         return previous == null ? (value != null) : (!previous.equals(value));
674     }
675 
676     /**
677      * @since 1.2
678      */
679     public void resetValue()
680     {
681         setSubmittedValue(null);
682         setValue(null);
683         setLocalValueSet(false);
684         setValid(true);
685     }
686     
687     /**
688      * A boolean value that identifies the phase during which action events should fire.
689      * <p>
690      * During normal event processing, action methods and action listener methods are fired during the
691      * "invoke application" phase of request processing. If this attribute is set to "true", these methods are fired
692      * instead at the end of the "apply request values" phase.
693      * </p>
694      */
695     @JSFProperty
696     public boolean isImmediate()
697     {
698         return (Boolean) getStateHelper().eval(PropertyKeys.immediate, Boolean.FALSE);
699     }
700 
701     public void setImmediate(boolean immediate)
702     {
703         getStateHelper().put(PropertyKeys.immediate, immediate );
704     }
705 
706     /**
707      * A boolean value that indicates whether an input value is required.
708      * <p>
709      * If this value is true and no input value is provided by a postback operation, then the "requiredMessage" text is
710      * registered as a FacesMessage for the request, and validation fails.
711      * </p>
712      * <p>
713      * Default value: false.
714      * </p>
715      */
716     @JSFProperty(defaultValue = "false")
717     public boolean isRequired()
718     {
719         return (Boolean) getStateHelper().eval(PropertyKeys.required, Boolean.FALSE);
720     }
721 
722     public void setRequired(boolean required)
723     {
724         getStateHelper().put(PropertyKeys.required, required ); 
725     }
726 
727     /**
728      * Text to be displayed to the user as an error message when conversion of a submitted value to the target type
729      * fails.
730      * <p>
731      * </p>
732      */
733     @JSFProperty
734     public String getConverterMessage()
735     {
736         return (String) getStateHelper().eval(PropertyKeys.converterMessage);
737     }
738 
739     public void setConverterMessage(String converterMessage)
740     {
741         getStateHelper().put(PropertyKeys.converterMessage, converterMessage );
742     }
743 
744     /**
745      * Text to be displayed to the user as an error message when this component is marked as "required" but no input
746      * data is present during a postback (ie the user left the required field blank).
747      */
748     @JSFProperty
749     public String getRequiredMessage()
750     {
751         return (String) getStateHelper().eval(PropertyKeys.requiredMessage);
752     }
753 
754     public void setRequiredMessage(String requiredMessage)
755     {
756         getStateHelper().put(PropertyKeys.requiredMessage, requiredMessage );
757     }
758 
759     /**
760      * A method-binding EL expression which is invoked during the validation phase for this component.
761      * <p>
762      * The invoked method is expected to check the submitted value for this component, and if not acceptable then report
763      * a validation error for the component.
764      * </p>
765      * <p>
766      * The method is expected to have the prototype
767      * </p>
768      * <code>public void aMethod(FacesContext, UIComponent,Object)</code>
769      * 
770      * @deprecated
771      */
772     @SuppressWarnings("dep-ann")
773     @JSFProperty(stateHolder=true, returnSignature = "void",
774             methodSignature = "javax.faces.context.FacesContext,javax.faces.component.UIComponent,java.lang.Object")
775     public MethodBinding getValidator()
776     {
777         return (MethodBinding) getStateHelper().eval(PropertyKeys.validator);
778     }
779 
780     /** See getValidator.
781      *  
782      * @deprecated 
783      */
784     public void setValidator(MethodBinding validator)
785     {
786         getStateHelper().put(PropertyKeys.validator, validator);
787     }
788 
789     /** See getValidator. */
790     public void addValidator(Validator validator)
791     {
792         if (validator == null)
793         {
794             throw new NullPointerException("validator");
795         }
796         
797         if (_validatorList == null)
798         {
799             //normally add user 0-3 validators: 
800             _validatorList = new _DeltaList<Validator>(new ArrayList<Validator>(3));
801         }
802 
803         _validatorList.add(validator);
804         
805         // The argument validator must be inspected for the presence of the ResourceDependency annotation.
806         //_handleAnnotations(FacesContext.getCurrentInstance(), validator);
807     }
808 
809     /** See getValidator. */
810     public void removeValidator(Validator validator)
811     {
812         if (validator == null || _validatorList == null)
813         {
814             return;
815         }
816 
817         _validatorList.remove(validator);
818     }
819 
820     /** See getValidator. */
821     public Validator[] getValidators()
822     {
823         return _validatorList == null ? EMPTY_VALIDATOR_ARRAY
824                 : _validatorList.toArray(new Validator[_validatorList.size()]);
825     }
826 
827     /**
828      * Text which will be shown if validation fails.
829      */
830     @JSFProperty
831     public String getValidatorMessage()
832     {
833         return (String) getStateHelper().eval(PropertyKeys.validatorMessage);
834     }
835 
836     public void setValidatorMessage(String validatorMessage)
837     {
838         getStateHelper().put(PropertyKeys.validatorMessage, validatorMessage );
839     }
840 
841     /**
842      * A method which is invoked during postback processing for the current view if the submitted value for this
843      * component is not equal to the value which the "value" expression for this component returns.
844      * <p>
845      * The phase in which this method is invoked can be controlled via the immediate attribute.
846      * </p>
847      * 
848      * @deprecated
849      */
850     @JSFProperty(stateHolder=true, returnSignature = "void",
851                  methodSignature = "javax.faces.event.ValueChangeEvent", clientEvent="valueChange")
852     public MethodBinding getValueChangeListener()
853     {
854         return (MethodBinding) getStateHelper().eval(PropertyKeys.valueChangeListener);
855     }
856 
857     /**
858      * See getValueChangeListener.
859      * 
860      * @deprecated
861      */
862     public void setValueChangeListener(MethodBinding valueChangeListener)
863     {
864         getStateHelper().put(PropertyKeys.valueChangeListener, valueChangeListener);
865     }
866 
867     /**
868      * Specifies whether the component's value is currently valid, ie whether the validators attached to this component
869      * have allowed it.
870      */
871     @JSFProperty(defaultValue = "true", tagExcluded = true)
872     public boolean isValid()
873     {
874         Object value = getStateHelper().get(PropertyKeys.valid);
875         if (value != null)
876         {
877             return (Boolean) value;        
878         }
879         return true; 
880     }
881 
882     public void setValid(boolean valid)
883     {
884         // default value for valid is true, so if the intention is to save the default
885         // value when nothing else was set before, don't do it. This is done in order to
886         // reduce the size of the saved state of the state helper. Default values won't be
887         // included in the saved state. 
888         if (getStateHelper().get(PropertyKeys.valid) != null || !valid)
889         {
890             getStateHelper().put(PropertyKeys.valid, valid );
891         }
892     }
893 
894     /**
895      * Specifies whether a local value is currently set.
896      * <p>
897      * If false, values are being retrieved from any attached ValueBinding.
898      */
899     @JSFProperty(defaultValue = "false", tagExcluded = true)
900     public boolean isLocalValueSet()
901     {
902         Object value = getStateHelper().get(PropertyKeys.localValueSet);
903         if (value != null)
904         {
905             return (Boolean) value;        
906         }
907         return false;
908     }
909 
910     public void setLocalValueSet(boolean localValueSet)
911     {
912         // default value for localValueSet is false, so if the intention is to save the default
913         // value when nothing else was set before, don't do it. This is done in order to
914         // reduce the size of the saved state of the state helper. Default values won't be
915         // included in the saved state.
916         if (getStateHelper().get(PropertyKeys.localValueSet) != null || localValueSet)
917         {
918             getStateHelper().put(PropertyKeys.localValueSet, localValueSet );
919         }
920     }
921 
922     /**
923      * Gets the current submitted value. This value, if non-null, is set by the Renderer to store a possibly invalid
924      * value for later conversion or redisplay, and has not yet been converted into the proper type for this component
925      * instance. This method should only be used by the decode() and validate() method of this component, or its
926      * corresponding Renderer; however, user code may manually set it to null to erase any submitted value.
927      */
928     @JSFProperty(tagExcluded = true)
929     public Object getSubmittedValue()
930     {
931         return  getStateHelper().get(PropertyKeys.submittedValue);
932     }
933 
934     public void setSubmittedValue(Object submittedValue)
935     {
936         FacesContext facesContext = getFacesContext();
937         if (facesContext != null && facesContext.isProjectStage(ProjectStage.Development))
938         {
939             // extended debug-info when in Development mode
940             _createFieldDebugInfo(facesContext, "submittedValue",
941                     getSubmittedValue(), submittedValue, 1);
942         }
943         getStateHelper().put(PropertyKeys.submittedValue, submittedValue );
944     }
945 
946     public void addValueChangeListener(ValueChangeListener listener)
947     {
948         addFacesListener(listener);
949     }
950 
951     public void removeValueChangeListener(ValueChangeListener listener)
952     {
953         removeFacesListener(listener);
954     }
955 
956     /**
957      * The valueChange event is delivered when the value attribute
958      * is changed.
959      */
960     @JSFListener(event="javax.faces.event.ValueChangeEvent")
961     public ValueChangeListener[] getValueChangeListeners()
962     {
963         return (ValueChangeListener[]) getFacesListeners(ValueChangeListener.class);
964     }
965 
966     enum PropertyKeys
967     {
968          immediate
969         , required
970         , converterMessage
971         , requiredMessage
972         , validator
973         , validatorListSet
974         , validatorMessage
975         , valueChangeListener
976         , valid
977         , localValueSet
978         , submittedValue
979     }
980     
981     private static final Object[] INITIAL_STATE_PROPERTIES = new
982             Object[]{
983                 UIOutput.PropertyKeys.value,
984                 null,
985                 UIInput.PropertyKeys.localValueSet,
986                 false,
987                 UIInput.PropertyKeys.submittedValue,
988                 null,
989                 UIInput.PropertyKeys.valid,
990                 true
991             };
992     
993     public void markInitialState()
994     {
995         StateHelper helper = getStateHelper(false);
996         if (helper != null && helper instanceof _DeltaStateHelper)
997         {
998             ((_DeltaStateHelper)helper).markPropertyInInitialState(INITIAL_STATE_PROPERTIES);
999         }
1000         super.markInitialState();
1001         if (_validatorList != null)
1002         {
1003             _validatorList.markInitialState();
1004         }
1005     }
1006     
1007     public void clearInitialState()
1008     {
1009         if (initialStateMarked())
1010         {
1011             super.clearInitialState();
1012             if (_validatorList != null)
1013             {
1014                 _validatorList.clearInitialState();
1015             }
1016         }
1017     }    
1018 
1019     @Override
1020     public Object saveState(FacesContext facesContext)
1021     {
1022         if (initialStateMarked())
1023         {
1024             Object parentSaved = super.saveState(facesContext);
1025             Object validatorListSaved = saveValidatorList(facesContext);
1026             if (parentSaved == null && validatorListSaved == null)
1027             {
1028                 //No values
1029                 return null;
1030             }
1031             
1032             Object[] values = new Object[2];
1033             values[0] = parentSaved;
1034             values[1] = validatorListSaved;
1035             return values;
1036         }
1037         else
1038         {
1039             Object[] values = new Object[2];
1040             values[0] = super.saveState(facesContext);
1041             values[1] = saveValidatorList(facesContext);
1042             return values;
1043         }
1044     }
1045 
1046     @SuppressWarnings("unchecked")
1047     @Override
1048     public void restoreState(FacesContext facesContext, Object state)
1049     {
1050         if (state == null)
1051         {
1052             return;
1053         }
1054         
1055         Object[] values = (Object[])state;
1056         super.restoreState(facesContext,values[0]);
1057         if (values[1] instanceof _AttachedDeltaWrapper)
1058         {
1059             //Delta
1060             if (_validatorList != null)
1061             {
1062                 ((StateHolder)_validatorList).restoreState(facesContext,
1063                         ((_AttachedDeltaWrapper) values[1]).getWrappedStateObject());
1064             }
1065         }
1066         else if (values[1] != null || !initialStateMarked())
1067         {
1068             //Full
1069             _validatorList = (_DeltaList<Validator>)
1070                 restoreAttachedState(facesContext,values[1]);
1071         }
1072     }
1073     
1074     private Object saveValidatorList(FacesContext facesContext)
1075     {
1076         PartialStateHolder holder = (PartialStateHolder) _validatorList;
1077         if (initialStateMarked() && _validatorList != null && holder.initialStateMarked())
1078         {                
1079             Object attachedState = holder.saveState(facesContext);
1080             if (attachedState != null)
1081             {
1082                 return new _AttachedDeltaWrapper(_validatorList.getClass(),
1083                         attachedState);
1084             }
1085             //_validatorList instances once is created never changes, we can return null
1086             return null;
1087         }
1088         else
1089         {
1090             return saveAttachedState(facesContext,_validatorList);
1091         }            
1092     }
1093     
1094     /**
1095      * Returns the debug-info Map for this component.
1096      * @return
1097      */
1098     @SuppressWarnings("unchecked")
1099     private Map<String, List<Object[]>> _getDebugInfoMap()
1100     {
1101         Map<String, Object> requestMap = getFacesContext()
1102                 .getExternalContext().getRequestMap();
1103         Map<String, List<Object[]>> debugInfo = (Map<String, List<Object[]>>) 
1104                 requestMap.get(DEBUG_INFO_KEY + getClientId());
1105         if (debugInfo == null)
1106         {
1107             // no debug info available yet, create one and put it on the attributes map
1108             debugInfo = new HashMap<String, List<Object[]>>();
1109             requestMap.put(DEBUG_INFO_KEY + getClientId(), debugInfo);
1110         }
1111         return debugInfo;
1112     }
1113     
1114     /**
1115      * Returns the field's debug-infos from the component's debug-info Map.
1116      * @param field
1117      * @return
1118      */
1119     private List<Object[]> _getFieldDebugInfos(final String field)
1120     {
1121         Map<String, List<Object[]>> debugInfo = _getDebugInfoMap();
1122         List<Object[]> fieldDebugInfo = debugInfo.get(field);
1123         if (fieldDebugInfo == null)
1124         {
1125             // no field debug-infos yet, create them and store it in the Map
1126             fieldDebugInfo = new ArrayList<Object[]>();
1127             debugInfo.put(field, fieldDebugInfo);
1128         }
1129         return fieldDebugInfo;
1130     }
1131     
1132     /**
1133      * Creates the field debug-info for the given field, which changed
1134      * from oldValue to newValue.
1135      * 
1136      * @param facesContext
1137      * @param field
1138      * @param oldValue
1139      * @param newValue
1140      * @param skipStackTaceElements How many StackTraceElements should be skipped
1141      *                              when the calling function will be determined.
1142      */
1143     private void _createFieldDebugInfo(FacesContext facesContext,
1144             final String field, Object oldValue, 
1145             Object newValue, final int skipStackTaceElements)
1146     {
1147         if (oldValue == null && newValue == null)
1148         {
1149             // both values are null, not interesting and can
1150             // happen a lot in UIData with saving and restoring state
1151             return;
1152         }
1153         
1154         if (facesContext.getViewRoot() == null)
1155         {
1156             // No viewRoot set, it is creating component, 
1157             // so it is not possible to calculate the clientId, 
1158             // abort processing because the interesting part will
1159             // happen later.
1160             return;
1161         }
1162         
1163         if (getParent() == null || !isInView())
1164         {
1165             //Skip if no parent or is not in view
1166             return;
1167         }
1168         
1169         // convert Array values into a more readable format
1170         if (oldValue != null && oldValue.getClass().isArray() && Object[].class.isAssignableFrom(oldValue.getClass()))
1171         {
1172             oldValue = Arrays.deepToString((Object[]) oldValue);
1173         }
1174         if (newValue != null && newValue.getClass().isArray() && Object[].class.isAssignableFrom(newValue.getClass()))
1175         {
1176             newValue = Arrays.deepToString((Object[]) newValue);
1177         }
1178         
1179         // use Throwable to get the current call stack
1180         Throwable throwableHelper = new Throwable();
1181         StackTraceElement[] stackTraceElements = throwableHelper.getStackTrace();
1182         List<StackTraceElement> debugStackTraceElements = new LinkedList<StackTraceElement>();
1183         
1184         // + 1 because this method should also be skipped
1185         for (int i = skipStackTaceElements + 1; i < stackTraceElements.length; i++)
1186         {
1187             debugStackTraceElements.add(stackTraceElements[i]);
1188             
1189             if (FacesServlet.class.getCanonicalName()
1190                     .equals(stackTraceElements[i].getClassName()))
1191             {
1192                 // stop after the FacesServlet
1193                 break;
1194             }
1195         }
1196         
1197         // create the debug-info array
1198         // structure:
1199         //     - 0: phase
1200         //     - 1: old value
1201         //     - 2: new value
1202         //     - 3: StackTraceElement List
1203         // NOTE that we cannot create a class here to encapsulate this data,
1204         // because this is not on the spec and the class would not be available in impl.
1205         Object[] debugInfo = new Object[4];
1206         debugInfo[0] = facesContext.getCurrentPhaseId();
1207         debugInfo[1] = oldValue;
1208         debugInfo[2] = newValue;
1209         debugInfo[3] = debugStackTraceElements;
1210         
1211         // add the debug info
1212         _getFieldDebugInfos(field).add(debugInfo);
1213     }
1214     
1215     /**
1216      * Check if a value is empty or not. Since we don't know the class of
1217      * value we have to check and deal with it properly.
1218      * 
1219      * @since 2.0
1220      * @param value
1221      * @return
1222      */
1223     public static boolean isEmpty(Object value)
1224     {
1225         if (value == null)
1226         {
1227             return true;
1228         }
1229         else if (value instanceof String)
1230         {
1231             if ( ((String)value).trim().length() <= 0 )
1232             {
1233                 return true;
1234             }
1235         }
1236         else if (value instanceof Collection)
1237         {
1238             if ( ((Collection)value).isEmpty())
1239             {
1240                 return true;
1241             }
1242         }
1243         else if (value.getClass().isArray())
1244         {
1245             if (java.lang.reflect.Array.getLength(value) <= 0)
1246             {
1247                 return true;
1248             }
1249         }
1250         else if (value instanceof Map)
1251         {
1252             if ( ((Map)value).isEmpty())
1253             {
1254                 return true;
1255             }
1256         }
1257         return false;
1258     }
1259 
1260 }