001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.valid;
016    
017    import java.util.ArrayList;
018    import java.util.Collections;
019    import java.util.HashMap;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.tapestry.IMarkupWriter;
025    import org.apache.tapestry.IRender;
026    import org.apache.tapestry.IRequestCycle;
027    import org.apache.tapestry.Tapestry;
028    import org.apache.tapestry.form.IFormComponent;
029    
030    /**
031     * A base implementation of {@link IValidationDelegate}that can be used as a managed bean. This
032     * class is often subclassed, typically to override presentation details.
033     * 
034     * @author Howard Lewis Ship
035     * @since 1.0.5
036     */
037    
038    public class ValidationDelegate implements IValidationDelegate
039    {
040        private static final long serialVersionUID = 6215074338439140780L;
041    
042        private transient IFormComponent _currentComponent;
043    
044        private transient String _focusField;
045    
046        private transient int _focusPriority = -1;
047    
048        /**
049         * A list of {@link IFieldTracking}.
050         */
051    
052        private final List _trackings = new ArrayList();
053    
054        /**
055         * A map of {@link IFieldTracking}, keyed on form element name.
056         */
057    
058        private final Map _trackingMap = new HashMap();
059    
060        public void clear()
061        {
062            _currentComponent = null;
063            _trackings.clear();
064            _trackingMap.clear();
065        }
066    
067        public void clearErrors()
068        {
069            if (_trackings == null)
070                return;
071    
072            Iterator i = _trackings.iterator();
073            while (i.hasNext())
074            {
075                FieldTracking ft = (FieldTracking) i.next();
076                ft.setErrorRenderer(null);
077            }
078        }
079    
080        /**
081         * If the form component is in error, places a <font color="red"< around it. Note: this
082         * will only work on the render phase after a rewind, and will be confused if components are
083         * inside any kind of loop.
084         */
085    
086        public void writeLabelPrefix(IFormComponent component, IMarkupWriter writer, IRequestCycle cycle)
087        {
088            if (isInError(component))
089            {
090                writer.begin("font");
091                writer.attribute("color", "red");
092            }
093        }
094    
095        /**
096         * Does nothing by default.
097         * {@inheritDoc}
098         */
099        
100        public void writeLabelAttributes(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component) {
101            }
102    
103            /**
104         * Closes the <font> element,started by
105         * {@link #writeLabelPrefix(IFormComponent,IMarkupWriter,IRequestCycle)}, if the form component
106         * is in error.
107         */
108    
109        public void writeLabelSuffix(IFormComponent component, IMarkupWriter writer, IRequestCycle cycle)
110        {
111            if (isInError(component))
112            {
113                writer.end();
114            }
115        }
116    
117        /**
118         * Returns the {@link IFieldTracking}for the current component, if any. The
119         * {@link IFieldTracking}is usually created in {@link #record(String, ValidationConstraint)}or
120         * in {@link #record(IRender, ValidationConstraint)}.
121         * <p>
122         * Components may be rendered multiple times, with multiple names (provided by the
123         * {@link org.apache.tapestry.form.Form}, care must be taken that this method is invoked
124         * <em>after</em> the Form has provided a unique {@link IFormComponent#getName()}for the
125         * component.
126         * 
127         * @see #setFormComponent(IFormComponent)
128         * @return the {@link FieldTracking}, or null if the field has no tracking.
129         */
130    
131        protected FieldTracking getComponentTracking()
132        {
133            return (FieldTracking) _trackingMap.get(_currentComponent.getName());
134        }
135    
136        public void setFormComponent(IFormComponent component)
137        {
138            _currentComponent = component;
139        }
140    
141        public boolean isInError()
142        {
143            IFieldTracking tracking = getComponentTracking();
144    
145            return tracking != null && tracking.isInError();
146        }
147    
148        public String getFieldInputValue()
149        {
150            IFieldTracking tracking = getComponentTracking();
151    
152            return tracking == null ? null : tracking.getInput();
153        }
154    
155        /**
156         * Returns all the field trackings as an unmodifiable List.
157         */
158    
159        public List getFieldTracking()
160        {
161            if (Tapestry.size(_trackings) == 0)
162                return null;
163    
164            return Collections.unmodifiableList(_trackings);
165        }
166    
167        /** @since 3.0.2 */
168        public IFieldTracking getCurrentFieldTracking()
169        {
170            return findCurrentTracking();
171        }
172    
173        public void reset()
174        {
175            IFieldTracking tracking = getComponentTracking();
176    
177            if (tracking != null)
178            {
179                _trackings.remove(tracking);
180                _trackingMap.remove(tracking.getFieldName());
181            }
182        }
183    
184        /**
185         * Invokes {@link #record(String, ValidationConstraint)}, or
186         * {@link #record(IRender, ValidationConstraint)}if the
187         * {@link ValidatorException#getErrorRenderer() error renderer property}is not null.
188         */
189    
190        public void record(ValidatorException ex)
191        {
192            IRender errorRenderer = ex.getErrorRenderer();
193    
194            if (errorRenderer == null)
195                record(ex.getMessage(), ex.getConstraint());
196            else
197                record(errorRenderer, ex.getConstraint());
198        }
199    
200        /**
201         * Invokes {@link #record(IRender, ValidationConstraint)}, after wrapping the message parameter
202         * in a {@link RenderString}.
203         */
204    
205        public void record(String message, ValidationConstraint constraint)
206        {
207            record(new RenderString(message), constraint);
208        }
209    
210        /**
211         * Records error information about the currently selected component, or records unassociated
212         * (with any field) errors.
213         * <p>
214         * Currently, you may have at most one error per <em>field</em> (note the difference between
215         * field and component), but any number of unassociated errors.
216         * <p>
217         * Subclasses may override the default error message (based on other factors, such as the field
218         * and constraint) before invoking this implementation.
219         * 
220         * @since 1.0.9
221         */
222    
223        public void record(IRender errorRenderer, ValidationConstraint constraint)
224        {
225            FieldTracking tracking = findCurrentTracking();
226    
227            // Note that recording two errors for the same field is not advised; the
228            // second will override the first.
229    
230            tracking.setErrorRenderer(errorRenderer);
231            tracking.setConstraint(constraint);
232        }
233    
234        /** @since 4.0 */
235    
236        public void record(IFormComponent field, String message)
237        {
238            setFormComponent(field);
239    
240            record(message, null);
241        }
242    
243        public void recordFieldInputValue(String input)
244        {
245            FieldTracking tracking = findCurrentTracking();
246    
247            tracking.setInput(input);
248        }
249    
250        /**
251         * Finds or creates the field tracking for the {@link #setFormComponent(IFormComponent)}
252         * &nbsp;current component. If no current component, an unassociated error is created and
253         * returned.
254         * 
255         * @since 3.0
256         */
257    
258        protected FieldTracking findCurrentTracking()
259        {
260            FieldTracking result = null;
261    
262            if (_currentComponent == null)
263            {
264                result = new FieldTracking();
265    
266                // Add it to the field trackings, but not to the
267                // map.
268    
269                _trackings.add(result);
270            }
271            else
272            {
273                result = getComponentTracking();
274    
275                if (result == null)
276                {
277                    String fieldName = _currentComponent.getName();
278    
279                    result = new FieldTracking(fieldName, _currentComponent);
280    
281                    _trackings.add(result);
282                    _trackingMap.put(fieldName, result);
283                }
284            }
285    
286            return result;
287        }
288    
289        /**
290         * Does nothing. Override in a subclass to decoreate fields.
291         */
292    
293        public void writePrefix(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component,
294                IValidator validator)
295        {
296        }
297    
298        /**
299         * Does nothing. Override in a subclass to decorate fields.
300         */
301    
302        public void writeAttributes(IMarkupWriter writer, IRequestCycle cycle,
303                IFormComponent component, IValidator validator)
304        {
305        }
306    
307        /**
308         * Default implementation; if the current field is in error, then a suffix is written. The
309         * suffix is: <code>&amp;nbsp;&lt;font color="red"&gt;**&lt;/font&gt;</code>.
310         */
311    
312        public void writeSuffix(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component,
313                IValidator validator)
314        {
315            if (isInError())
316            {
317                writer.printRaw("&nbsp;");
318                writer.begin("font");
319                writer.attribute("color", "red");
320                writer.print("**");
321                writer.end();
322            }
323        }
324    
325        public boolean getHasErrors()
326        {
327            return getFirstError() != null;
328        }
329    
330        /**
331         * A convienience, as most pages just show the first error on the page.
332         * <p>
333         * As of release 1.0.9, this returns an instance of {@link IRender}, not a {@link String}.
334         */
335    
336        public IRender getFirstError()
337        {
338            if (Tapestry.size(_trackings) == 0)
339                return null;
340    
341            Iterator i = _trackings.iterator();
342    
343            while (i.hasNext())
344            {
345                IFieldTracking tracking = (IFieldTracking) i.next();
346    
347                if (tracking.isInError())
348                    return tracking.getErrorRenderer();
349            }
350    
351            return null;
352        }
353    
354        /**
355         * Checks to see if the field is in error. This will <em>not</em> work properly in a loop, but
356         * is only used by {@link FieldLabel}. Therefore, using {@link FieldLabel}in a loop (where the
357         * {@link IFormComponent}is renderred more than once) will not provide correct results.
358         */
359    
360        protected boolean isInError(IFormComponent component)
361        {
362            // Get the name as most recently rendered.
363    
364            String fieldName = component.getName();
365    
366            IFieldTracking tracking = (IFieldTracking) _trackingMap.get(fieldName);
367    
368            return tracking != null && tracking.isInError();
369        }
370    
371        /**
372         * Returns a {@link List}of {@link IFieldTracking}s. This is the master list of trackings,
373         * except that it omits and trackings that are not associated with a particular field. May
374         * return an empty list, or null.
375         * <p>
376         * Order is not determined, though it is likely the order in which components are laid out on in
377         * the template (this is subject to change).
378         */
379    
380        public List getAssociatedTrackings()
381        {
382            int count = Tapestry.size(_trackings);
383    
384            if (count == 0)
385                return null;
386    
387            List result = new ArrayList(count);
388    
389            for (int i = 0; i < count; i++)
390            {
391                IFieldTracking tracking = (IFieldTracking) _trackings.get(i);
392    
393                if (tracking.getFieldName() == null)
394                    continue;
395    
396                result.add(tracking);
397            }
398    
399            return result;
400        }
401    
402        /**
403         * Like {@link #getAssociatedTrackings()}, but returns only the unassociated trackings.
404         * Unassociated trackings are new (in release 1.0.9), and are why interface
405         * {@link IFieldTracking}is not very well named.
406         * <p>
407         * The trackings are returned in an unspecified order, which (for the moment, anyway) is the
408         * order in which they were added (this could change in the future, or become more concrete).
409         */
410    
411        public List getUnassociatedTrackings()
412        {
413            int count = Tapestry.size(_trackings);
414    
415            if (count == 0)
416                return null;
417    
418            List result = new ArrayList(count);
419    
420            for (int i = 0; i < count; i++)
421            {
422                IFieldTracking tracking = (IFieldTracking) _trackings.get(i);
423    
424                if (tracking.getFieldName() != null)
425                    continue;
426    
427                result.add(tracking);
428            }
429    
430            return result;
431        }
432    
433        public List getErrorRenderers()
434        {
435            List result = new ArrayList();
436    
437            Iterator i = _trackings.iterator();
438            while (i.hasNext())
439            {
440                IFieldTracking tracking = (IFieldTracking) i.next();
441    
442                IRender errorRenderer = tracking.getErrorRenderer();
443    
444                if (errorRenderer != null)
445                    result.add(errorRenderer);
446            }
447    
448            return result;
449        }
450    
451        /** @since 4.0 */
452    
453        public void registerForFocus(IFormComponent field, int priority)
454        {
455            if (priority > _focusPriority)
456            {
457                _focusField = field.getClientId();
458                _focusPriority = priority;
459            }
460        }
461    
462        /**
463         * Returns the focus field, or null if no form components registered for focus (i.e., they were
464         * all disabled).
465         */
466    
467        public String getFocusField()
468        {
469            return _focusField;
470        }
471    
472    }