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     private boolean shouldValidateEmptyFields(FacesContext context)
507     {
508         ExternalContext extCtx = context.getExternalContext();
509         String validateEmptyFields = (String) extCtx.getInitParameter(VALIDATE_EMPTY_FIELDS_PARAM_NAME);
510         if (validateEmptyFields == null)
511         {
512             validateEmptyFields = (String) extCtx.getApplicationMap().get(VALIDATE_EMPTY_FIELDS_PARAM_NAME);
513         }
514 
515         // null means the same as auto.
516         if (validateEmptyFields == null)
517         {
518             validateEmptyFields = "auto";
519         }
520         else
521         {
522             // The environment variables are case insensitive.
523             validateEmptyFields = validateEmptyFields.toLowerCase();
524         }
525 
526         if (validateEmptyFields.equals("auto") && _ExternalSpecifications.isBeanValidationAvailable())
527         {
528             return true;
529         }
530         else if (validateEmptyFields.equals("true"))
531         {
532             return true;
533         }
534         return false;
535     }
536 
537     /**
538      * Determine whether the new value is valid, and queue a ValueChangeEvent if necessary.
539      * <p>
540      * The "submitted value" is converted to the necessary type; conversion failure is reported as an error and
541      * validation processing terminates for this component. See documentation for method getConvertedValue for details
542      * on the conversion process.
543      * <p>
544      * Any validators attached to this component are then run, passing the converted value.
545      * <p>
546      * The old value of this component is then fetched (possibly involving the evaluation of a value-binding expression,
547      * ie invoking a method on a user object). The old value is compared to the new validated value, and if they are
548      * different then a ValueChangeEvent is queued for later processing.
549      * <p>
550      * On successful completion of this method:
551      * <ul>
552      * <li>isValid() is true
553      * <li>isLocalValueSet() is true
554      * <li>submittedValue is reset to null
555      * <li>a ValueChangeEvent is queued if the new value != old value
556      * </ul>
557      */
558     public void validate(FacesContext context)
559     {
560         if (context == null)
561         {
562             throw new NullPointerException("context");
563         }
564 
565         Object submittedValue = getSubmittedValue();
566         if (submittedValue == null)
567         {
568             return;
569         }
570 
571         // Begin new JSF 2.0 requirement (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL)
572         if (shouldInterpretEmptyStringSubmittedValuesAsNull(context) && isEmptyString(submittedValue))
573         {   
574             // -= matzew = setSubmittedValue(null) is wrong, see:
575             // https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=671
576             setSubmittedValue(null);
577             submittedValue = null;
578         }
579         // End new JSF 2.0 requirement (INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL)
580 
581         Object convertedValue;
582         try
583         {
584             convertedValue = getConvertedValue(context, submittedValue);
585         }
586         catch (ConverterException e)
587         {
588             String converterMessage = getConverterMessage();
589             if (converterMessage != null)
590             {
591                 context.addMessage(getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR,
592                         converterMessage, converterMessage));
593             }
594             else
595             {
596                 FacesMessage facesMessage = e.getFacesMessage();
597                 if (facesMessage != null)
598                 {
599                     context.addMessage(getClientId(context), facesMessage);
600                 }
601                 else
602                 {
603                     _MessageUtils.addErrorMessage(context, this, CONVERSION_MESSAGE_ID,
604                             new Object[] { _MessageUtils.getLabel(context, this) });
605                 }
606             }
607             setValid(false);
608             return;
609         }
610 
611         validateValue(context, convertedValue);
612 
613         if (!isValid())
614         {
615             return;
616         }
617 
618         Object previousValue = getValue();
619         setValue(convertedValue);
620         setSubmittedValue(null);
621         if (compareValues(previousValue, convertedValue))
622         {
623             queueEvent(new ValueChangeEvent(this, previousValue, convertedValue));
624         }
625     }
626 
627     /**
628      * Convert the provided object to the desired value.
629      * <p>
630      * If there is a renderer for this component, then call the renderer's getConvertedValue method. While this can of
631      * course be implemented in any way the renderer desires, it typically performs exactly the same processing that
632      * this method would have done anyway (ie that described below for the no-renderer case).
633      * <p>
634      * Otherwise:
635      * <ul>
636      * <li>If the submittedValue is not a String then just return the submittedValue unconverted.
637      * <li>If there is no "value" value-binding then just return the submittedValue unconverted.
638      * <li>Use introspection to determine the type of the target property specified by the value-binding, and then use
639      * Application.createConverter to find a converter that can map from String to the required type. Apply the
640      * converter to the submittedValue and return the result.
641      * </ul>
642      */
643     protected Object getConvertedValue(FacesContext context, Object submittedValue) throws ConverterException
644     {
645         Renderer renderer = getRenderer(context);
646         if (renderer != null)
647         {
648             return renderer.getConvertedValue(context, this, submittedValue);
649         }
650         else if (submittedValue instanceof String)
651         {
652             Converter converter = _SharedRendererUtils.findUIOutputConverter(context, this);
653             if (converter != null)
654             {
655                 return converter.getAsObject(context, this, (String) submittedValue);
656             }
657         }
658         return submittedValue;
659     }
660 
661     protected boolean compareValues(Object previous, Object value)
662     {
663         return previous == null ? (value != null) : (!previous.equals(value));
664     }
665 
666     /**
667      * @since 1.2
668      */
669     public void resetValue()
670     {
671         setSubmittedValue(null);
672         setValue(null);
673         setLocalValueSet(false);
674         setValid(true);
675     }
676     
677     /**
678      * A boolean value that identifies the phase during which action events should fire.
679      * <p>
680      * During normal event processing, action methods and action listener methods are fired during the
681      * "invoke application" phase of request processing. If this attribute is set to "true", these methods are fired
682      * instead at the end of the "apply request values" phase.
683      * </p>
684      */
685     @JSFProperty
686     public boolean isImmediate()
687     {
688         return (Boolean) getStateHelper().eval(PropertyKeys.immediate, Boolean.FALSE);
689     }
690 
691     public void setImmediate(boolean immediate)
692     {
693         getStateHelper().put(PropertyKeys.immediate, immediate );
694     }
695 
696     /**
697      * A boolean value that indicates whether an input value is required.
698      * <p>
699      * If this value is true and no input value is provided by a postback operation, then the "requiredMessage" text is
700      * registered as a FacesMessage for the request, and validation fails.
701      * </p>
702      * <p>
703      * Default value: false.
704      * </p>
705      */
706     @JSFProperty(defaultValue = "false")
707     public boolean isRequired()
708     {
709         return (Boolean) getStateHelper().eval(PropertyKeys.required, Boolean.FALSE);
710     }
711 
712     public void setRequired(boolean required)
713     {
714         getStateHelper().put(PropertyKeys.required, required ); 
715     }
716 
717     /**
718      * Text to be displayed to the user as an error message when conversion of a submitted value to the target type
719      * fails.
720      * <p>
721      * </p>
722      */
723     @JSFProperty
724     public String getConverterMessage()
725     {
726         return (String) getStateHelper().eval(PropertyKeys.converterMessage);
727     }
728 
729     public void setConverterMessage(String converterMessage)
730     {
731         getStateHelper().put(PropertyKeys.converterMessage, converterMessage );
732     }
733 
734     /**
735      * Text to be displayed to the user as an error message when this component is marked as "required" but no input
736      * data is present during a postback (ie the user left the required field blank).
737      */
738     @JSFProperty
739     public String getRequiredMessage()
740     {
741         return (String) getStateHelper().eval(PropertyKeys.requiredMessage);
742     }
743 
744     public void setRequiredMessage(String requiredMessage)
745     {
746         getStateHelper().put(PropertyKeys.requiredMessage, requiredMessage );
747     }
748 
749     /**
750      * A method-binding EL expression which is invoked during the validation phase for this component.
751      * <p>
752      * The invoked method is expected to check the submitted value for this component, and if not acceptable then report
753      * a validation error for the component.
754      * </p>
755      * <p>
756      * The method is expected to have the prototype
757      * </p>
758      * <code>public void aMethod(FacesContext, UIComponent,Object)</code>
759      * 
760      * @deprecated
761      */
762     @SuppressWarnings("dep-ann")
763     @JSFProperty(stateHolder=true, returnSignature = "void",
764             methodSignature = "javax.faces.context.FacesContext,javax.faces.component.UIComponent,java.lang.Object")
765     public MethodBinding getValidator()
766     {
767         return (MethodBinding) getStateHelper().eval(PropertyKeys.validator);
768     }
769 
770     /** See getValidator.
771      *  
772      * @deprecated 
773      */
774     public void setValidator(MethodBinding validator)
775     {
776         getStateHelper().put(PropertyKeys.validator, validator);
777     }
778 
779     /** See getValidator. */
780     public void addValidator(Validator validator)
781     {
782         if (validator == null)
783         {
784             throw new NullPointerException("validator");
785         }
786         
787         if (_validatorList == null)
788         {
789             //normally add user 0-3 validators: 
790             _validatorList = new _DeltaList<Validator>(new ArrayList<Validator>(3));
791         }
792 
793         _validatorList.add(validator);
794         
795         // The argument validator must be inspected for the presence of the ResourceDependency annotation.
796         //_handleAnnotations(FacesContext.getCurrentInstance(), validator);
797     }
798 
799     /** See getValidator. */
800     public void removeValidator(Validator validator)
801     {
802         if (validator == null || _validatorList == null)
803         {
804             return;
805         }
806 
807         _validatorList.remove(validator);
808     }
809 
810     /** See getValidator. */
811     public Validator[] getValidators()
812     {
813         return _validatorList == null ? EMPTY_VALIDATOR_ARRAY
814                 : _validatorList.toArray(new Validator[_validatorList.size()]);
815     }
816 
817     /**
818      * Text which will be shown if validation fails.
819      */
820     @JSFProperty
821     public String getValidatorMessage()
822     {
823         return (String) getStateHelper().eval(PropertyKeys.validatorMessage);
824     }
825 
826     public void setValidatorMessage(String validatorMessage)
827     {
828         getStateHelper().put(PropertyKeys.validatorMessage, validatorMessage );
829     }
830 
831     /**
832      * A method which is invoked during postback processing for the current view if the submitted value for this
833      * component is not equal to the value which the "value" expression for this component returns.
834      * <p>
835      * The phase in which this method is invoked can be controlled via the immediate attribute.
836      * </p>
837      * 
838      * @deprecated
839      */
840     @JSFProperty(stateHolder=true, returnSignature = "void",
841                  methodSignature = "javax.faces.event.ValueChangeEvent", clientEvent="valueChange")
842     public MethodBinding getValueChangeListener()
843     {
844         return (MethodBinding) getStateHelper().eval(PropertyKeys.valueChangeListener);
845     }
846 
847     /**
848      * See getValueChangeListener.
849      * 
850      * @deprecated
851      */
852     public void setValueChangeListener(MethodBinding valueChangeListener)
853     {
854         getStateHelper().put(PropertyKeys.valueChangeListener, valueChangeListener);
855     }
856 
857     /**
858      * Specifies whether the component's value is currently valid, ie whether the validators attached to this component
859      * have allowed it.
860      */
861     @JSFProperty(defaultValue = "true", tagExcluded = true)
862     public boolean isValid()
863     {
864         Object value = getStateHelper().get(PropertyKeys.valid);
865         if (value != null)
866         {
867             return (Boolean) value;        
868         }
869         return true; 
870     }
871 
872     public void setValid(boolean valid)
873     {
874         // default value for valid is true, so if the intention is to save the default
875         // value when nothing else was set before, don't do it. This is done in order to
876         // reduce the size of the saved state of the state helper. Default values won't be
877         // included in the saved state. 
878         if (getStateHelper().get(PropertyKeys.valid) != null || !valid)
879         {
880             getStateHelper().put(PropertyKeys.valid, valid );
881         }
882     }
883 
884     /**
885      * Specifies whether a local value is currently set.
886      * <p>
887      * If false, values are being retrieved from any attached ValueBinding.
888      */
889     @JSFProperty(defaultValue = "false", tagExcluded = true)
890     public boolean isLocalValueSet()
891     {
892         Object value = getStateHelper().get(PropertyKeys.localValueSet);
893         if (value != null)
894         {
895             return (Boolean) value;        
896         }
897         return false;
898     }
899 
900     public void setLocalValueSet(boolean localValueSet)
901     {
902         // default value for localValueSet is false, so if the intention is to save the default
903         // value when nothing else was set before, don't do it. This is done in order to
904         // reduce the size of the saved state of the state helper. Default values won't be
905         // included in the saved state.
906         if (getStateHelper().get(PropertyKeys.localValueSet) != null || localValueSet)
907         {
908             getStateHelper().put(PropertyKeys.localValueSet, localValueSet );
909         }
910     }
911 
912     /**
913      * Gets the current submitted value. This value, if non-null, is set by the Renderer to store a possibly invalid
914      * value for later conversion or redisplay, and has not yet been converted into the proper type for this component
915      * instance. This method should only be used by the decode() and validate() method of this component, or its
916      * corresponding Renderer; however, user code may manually set it to null to erase any submitted value.
917      */
918     @JSFProperty(tagExcluded = true)
919     public Object getSubmittedValue()
920     {
921         return  getStateHelper().get(PropertyKeys.submittedValue);
922     }
923 
924     public void setSubmittedValue(Object submittedValue)
925     {
926         FacesContext facesContext = getFacesContext();
927         if (facesContext != null && facesContext.isProjectStage(ProjectStage.Development))
928         {
929             // extended debug-info when in Development mode
930             _createFieldDebugInfo(facesContext, "submittedValue",
931                     getSubmittedValue(), submittedValue, 1);
932         }
933         getStateHelper().put(PropertyKeys.submittedValue, submittedValue );
934     }
935 
936     public void addValueChangeListener(ValueChangeListener listener)
937     {
938         addFacesListener(listener);
939     }
940 
941     public void removeValueChangeListener(ValueChangeListener listener)
942     {
943         removeFacesListener(listener);
944     }
945 
946     /**
947      * The valueChange event is delivered when the value attribute
948      * is changed.
949      */
950     @JSFListener(event="javax.faces.event.ValueChangeEvent")
951     public ValueChangeListener[] getValueChangeListeners()
952     {
953         return (ValueChangeListener[]) getFacesListeners(ValueChangeListener.class);
954     }
955 
956     enum PropertyKeys
957     {
958          immediate
959         , required
960         , converterMessage
961         , requiredMessage
962         , validator
963         , validatorListSet
964         , validatorMessage
965         , valueChangeListener
966         , valid
967         , localValueSet
968         , submittedValue
969     }
970     
971     public void markInitialState()
972     {
973         super.markInitialState();
974         if (_validatorList != null)
975         {
976             _validatorList.markInitialState();
977         }
978     }
979     
980     public void clearInitialState()
981     {
982         if (initialStateMarked())
983         {
984             super.clearInitialState();
985             if (_validatorList != null)
986             {
987                 _validatorList.clearInitialState();
988             }
989         }
990     }    
991 
992     @Override
993     public Object saveState(FacesContext facesContext)
994     {
995         if (initialStateMarked())
996         {
997             Object parentSaved = super.saveState(facesContext);
998             Object validatorListSaved = saveValidatorList(facesContext);
999             if (parentSaved == null && validatorListSaved == null)
1000             {
1001                 //No values
1002                 return null;
1003             }
1004             
1005             Object[] values = new Object[2];
1006             values[0] = parentSaved;
1007             values[1] = validatorListSaved;
1008             return values;
1009         }
1010         else
1011         {
1012             Object[] values = new Object[2];
1013             values[0] = super.saveState(facesContext);
1014             values[1] = saveValidatorList(facesContext);
1015             return values;
1016         }
1017     }
1018 
1019     @SuppressWarnings("unchecked")
1020     @Override
1021     public void restoreState(FacesContext facesContext, Object state)
1022     {
1023         if (state == null)
1024         {
1025             return;
1026         }
1027         
1028         Object[] values = (Object[])state;
1029         super.restoreState(facesContext,values[0]);
1030         if (values[1] instanceof _AttachedDeltaWrapper)
1031         {
1032             //Delta
1033             if (_validatorList != null)
1034             {
1035                 ((StateHolder)_validatorList).restoreState(facesContext,
1036                         ((_AttachedDeltaWrapper) values[1]).getWrappedStateObject());
1037             }
1038         }
1039         else if (values[1] != null || !initialStateMarked())
1040         {
1041             //Full
1042             _validatorList = (_DeltaList<Validator>)
1043                 restoreAttachedState(facesContext,values[1]);
1044         }
1045     }
1046     
1047     private Object saveValidatorList(FacesContext facesContext)
1048     {
1049         PartialStateHolder holder = (PartialStateHolder) _validatorList;
1050         if (initialStateMarked() && _validatorList != null && holder.initialStateMarked())
1051         {                
1052             Object attachedState = holder.saveState(facesContext);
1053             if (attachedState != null)
1054             {
1055                 return new _AttachedDeltaWrapper(_validatorList.getClass(),
1056                         attachedState);
1057             }
1058             //_validatorList instances once is created never changes, we can return null
1059             return null;
1060         }
1061         else
1062         {
1063             return saveAttachedState(facesContext,_validatorList);
1064         }            
1065     }
1066     
1067     /**
1068      * Returns the debug-info Map for this component.
1069      * @return
1070      */
1071     @SuppressWarnings("unchecked")
1072     private Map<String, List<Object[]>> _getDebugInfoMap()
1073     {
1074         Map<String, Object> requestMap = getFacesContext()
1075                 .getExternalContext().getRequestMap();
1076         Map<String, List<Object[]>> debugInfo = (Map<String, List<Object[]>>) 
1077                 requestMap.get(DEBUG_INFO_KEY + getClientId());
1078         if (debugInfo == null)
1079         {
1080             // no debug info available yet, create one and put it on the attributes map
1081             debugInfo = new HashMap<String, List<Object[]>>();
1082             requestMap.put(DEBUG_INFO_KEY + getClientId(), debugInfo);
1083         }
1084         return debugInfo;
1085     }
1086     
1087     /**
1088      * Returns the field's debug-infos from the component's debug-info Map.
1089      * @param field
1090      * @return
1091      */
1092     private List<Object[]> _getFieldDebugInfos(final String field)
1093     {
1094         Map<String, List<Object[]>> debugInfo = _getDebugInfoMap();
1095         List<Object[]> fieldDebugInfo = debugInfo.get(field);
1096         if (fieldDebugInfo == null)
1097         {
1098             // no field debug-infos yet, create them and store it in the Map
1099             fieldDebugInfo = new ArrayList<Object[]>();
1100             debugInfo.put(field, fieldDebugInfo);
1101         }
1102         return fieldDebugInfo;
1103     }
1104     
1105     /**
1106      * Creates the field debug-info for the given field, which changed
1107      * from oldValue to newValue.
1108      * 
1109      * @param facesContext
1110      * @param field
1111      * @param oldValue
1112      * @param newValue
1113      * @param skipStackTaceElements How many StackTraceElements should be skipped
1114      *                              when the calling function will be determined.
1115      */
1116     private void _createFieldDebugInfo(FacesContext facesContext,
1117             final String field, Object oldValue, 
1118             Object newValue, final int skipStackTaceElements)
1119     {
1120         if (oldValue == null && newValue == null)
1121         {
1122             // both values are null, not interesting and can
1123             // happen a lot in UIData with saving and restoring state
1124             return;
1125         }
1126         
1127         if (facesContext.getViewRoot() == null)
1128         {
1129             // No viewRoot set, it is creating component, 
1130             // so it is not possible to calculate the clientId, 
1131             // abort processing because the interesting part will
1132             // happen later.
1133             return;
1134         }
1135         
1136         if (getParent() == null || !isInView())
1137         {
1138             //Skip if no parent or is not in view
1139             return;
1140         }
1141         
1142         // convert Array values into a more readable format
1143         if (oldValue != null && oldValue.getClass().isArray() && Object[].class.isAssignableFrom(oldValue.getClass()))
1144         {
1145             oldValue = Arrays.deepToString((Object[]) oldValue);
1146         }
1147         if (newValue != null && newValue.getClass().isArray() && Object[].class.isAssignableFrom(newValue.getClass()))
1148         {
1149             newValue = Arrays.deepToString((Object[]) newValue);
1150         }
1151         
1152         // use Throwable to get the current call stack
1153         Throwable throwableHelper = new Throwable();
1154         StackTraceElement[] stackTraceElements = throwableHelper.getStackTrace();
1155         List<StackTraceElement> debugStackTraceElements = new LinkedList<StackTraceElement>();
1156         
1157         // + 1 because this method should also be skipped
1158         for (int i = skipStackTaceElements + 1; i < stackTraceElements.length; i++)
1159         {
1160             debugStackTraceElements.add(stackTraceElements[i]);
1161             
1162             if (FacesServlet.class.getCanonicalName()
1163                     .equals(stackTraceElements[i].getClassName()))
1164             {
1165                 // stop after the FacesServlet
1166                 break;
1167             }
1168         }
1169         
1170         // create the debug-info array
1171         // structure:
1172         //     - 0: phase
1173         //     - 1: old value
1174         //     - 2: new value
1175         //     - 3: StackTraceElement List
1176         // NOTE that we cannot create a class here to encapsulate this data,
1177         // because this is not on the spec and the class would not be available in impl.
1178         Object[] debugInfo = new Object[4];
1179         debugInfo[0] = facesContext.getCurrentPhaseId();
1180         debugInfo[1] = oldValue;
1181         debugInfo[2] = newValue;
1182         debugInfo[3] = debugStackTraceElements;
1183         
1184         // add the debug info
1185         _getFieldDebugInfos(field).add(debugInfo);
1186     }
1187     
1188     /**
1189      * Check if a value is empty or not. Since we don't know the class of
1190      * value we have to check and deal with it properly.
1191      * 
1192      * @since 2.0
1193      * @param value
1194      * @return
1195      */
1196     public static boolean isEmpty(Object value)
1197     {
1198         if (value == null)
1199         {
1200             return true;
1201         }
1202         else if (value instanceof String)
1203         {
1204             if ( ((String)value).trim().length() <= 0 )
1205             {
1206                 return true;
1207             }
1208         }
1209         else if (value instanceof Collection)
1210         {
1211             if ( ((Collection)value).isEmpty())
1212             {
1213                 return true;
1214             }
1215         }
1216         else if (value.getClass().isArray())
1217         {
1218             if (java.lang.reflect.Array.getLength(value) <= 0)
1219             {
1220                 return true;
1221             }
1222         }
1223         else if (value instanceof Map)
1224         {
1225             if ( ((Map)value).isEmpty())
1226             {
1227                 return true;
1228             }
1229         }
1230         return false;
1231     }
1232 
1233 }