Coverage Report - org.apache.tapestry.valid.ValidationDelegate
 
Classes in this File Line Coverage Branch Coverage Complexity
ValidationDelegate
81% 
83% 
2
 
 1  
 // Copyright 2004, 2005 The Apache Software Foundation
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package org.apache.tapestry.valid;
 16  
 
 17  
 import org.apache.tapestry.IMarkupWriter;
 18  
 import org.apache.tapestry.IRender;
 19  
 import org.apache.tapestry.IRequestCycle;
 20  
 import org.apache.tapestry.Tapestry;
 21  
 import org.apache.tapestry.form.IFormComponent;
 22  
 
 23  
 import java.util.*;
 24  
 
 25  
 /**
 26  
  * A base implementation of {@link IValidationDelegate} that can be used as a
 27  
  * managed bean. This class is often subclassed, typically to override
 28  
  * presentation details.
 29  
  * 
 30  
  * @author Howard Lewis Ship
 31  
  * @since 1.0.5
 32  
  */
 33  
 
 34  13
 public class ValidationDelegate implements IValidationDelegate
 35  
 {
 36  
 
 37  
     private static final long serialVersionUID = 6215074338439140780L;
 38  
 
 39  
     private transient IFormComponent _currentComponent;
 40  
 
 41  
     private transient String _focusField;
 42  
 
 43  13
     private transient int _focusPriority = -1;
 44  
 
 45  
     /**
 46  
      * A list of {@link IFieldTracking}.
 47  
      */
 48  
 
 49  13
     private final List _trackings = new ArrayList();
 50  
 
 51  
     /**
 52  
      * A map of {@link IFieldTracking}, keyed on form element name.
 53  
      */
 54  
 
 55  13
     private final Map _trackingMap = new HashMap();
 56  
 
 57  
     public void clear()
 58  
     {
 59  14
         _currentComponent = null;
 60  14
         _trackings.clear();
 61  14
         _trackingMap.clear();
 62  14
     }
 63  
 
 64  
     public void clearErrors()
 65  
     {
 66  1
         if (_trackings == null) 
 67  0
             return;
 68  
 
 69  1
         Iterator i = _trackings.iterator();
 70  2
         while(i.hasNext())
 71  
         {
 72  1
             FieldTracking ft = (FieldTracking) i.next();
 73  1
             ft.setErrorRenderer(null);
 74  1
         }
 75  1
     }
 76  
 
 77  
     /**
 78  
      * If the form component is in error, places a <font color="red"<
 79  
      * around it. Note: this will only work on the render phase after a rewind,
 80  
      * and will be confused if components are inside any kind of loop.
 81  
      */
 82  
 
 83  
     public void writeLabelPrefix(IFormComponent component,
 84  
             IMarkupWriter writer, IRequestCycle cycle)
 85  
     {
 86  0
         if (isInError(component))
 87  
         {
 88  0
             writer.begin("font");
 89  0
             writer.attribute("color", "red");
 90  
         }
 91  0
     }
 92  
 
 93  
     /**
 94  
      * Does nothing by default. {@inheritDoc}
 95  
      */
 96  
 
 97  
     public void writeLabelAttributes(IMarkupWriter writer, IRequestCycle cycle,
 98  
             IFormComponent component)
 99  
     {
 100  5
     }
 101  
     
 102  
     /**
 103  
      * {@inheritDoc}
 104  
      */
 105  
     public void beforeLabelText(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component)
 106  
     {
 107  0
     }
 108  
     
 109  
     /**
 110  
      * {@inheritDoc}
 111  
      */
 112  
     public void afterLabelText(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component)
 113  
     {
 114  0
     }
 115  
     
 116  
     /**
 117  
      * Closes the <font> element,started by
 118  
      * {@link #writeLabelPrefix(IFormComponent,IMarkupWriter,IRequestCycle)},
 119  
      * if the form component is in error.
 120  
      */
 121  
 
 122  
     public void writeLabelSuffix(IFormComponent component,
 123  
             IMarkupWriter writer, IRequestCycle cycle)
 124  
     {
 125  0
         if (isInError(component))
 126  
         {
 127  0
             writer.end();
 128  
         }
 129  0
     }
 130  
 
 131  
     /**
 132  
      * Returns the {@link IFieldTracking}for the current component, if any. The
 133  
      * {@link IFieldTracking}is usually created in
 134  
      * {@link #record(String, ValidationConstraint)}or in
 135  
      * {@link #record(IRender, ValidationConstraint)}.
 136  
      * <p>
 137  
      * Components may be rendered multiple times, with multiple names (provided
 138  
      * by the {@link org.apache.tapestry.form.Form}, care must be taken that
 139  
      * this method is invoked <em>after</em> the Form has provided a unique
 140  
      * {@link IFormComponent#getName()}for the component.
 141  
      * 
 142  
      * @see #setFormComponent(IFormComponent)
 143  
      * @return the {@link FieldTracking}, or null if the field has no tracking.
 144  
      */
 145  
 
 146  
     protected FieldTracking getComponentTracking()
 147  
     {
 148  31
         return (FieldTracking) _trackingMap.get(_currentComponent.getName());
 149  
     }
 150  
 
 151  
     public void setFormComponent(IFormComponent component)
 152  
     {
 153  23
         _currentComponent = component;
 154  23
     }
 155  
 
 156  
     public boolean isInError()
 157  
     {
 158  5
         IFieldTracking tracking = getComponentTracking();
 159  
 
 160  5
         return tracking != null && tracking.isInError();
 161  
     }
 162  
 
 163  
     public String getFieldInputValue()
 164  
     {
 165  1
         IFieldTracking tracking = getComponentTracking();
 166  
 
 167  1
         return tracking == null ? null : tracking.getInput();
 168  
     }
 169  
 
 170  
     /**
 171  
      * Returns all the field trackings as an unmodifiable List.
 172  
      */
 173  
 
 174  
     public List getFieldTracking()
 175  
     {
 176  8
         if (Tapestry.size(_trackings) == 0) return null;
 177  
 
 178  7
         return Collections.unmodifiableList(_trackings);
 179  
     }
 180  
 
 181  
     /** @since 3.0.2 */
 182  
     public IFieldTracking getCurrentFieldTracking()
 183  
     {
 184  0
         return findCurrentTracking();
 185  
     }
 186  
 
 187  
     public void reset()
 188  
     {
 189  3
         IFieldTracking tracking = getComponentTracking();
 190  
 
 191  3
         if (tracking != null)
 192  
         {
 193  3
             _trackings.remove(tracking);
 194  3
             _trackingMap.remove(tracking.getFieldName());
 195  
         }
 196  3
     }
 197  
 
 198  
     /**
 199  
      * Invokes {@link #record(String, ValidationConstraint)}, or
 200  
      * {@link #record(IRender, ValidationConstraint)}if the
 201  
      * {@link ValidatorException#getErrorRenderer() error renderer property}is
 202  
      * not null.
 203  
      */
 204  
 
 205  
     public void record(ValidatorException ex)
 206  
     {
 207  8
         IRender errorRenderer = ex.getErrorRenderer();
 208  
 
 209  8
         if (errorRenderer == null)
 210  7
             record(ex.getMessage(), ex.getConstraint());
 211  
         else 
 212  1
             record(errorRenderer, ex.getConstraint());
 213  8
     }
 214  
 
 215  
     /**
 216  
      * Invokes {@link #record(IRender, ValidationConstraint)}, after wrapping
 217  
      * the message parameter in a {@link RenderString}.
 218  
      */
 219  
 
 220  
     public void record(String message, ValidationConstraint constraint)
 221  
     {
 222  11
         record(new RenderString(message), constraint);
 223  11
     }
 224  
 
 225  
     /**
 226  
      * Records error information about the currently selected component, or
 227  
      * records unassociated (with any field) errors.
 228  
      * <p>
 229  
      * Currently, you may have at most one error per <em>field</em> (note the
 230  
      * difference between field and component), but any number of unassociated
 231  
      * errors.
 232  
      * <p>
 233  
      * Subclasses may override the default error message (based on other
 234  
      * factors, such as the field and constraint) before invoking this
 235  
      * implementation.
 236  
      * 
 237  
      * @since 1.0.9
 238  
      */
 239  
 
 240  
     public void record(IRender errorRenderer, ValidationConstraint constraint)
 241  
     {
 242  13
         FieldTracking tracking = findCurrentTracking();
 243  
 
 244  
         // Note that recording two errors for the same field is not advised; the
 245  
         // second will override the first.
 246  
 
 247  13
         tracking.setErrorRenderer(errorRenderer);
 248  13
         tracking.setConstraint(constraint);
 249  13
     }
 250  
 
 251  
     /** @since 4.0 */
 252  
 
 253  
     public void record(IFormComponent field, String message)
 254  
     {
 255  2
         setFormComponent(field);
 256  
 
 257  2
         record(message, null);
 258  2
     }
 259  
 
 260  
     public void recordFieldInputValue(String input)
 261  
     {
 262  11
         FieldTracking tracking = findCurrentTracking();
 263  
 
 264  11
         tracking.setInput(input);
 265  11
     }
 266  
 
 267  
     /**
 268  
      * Finds or creates the field tracking for the
 269  
      * {@link #setFormComponent(IFormComponent)} &nbsp;current component. If no
 270  
      * current component, an unassociated error is created and returned.
 271  
      * 
 272  
      * @since 3.0
 273  
      */
 274  
 
 275  
     protected FieldTracking findCurrentTracking()
 276  
     {
 277  24
         FieldTracking result = null;
 278  
 
 279  24
         if (_currentComponent == null)
 280  
         {
 281  2
             result = new FieldTracking();
 282  
 
 283  
             // Add it to the field trackings, but not to the
 284  
             // map.
 285  
 
 286  2
             _trackings.add(result);
 287  
         }
 288  
         else
 289  
         {
 290  22
             result = getComponentTracking();
 291  
 
 292  22
             if (result == null)
 293  
             {
 294  14
                 String fieldName = _currentComponent.getName();
 295  
 
 296  14
                 result = new FieldTracking(fieldName, _currentComponent);
 297  
 
 298  14
                 _trackings.add(result);
 299  14
                 _trackingMap.put(fieldName, result);
 300  
             }
 301  
         }
 302  
 
 303  24
         return result;
 304  
     }
 305  
 
 306  
     /**
 307  
      * Does nothing. Override in a subclass to decorate fields.
 308  
      */
 309  
 
 310  
     public void writePrefix(IMarkupWriter writer, IRequestCycle cycle,
 311  
             IFormComponent component, IValidator validator)
 312  
     {
 313  3
     }
 314  
 
 315  
     /**
 316  
      * Currently appends a single css class attribute of <code>fieldInvalid</code> if the field
 317  
      * is in error.  If the field has a matching constraint of {@link ValidationConstraint#REQUIRED}
 318  
      * the <code>fieldMissing</code> is written instead. 
 319  
      */
 320  
 
 321  
     public void writeAttributes(IMarkupWriter writer, IRequestCycle cycle,
 322  
             IFormComponent component, IValidator validator)
 323  
     {
 324  3
         IFieldTracking tracking = getFieldTracking(component);
 325  3
         if (tracking == null)
 326  3
             return;
 327  
 
 328  0
         if (tracking.getConstraint() != null
 329  
             && tracking.getConstraint() == ValidationConstraint.REQUIRED)
 330  
         {
 331  0
             writer.appendAttribute("class", "fieldMissing");
 332  0
         } else if (tracking.isInError())
 333  
         {
 334  
 
 335  0
             writer.appendAttribute("class", "fieldInvalid");
 336  
         }        
 337  0
     }
 338  
 
 339  
     /**
 340  
      * Default implementation; if the current field is in error, then a suffix
 341  
      * is written. The suffix is:
 342  
      * <code>&amp;nbsp;&lt;font color="red"&gt;**&lt;/font&gt;</code>.
 343  
      */
 344  
 
 345  
     public void writeSuffix(IMarkupWriter writer, IRequestCycle cycle,
 346  
             IFormComponent component, IValidator validator)
 347  
     {
 348  3
         if (isInError())
 349  
         {
 350  0
             writer.printRaw("&nbsp;");
 351  0
             writer.begin("font");
 352  0
             writer.attribute("color", "red");
 353  0
             writer.print("**");
 354  0
             writer.end();
 355  
         }
 356  3
     }
 357  
     
 358  
     public boolean getHasErrors()
 359  
     {
 360  8
         return getFirstError() != null;
 361  
     }
 362  
 
 363  
     /**
 364  
      * A convienience, as most pages just show the first error on the page.
 365  
      * <p>
 366  
      * As of release 1.0.9, this returns an instance of {@link IRender}, not a
 367  
      * {@link String}.
 368  
      */
 369  
 
 370  
     public IRender getFirstError()
 371  
     {
 372  15
         if (Tapestry.size(_trackings) == 0)
 373  4
             return null;
 374  
 
 375  11
         Iterator i = _trackings.iterator();
 376  
 
 377  16
         while(i.hasNext())
 378  
         {
 379  13
             IFieldTracking tracking = (IFieldTracking) i.next();
 380  
 
 381  13
             if (tracking.isInError()) return tracking.getErrorRenderer();
 382  5
         }
 383  
 
 384  3
         return null;
 385  
     }
 386  
 
 387  
     /**
 388  
      * Checks to see if the field is in error. This will <em>not</em> work
 389  
      * properly in a loop, but is only used by {@link FieldLabel}. Therefore,
 390  
      * using {@link FieldLabel}in a loop (where the {@link IFormComponent}is
 391  
      * renderred more than once) will not provide correct results.
 392  
      */
 393  
 
 394  
     protected boolean isInError(IFormComponent component)
 395  
     {
 396  
         // Get the name as most recently rendered.
 397  
 
 398  0
         String fieldName = component.getName();
 399  
 
 400  0
         IFieldTracking tracking = (IFieldTracking) _trackingMap.get(fieldName);
 401  
 
 402  0
         return tracking != null && tracking.isInError();
 403  
     }
 404  
 
 405  
     protected IFieldTracking getFieldTracking(IFormComponent component)
 406  
     {
 407  3
         String fieldName = component.getName();
 408  
 
 409  3
         return (IFieldTracking) _trackingMap.get(fieldName);
 410  
     }
 411  
 
 412  
     /**
 413  
      * Returns a {@link List}of {@link IFieldTracking}s. This is the master
 414  
      * list of trackings, except that it omits and trackings that are not
 415  
      * associated with a particular field. May return an empty list, or null.
 416  
      * <p>
 417  
      * Order is not determined, though it is likely the order in which
 418  
      * components are laid out on in the template (this is subject to change).
 419  
      */
 420  
 
 421  
     public List getAssociatedTrackings()
 422  
     {
 423  1
         int count = Tapestry.size(_trackings);
 424  
 
 425  1
         if (count == 0) return null;
 426  
 
 427  1
         List result = new ArrayList(count);
 428  
 
 429  3
         for(int i = 0; i < count; i++)
 430  
         {
 431  2
             IFieldTracking tracking = (IFieldTracking) _trackings.get(i);
 432  
 
 433  2
             if (tracking.getFieldName() == null) continue;
 434  
 
 435  1
             result.add(tracking);
 436  
         }
 437  
 
 438  1
         return result;
 439  
     }
 440  
 
 441  
     /**
 442  
      * Like {@link #getAssociatedTrackings()}, but returns only the
 443  
      * unassociated trackings. Unassociated trackings are new (in release
 444  
      * 1.0.9), and are why interface {@link IFieldTracking}is not very well
 445  
      * named.
 446  
      * <p>
 447  
      * The trackings are returned in an unspecified order, which (for the
 448  
      * moment, anyway) is the order in which they were added (this could change
 449  
      * in the future, or become more concrete).
 450  
      */
 451  
 
 452  
     public List getUnassociatedTrackings()
 453  
     {
 454  2
         int count = Tapestry.size(_trackings);
 455  
 
 456  2
         if (count == 0) return null;
 457  
 
 458  2
         List result = new ArrayList(count);
 459  
 
 460  5
         for(int i = 0; i < count; i++)
 461  
         {
 462  3
             IFieldTracking tracking = (IFieldTracking) _trackings.get(i);
 463  
 
 464  3
             if (tracking.getFieldName() != null) continue;
 465  
 
 466  2
             result.add(tracking);
 467  
         }
 468  
 
 469  2
         return result;
 470  
     }
 471  
 
 472  
     public List getErrorRenderers()
 473  
     {
 474  2
         List result = new ArrayList();
 475  
 
 476  2
         Iterator i = _trackings.iterator();
 477  4
         while(i.hasNext())
 478  
         {
 479  2
             IFieldTracking tracking = (IFieldTracking) i.next();
 480  
 
 481  2
             IRender errorRenderer = tracking.getErrorRenderer();
 482  
 
 483  2
             if (errorRenderer != null) 
 484  1
                 result.add(errorRenderer);
 485  2
         }
 486  
 
 487  2
         return result;
 488  
     }
 489  
 
 490  
     /** @since 4.0 */
 491  
 
 492  
     public void registerForFocus(IFormComponent field, int priority)
 493  
     {
 494  5
         if (priority > _focusPriority)
 495  
         {
 496  4
             _focusField = field.getClientId();
 497  4
             _focusPriority = priority;
 498  
         }
 499  5
     }
 500  
 
 501  
     /**
 502  
      * Returns the focus field, or null if no form components registered for
 503  
      * focus (i.e., they were all disabled).
 504  
      */
 505  
 
 506  
     public String getFocusField()
 507  
     {
 508  1
         return _focusField;
 509  
     }
 510  
 
 511  
 }