Clover coverage report - Code Coverage for tapestry release 3.1-alpha-1
Coverage timestamp: Mon Feb 21 2005 09:16:14 EST
file stats: LOC: 835   Methods: 30
NCLOC: 414   Classes: 2
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
Form.java 90.9% 96.1% 100% 95%
coverage coverage
 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.form;
 16   
 
 17   
 import java.util.ArrayList;
 18   
 import java.util.Arrays;
 19   
 import java.util.HashMap;
 20   
 import java.util.HashSet;
 21   
 import java.util.Iterator;
 22   
 import java.util.List;
 23   
 import java.util.Map;
 24   
 import java.util.Set;
 25   
 
 26   
 import org.apache.hivemind.ApplicationRuntimeException;
 27   
 import org.apache.hivemind.HiveMind;
 28   
 import org.apache.tapestry.AbstractComponent;
 29   
 import org.apache.tapestry.IActionListener;
 30   
 import org.apache.tapestry.IDirect;
 31   
 import org.apache.tapestry.IEngine;
 32   
 import org.apache.tapestry.IForm;
 33   
 import org.apache.tapestry.IMarkupWriter;
 34   
 import org.apache.tapestry.IRequestCycle;
 35   
 import org.apache.tapestry.RenderRewoundException;
 36   
 import org.apache.tapestry.StaleLinkException;
 37   
 import org.apache.tapestry.Tapestry;
 38   
 import org.apache.tapestry.engine.ActionServiceParameter;
 39   
 import org.apache.tapestry.engine.DirectServiceParameter;
 40   
 import org.apache.tapestry.engine.IEngineService;
 41   
 import org.apache.tapestry.engine.ILink;
 42   
 import org.apache.tapestry.html.Body;
 43   
 import org.apache.tapestry.services.ServiceConstants;
 44   
 import org.apache.tapestry.util.IdAllocator;
 45   
 import org.apache.tapestry.util.StringSplitter;
 46   
 import org.apache.tapestry.valid.IValidationDelegate;
 47   
 
 48   
 /**
 49   
  * Component which contains form element components. Forms use the action or direct services to
 50   
  * handle the form submission. A Form will wrap other components and static HTML, including form
 51   
  * components such as {@link TextArea},{@link TextField},{@link Checkbox}, etc. [ <a
 52   
  * href="../../../../../ComponentReference/Form.html">Component Reference </a>]
 53   
  * <p>
 54   
  * When a form is submitted, it continues through the rewind cycle until <em>after</em> all of its
 55   
  * wrapped elements have renderred. As the form component render (in the rewind cycle), they will be
 56   
  * updating properties of the containing page and notifying thier listeners. Again: each form
 57   
  * component is responsible not only for rendering HTML (to present the form), but for handling it's
 58   
  * share of the form submission.
 59   
  * <p>
 60   
  * Only after all that is done will the Form notify its listener.
 61   
  * <p>
 62   
  * Starting in release 1.0.2, a Form can use either the direct service or the action service. The
 63   
  * default is the direct service, even though in earlier releases, only the action service was
 64   
  * available.
 65   
  * 
 66   
  * @author Howard Lewis Ship, David Solis
 67   
  */
 68   
 
 69   
 public abstract class Form extends AbstractComponent implements IForm, IDirect
 70   
 {
 71   
     private static class HiddenValue
 72   
     {
 73   
         String _name;
 74   
 
 75   
         String _value;
 76   
 
 77   
         String _id;
 78   
 
 79  6
         private HiddenValue(String name, String value)
 80   
         {
 81  6
             this(name, null, value);
 82   
         }
 83   
 
 84  10
         private HiddenValue(String name, String id, String value)
 85   
         {
 86  10
             _name = name;
 87  10
             _id = id;
 88  10
             _value = value;
 89   
         }
 90   
     }
 91   
 
 92   
     private boolean _rewinding;
 93   
 
 94   
     private String _name;
 95   
 
 96   
     /**
 97   
      * Used when rewinding the form to figure to match allocated ids (allocated during the rewind)
 98   
      * against expected ids (allocated in the previous request cycle, when the form was rendered).
 99   
      * 
 100   
      * @since 3.0
 101   
      */
 102   
 
 103   
     private int _allocatedIdIndex;
 104   
 
 105   
     /**
 106   
      * The list of allocated ids for form elements within this form. This list is constructed when a
 107   
      * form renders, and is validated against when the form is rewound.
 108   
      * 
 109   
      * @since 3.0
 110   
      */
 111   
 
 112   
     private List _allocatedIds = new ArrayList();
 113   
 
 114   
     /**
 115   
      * {@link Map}, keyed on {@link FormEventType}. Values are either a String (the name of a
 116   
      * single event), or a {@link List}of Strings.
 117   
      * 
 118   
      * @since 1.0.2
 119   
      */
 120   
 
 121   
     private Map _events;
 122   
 
 123   
     private static final int EVENT_MAP_SIZE = 3;
 124   
 
 125   
     private IdAllocator _elementIdAllocator = new IdAllocator();
 126   
 
 127   
     private String _encodingType;
 128   
 
 129   
     private List _hiddenValues;
 130   
 
 131   
     /**
 132   
      * @since 3.1
 133   
      */
 134   
     private Set _standardReservedIds = new HashSet();
 135   
 
 136   
     {
 137  36
         _standardReservedIds.addAll(Arrays.asList(ServiceConstants.RESERVED_IDS));
 138   
     }
 139   
 
 140   
     /**
 141   
      * Returns the currently active {@link IForm}, or null if no form is active. This is a
 142   
      * convienience method, the result will be null, or an instance of {@link IForm}, but not
 143   
      * necessarily a <code>Form</code>.
 144   
      */
 145   
 
 146  177
     public static IForm get(IRequestCycle cycle)
 147   
     {
 148  177
         return (IForm) cycle.getAttribute(ATTRIBUTE_NAME);
 149   
     }
 150   
 
 151   
     /**
 152   
      * Indicates to any wrapped form components that they should respond to the form submission.
 153   
      * 
 154   
      * @throws ApplicationRuntimeException
 155   
      *             if not rendering.
 156   
      */
 157   
 
 158  138
     public boolean isRewinding()
 159   
     {
 160  138
         if (!isRendering())
 161  0
             throw Tapestry.createRenderOnlyPropertyException(this, "rewinding");
 162   
 
 163  138
         return _rewinding;
 164   
     }
 165   
 
 166   
     /**
 167   
      * Injected.
 168   
      * 
 169   
      * @since 3.1
 170   
      */
 171   
 
 172   
     public abstract IEngineService getDirectService();
 173   
 
 174   
     /**
 175   
      * Injected.
 176   
      * 
 177   
      * @since 3.1
 178   
      */
 179   
 
 180   
     public abstract IEngineService getActionService();
 181   
 
 182   
     /**
 183   
      * Returns true if this Form is configured to use the direct service.
 184   
      * <p>
 185   
      * This is derived from the direct parameter, and defaults to true if not bound.
 186   
      * 
 187   
      * @since 1.0.2
 188   
      */
 189   
 
 190   
     public abstract boolean isDirect();
 191   
 
 192   
     /**
 193   
      * Returns true if the stateful parameter is bound to a true value. If stateful is not bound,
 194   
      * also returns the default, true.
 195   
      * 
 196   
      * @since 1.0.1
 197   
      */
 198   
 
 199  2
     public boolean getRequiresSession()
 200   
     {
 201  2
         return isStateful();
 202   
     }
 203   
 
 204   
     /**
 205   
      * Constructs a unique identifier (within the Form). The identifier consists of the component's
 206   
      * id, with an index number added to ensure uniqueness.
 207   
      * <p>
 208   
      * Simply invokes
 209   
      * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the
 210   
      * component's id.
 211   
      * 
 212   
      * @since 1.0.2
 213   
      */
 214   
 
 215  137
     public String getElementId(IFormComponent component)
 216   
     {
 217  137
         return getElementId(component, component.getId());
 218   
     }
 219   
 
 220   
     /**
 221   
      * Constructs a unique identifier from the base id. If possible, the id is used as-is.
 222   
      * Otherwise, a unique identifier is appended to the id.
 223   
      * <p>
 224   
      * This method is provided simply so that some components ({@link ImageSubmit}) have more
 225   
      * specific control over their names.
 226   
      * 
 227   
      * @since 1.0.3
 228   
      */
 229   
 
 230  137
     public String getElementId(IFormComponent component, String baseId)
 231   
     {
 232  137
         String result = _elementIdAllocator.allocateId(baseId);
 233   
 
 234  137
         if (_rewinding)
 235   
         {
 236  65
             if (_allocatedIdIndex >= _allocatedIds.size())
 237   
             {
 238  1
                 throw new StaleLinkException(FormMessages.formTooManyIds(
 239   
                         this,
 240   
                         _allocatedIds.size(),
 241   
                         component), this);
 242   
             }
 243   
 
 244  64
             String expected = (String) _allocatedIds.get(_allocatedIdIndex);
 245   
 
 246  64
             if (!result.equals(expected))
 247  2
                 throw new StaleLinkException(FormMessages.formIdMismatch(
 248   
                         this,
 249   
                         _allocatedIdIndex,
 250   
                         expected,
 251   
                         result,
 252   
                         component), this);
 253   
         }
 254   
         else
 255   
         {
 256  72
             _allocatedIds.add(result);
 257   
         }
 258   
 
 259  134
         _allocatedIdIndex++;
 260   
 
 261  134
         component.setName(result);
 262   
 
 263  134
         return result;
 264   
     }
 265   
 
 266   
     /**
 267   
      * Returns the name generated for the form. This is used to faciliate components that write
 268   
      * JavaScript and need to access the form or its contents.
 269   
      * <p>
 270   
      * This value is generated when the form renders, and is not cleared. If the Form is inside a
 271   
      * {@link org.apache.tapestry.components.Foreach}, this will be the most recently generated
 272   
      * name for the Form.
 273   
      * <p>
 274   
      * This property is exposed so that sophisticated applications can write JavaScript handlers for
 275   
      * the form and components within the form.
 276   
      * 
 277   
      * @see AbstractFormComponent#getName()
 278   
      */
 279   
 
 280  49
     public String getName()
 281   
     {
 282  49
         return _name;
 283   
     }
 284   
 
 285   
     /** @since 3.0 * */
 286   
 
 287  105
     protected void prepareForRender(IRequestCycle cycle)
 288   
     {
 289  105
         super.prepareForRender(cycle);
 290   
 
 291  105
         if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
 292  1
             throw new ApplicationRuntimeException(FormMessages.formsMayNotNest(), this, null, null);
 293   
 
 294  104
         cycle.setAttribute(ATTRIBUTE_NAME, this);
 295   
     }
 296   
 
 297  105
     protected void cleanupAfterRender(IRequestCycle cycle)
 298   
     {
 299  105
         _allocatedIdIndex = 0;
 300  105
         _allocatedIds.clear();
 301   
 
 302  105
         _events = null;
 303   
 
 304  105
         _elementIdAllocator.clear();
 305   
 
 306  105
         if (_hiddenValues != null)
 307  6
             _hiddenValues.clear();
 308   
 
 309  105
         cycle.removeAttribute(ATTRIBUTE_NAME);
 310   
 
 311  105
         _encodingType = null;
 312   
 
 313  105
         IValidationDelegate delegate = getDelegate();
 314   
 
 315  105
         if (delegate != null)
 316  11
             delegate.setFormComponent(null);
 317   
 
 318  105
         super.cleanupAfterRender(cycle);
 319   
     }
 320   
 
 321  49
     protected void writeAttributes(IMarkupWriter writer, ILink link)
 322   
     {
 323  49
         writer.begin(getTag());
 324  49
         writer.attribute("method", getMethod());
 325  49
         writer.attribute("name", _name);
 326  49
         writer.attribute("action", link.getURL(null, false));
 327   
 
 328  49
         if (_encodingType != null)
 329  2
             writer.attribute("enctype", _encodingType);
 330   
     }
 331   
 
 332  104
     protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
 333   
     {
 334  104
         String actionId = cycle.getNextActionId();
 335  104
         _name = getDisplayName() + actionId;
 336   
 
 337  104
         boolean renderForm = !cycle.isRewinding();
 338  104
         boolean rewound = cycle.isRewound(this);
 339   
 
 340  104
         _rewinding = rewound;
 341   
 
 342  104
         _allocatedIdIndex = 0;
 343   
 
 344  104
         if (rewound)
 345  39
             reconstructAllocatedIds(cycle);
 346   
 
 347   
         // When rendering, use a nested writer so that an embedded Upload
 348   
         // component can force the encoding type.
 349   
 
 350  104
         IMarkupWriter nested = writer.getNestedWriter();
 351   
 
 352  104
         renderBody(nested, cycle);
 353   
 
 354  94
         if (renderForm)
 355   
         {
 356  54
             ILink link = getLink(cycle, actionId);
 357   
 
 358  54
             writeAttributes(writer, link);
 359   
 
 360  54
             renderInformalParameters(writer, cycle);
 361  54
             writer.println();
 362   
 
 363   
             // Write the hidden's, or at least, reserve the query parameters
 364   
             // required by the Gesture.
 365   
 
 366  54
             String extraIds = writeLinkParameters(writer, link, !renderForm);
 367   
 
 368   
             // What's this for? It's part of checking for stale links.
 369   
             // We record the list of allocated ids.
 370   
             // On rewind, we check that the stored list against which
 371   
             // ids were allocated. If the persistent state of the page or
 372   
             // application changed between render (previous request cycle)
 373   
             // and rewind (current request cycle), then the list
 374   
             // of ids will change as well.
 375   
 
 376  54
             writeHiddenField(writer, _name, buildAllocatedIdList());
 377   
 
 378  54
             if (HiveMind.isNonBlank(extraIds))
 379  16
                 writeHiddenField(writer, _name, extraIds);
 380   
 
 381  54
             writeHiddenValues(writer);
 382   
 
 383  54
             nested.close();
 384   
 
 385  54
             writer.end(getTag());
 386   
 
 387   
             // Write out event handlers collected during the rendering.
 388   
 
 389  54
             emitEventHandlers(writer, cycle);
 390   
         }
 391   
 
 392  94
         if (rewound)
 393   
         {
 394  35
             int expected = _allocatedIds.size();
 395   
 
 396   
             // The other case, _allocatedIdIndex > expected, is
 397   
             // checked for inside getElementId(). Remember that
 398   
             // _allocatedIdIndex is incremented after allocating.
 399   
 
 400  35
             if (_allocatedIdIndex < expected)
 401   
             {
 402  1
                 String nextExpectedId = (String) _allocatedIds.get(_allocatedIdIndex);
 403   
 
 404  1
                 throw new StaleLinkException(FormMessages.formTooFewIds(this, expected
 405   
                         - _allocatedIdIndex, nextExpectedId), this);
 406   
             }
 407   
 
 408  34
             IActionListener listener = getListener();
 409   
 
 410  34
             if (listener != null)
 411  23
                 listener.actionTriggered(this, cycle);
 412   
 
 413   
             // Abort the rewind render.
 414   
 
 415  34
             throw new RenderRewoundException(this);
 416   
         }
 417   
     }
 418   
 
 419   
     /**
 420   
      * Adds an additional event handler.
 421   
      * 
 422   
      * @since 1.0.2
 423   
      */
 424   
 
 425  6
     public void addEventHandler(FormEventType type, String functionName)
 426   
     {
 427  6
         if (_events == null)
 428  3
             _events = new HashMap(EVENT_MAP_SIZE);
 429   
 
 430  6
         Object value = _events.get(type);
 431   
 
 432   
         // The value can either be a String, or a List of String. Since
 433   
         // it is rare for there to be more than one event handling function,
 434   
         // we start with just a String.
 435   
 
 436  6
         if (value == null)
 437   
         {
 438  3
             _events.put(type, functionName);
 439  3
             return;
 440   
         }
 441   
 
 442   
         // The second function added converts it to a List.
 443   
 
 444  3
         if (value instanceof String)
 445   
         {
 446  3
             List list = new ArrayList();
 447  3
             list.add(value);
 448  3
             list.add(functionName);
 449   
 
 450  3
             _events.put(type, list);
 451  3
             return;
 452   
         }
 453   
 
 454   
         // The third and subsequent function just
 455   
         // adds to the List.
 456   
 
 457  0
         List list = (List) value;
 458  0
         list.add(functionName);
 459   
     }
 460   
 
 461  49
     protected void emitEventHandlers(IMarkupWriter writer, IRequestCycle cycle)
 462   
     {
 463   
 
 464  49
         if (_events == null || _events.isEmpty())
 465  46
             return;
 466   
 
 467  3
         Body body = Body.get(cycle);
 468   
 
 469  3
         if (body == null)
 470  0
             throw new ApplicationRuntimeException(FormMessages.formNeedsBodyForEventHandlers(),
 471   
                     this, null, null);
 472   
 
 473  3
         StringBuffer buffer = new StringBuffer();
 474   
 
 475  3
         Iterator i = _events.entrySet().iterator();
 476  3
         while (i.hasNext())
 477   
         {
 478   
 
 479  3
             Map.Entry entry = (Map.Entry) i.next();
 480  3
             FormEventType type = (FormEventType) entry.getKey();
 481  3
             Object value = entry.getValue();
 482   
 
 483  3
             buffer.append("document.");
 484  3
             buffer.append(_name);
 485  3
             buffer.append(".");
 486  3
             buffer.append(type.getPropertyName());
 487  3
             buffer.append(" = ");
 488   
 
 489   
             // The typical case; one event one event handler. Easy enough.
 490   
 
 491  3
             if (value instanceof String)
 492   
             {
 493  0
                 buffer.append(value.toString());
 494  0
                 buffer.append(";");
 495   
             }
 496   
             else
 497   
             {
 498   
                 // Build a composite function in-place
 499   
 
 500  3
                 buffer.append("function ()\n{\n");
 501   
 
 502  3
                 boolean combineWithAnd = type.getCombineUsingAnd();
 503   
 
 504  3
                 List l = (List) value;
 505  3
                 int count = l.size();
 506   
 
 507  3
                 for (int j = 0; j < count; j++)
 508   
                 {
 509  6
                     String functionName = (String) l.get(j);
 510   
 
 511  6
                     if (j > 0)
 512   
                     {
 513   
 
 514  3
                         if (combineWithAnd)
 515  3
                             buffer.append(" &&");
 516   
                         else
 517  0
                             buffer.append(";");
 518   
                     }
 519   
 
 520  6
                     buffer.append("\n  ");
 521   
 
 522  6
                     if (combineWithAnd)
 523   
                     {
 524  6
                         if (j == 0)
 525  3
                             buffer.append("return ");
 526   
                         else
 527  3
                             buffer.append("  ");
 528   
                     }
 529   
 
 530  6
                     buffer.append(functionName);
 531  6
                     buffer.append("()");
 532   
                 }
 533   
 
 534  3
                 buffer.append(";\n}");
 535   
             }
 536   
 
 537  3
             buffer.append("\n\n");
 538   
         }
 539   
 
 540  3
         body.addInitializationScript(buffer.toString());
 541   
     }
 542   
 
 543   
     /**
 544   
      * Simply invokes {@link #render(IMarkupWriter, IRequestCycle)}.
 545   
      * 
 546   
      * @since 1.0.2
 547   
      */
 548   
 
 549  32
     public void rewind(IMarkupWriter writer, IRequestCycle cycle)
 550   
     {
 551  32
         render(writer, cycle);
 552   
     }
 553   
 
 554   
     /**
 555   
      * Method invoked by the direct service.
 556   
      * 
 557   
      * @since 1.0.2
 558   
      */
 559   
 
 560  32
     public void trigger(IRequestCycle cycle)
 561   
     {
 562  32
         Object[] parameters = cycle.getServiceParameters();
 563   
 
 564  32
         cycle.rewindForm(this, (String) parameters[0]);
 565   
     }
 566   
 
 567   
     /**
 568   
      * Builds the EngineServiceLink for the form, using either the direct or action service.
 569   
      * 
 570   
      * @since 1.0.3
 571   
      */
 572   
 
 573  54
     private ILink getLink(IRequestCycle cycle, String actionId)
 574   
     {
 575  54
         if (isDirect())
 576   
         {
 577  38
             Object parameter = new DirectServiceParameter(this, new Object[]
 578   
             { actionId });
 579  38
             return getDirectService().getLink(cycle, parameter);
 580   
         }
 581   
 
 582   
         // I'd love to pull out support for the action service entirely!
 583   
 
 584  16
         Object parameter = new ActionServiceParameter(this, actionId);
 585   
 
 586  16
         return getActionService().getLink(cycle, parameter);
 587   
     }
 588   
 
 589   
     /**
 590   
      * Writes parameters provided by the {@link ILink}. These parameters define the information
 591   
      * needed to dispatch the request, plus state information. The names of these parameters must be
 592   
      * reserved so that conflicts don't occur that could disrupt the request processing. For
 593   
      * example, if the id 'page' is not reserved, then a conflict could occur with a component whose
 594   
      * id is 'page'. A certain number of ids are always reserved, and we find any additional ids
 595   
      * beyond that set.
 596   
      * 
 597   
      * @return a list of additional reserved ids (not contained within
 598   
      *         {@link ServiceConstants#RESERVED_IDS}.
 599   
      */
 600   
 
 601  54
     private String writeLinkParameters(IMarkupWriter writer, ILink link, boolean reserveOnly)
 602   
     {
 603  54
         String[] names = link.getParameterNames();
 604  54
         int count = Tapestry.size(names);
 605   
 
 606  54
         StringBuffer extraIds = new StringBuffer();
 607  54
         String sep = "";
 608   
 
 609   
         // All the reserved ids, which are essential for
 610   
         // dispatching the request, are automatically reserved.
 611   
         // Thus, if you have a component with an id of 'service', its element id
 612   
         // will likely be 'service$0'.
 613   
 
 614  54
         preallocateReservedIds();
 615   
 
 616  54
         for (int i = 0; i < count; i++)
 617   
         {
 618  324
             String name = names[i];
 619   
 
 620   
             // Reserve the name.
 621   
 
 622  324
             if (!_standardReservedIds.contains(name))
 623   
             {
 624  16
                 _elementIdAllocator.allocateId(name);
 625   
 
 626  16
                 extraIds.append(sep);
 627  16
                 extraIds.append(name);
 628   
 
 629  16
                 sep = ",";
 630   
             }
 631   
 
 632  324
             if (!reserveOnly)
 633  324
                 writeHiddenFieldsForParameter(writer, link, name);
 634   
         }
 635   
 
 636  54
         return extraIds.toString();
 637   
     }
 638   
 
 639  93
     private void preallocateReservedIds()
 640   
     {
 641  93
         for (int i = 0; i < ServiceConstants.RESERVED_IDS.length; i++)
 642  558
             _elementIdAllocator.allocateId(ServiceConstants.RESERVED_IDS[i]);
 643   
     }
 644   
 
 645   
     /**
 646   
      * @since 3.0
 647   
      */
 648   
 
 649  279
     protected void writeHiddenField(IMarkupWriter writer, String name, String value)
 650   
     {
 651  279
         writeHiddenField(writer, name, null, value);
 652   
     }
 653   
 
 654  289
     protected void writeHiddenField(IMarkupWriter writer, String name, String id, String value)
 655   
     {
 656  289
         writer.beginEmpty("input");
 657  289
         writer.attribute("type", "hidden");
 658  289
         writer.attribute("name", name);
 659   
 
 660  289
         if (HiveMind.isNonBlank(id))
 661  1
             writer.attribute("id", id);
 662   
 
 663  289
         writer.attribute("value", value);
 664  289
         writer.println();
 665   
     }
 666   
 
 667   
     /**
 668   
      * @since 2.2
 669   
      */
 670   
 
 671  324
     private void writeHiddenFieldsForParameter(IMarkupWriter writer, ILink link,
 672   
             String parameterName)
 673   
     {
 674  324
         String[] values = link.getParameterValues(parameterName);
 675   
 
 676   
         // In some cases, there are no values, but a space is "reserved" for the provided name.
 677   
 
 678  324
         if (values == null)
 679  87
             return;
 680   
 
 681  237
         for (int i = 0; i < values.length; i++)
 682   
         {
 683  237
             writeHiddenField(writer, parameterName, values[i]);
 684   
         }
 685   
     }
 686   
 
 687   
     /**
 688   
      * Converts the allocateIds property into a string, a comma-separated list of ids. This is
 689   
      * included as a hidden field in the form and is used to identify discrepencies when the form is
 690   
      * submitted.
 691   
      * 
 692   
      * @since 3.0
 693   
      */
 694   
 
 695  54
     protected String buildAllocatedIdList()
 696   
     {
 697  54
         StringBuffer buffer = new StringBuffer();
 698  54
         int count = _allocatedIds.size();
 699   
 
 700  54
         for (int i = 0; i < count; i++)
 701   
         {
 702  65
             if (i > 0)
 703  26
                 buffer.append(',');
 704   
 
 705  65
             buffer.append(_allocatedIds.get(i));
 706   
         }
 707   
 
 708  54
         return buffer.toString();
 709   
     }
 710   
 
 711   
     /**
 712   
      * Invoked when rewinding a form to re-initialize the _allocatedIds and _elementIdAllocator.
 713   
      * Converts a string passed as a parameter (and containing a comma separated list of ids) back
 714   
      * into the allocateIds property. In addition, return the state of the ID allocater back to
 715   
      * where it was at the start of the render.
 716   
      * 
 717   
      * @see #buildAllocatedIdList()
 718   
      * @since 3.0
 719   
      */
 720   
 
 721  39
     protected void reconstructAllocatedIds(IRequestCycle cycle)
 722   
     {
 723  39
         String[] values = cycle.getParameters(_name);
 724   
 
 725  39
         StringSplitter splitter = new StringSplitter(',');
 726   
 
 727  39
         String renderList = values[0];
 728  39
         if (HiveMind.isNonBlank(renderList))
 729   
         {
 730   
 
 731  35
             String[] ids = splitter.splitToArray(values[0]);
 732   
 
 733  35
             for (int i = 0; i < ids.length; i++)
 734  70
                 _allocatedIds.add(ids[i]);
 735   
         }
 736   
 
 737   
         // Now, reconstruct the the initial state of the
 738   
         // id allocator.
 739   
 
 740  39
         preallocateReservedIds();
 741   
 
 742  39
         if (values.length > 1)
 743   
         {
 744  2
             String extraReservedIds = values[1];
 745  2
             String[] ids = splitter.splitToArray(extraReservedIds);
 746   
 
 747  2
             for (int i = 0; i < ids.length; i++)
 748  2
                 _elementIdAllocator.allocateId(ids[i]);
 749   
         }
 750   
     }
 751   
 
 752   
     public abstract IValidationDelegate getDelegate();
 753   
 
 754   
     public abstract void setDelegate(IValidationDelegate delegate);
 755   
 
 756   
     public abstract IActionListener getListener();
 757   
 
 758   
     public abstract String getMethod();
 759   
 
 760   
     public abstract boolean isStateful();
 761   
 
 762  2
     public void setEncodingType(String encodingType)
 763   
     {
 764  2
         if (_encodingType != null && !_encodingType.equals(encodingType))
 765  0
             throw new ApplicationRuntimeException(FormMessages.encodingTypeContention(
 766   
                     this,
 767   
                     _encodingType,
 768   
                     encodingType), this, null, null);
 769   
 
 770  2
         _encodingType = encodingType;
 771   
     }
 772   
 
 773   
     /**
 774   
      * Returns the tag of the form. The WML equivalent, {@link org.apache.tapestry.wml.Go},
 775   
      * overrides this.
 776   
      * 
 777   
      * @since 3.0
 778   
      */
 779   
 
 780  98
     protected String getTag()
 781   
     {
 782  98
         return "form";
 783   
     }
 784   
 
 785   
     /**
 786   
      * Returns the name of the element. The WML equivalent, {@link org.apache.tapestry.wml.Go},
 787   
      * overrides this.
 788   
      * 
 789   
      * @since 3.0
 790   
      */
 791   
 
 792  94
     protected String getDisplayName()
 793   
     {
 794  94
         return "Form";
 795   
     }
 796   
 
 797   
     /** @since 3.0 */
 798   
 
 799  6
     public void addHiddenValue(String name, String value)
 800   
     {
 801  6
         if (_hiddenValues == null)
 802  2
             _hiddenValues = new ArrayList();
 803   
 
 804  6
         _hiddenValues.add(new HiddenValue(name, value));
 805   
     }
 806   
 
 807   
     /** @since 3.0 */
 808   
 
 809  4
     public void addHiddenValue(String name, String id, String value)
 810   
     {
 811  4
         if (_hiddenValues == null)
 812  1
             _hiddenValues = new ArrayList();
 813   
 
 814  4
         _hiddenValues.add(new HiddenValue(name, id, value));
 815   
     }
 816   
 
 817   
     /**
 818   
      * Writes hidden values accumulated during the render (by components invoking
 819   
      * {@link #addHiddenValue(String, String)}.
 820   
      * 
 821   
      * @since 3.0
 822   
      */
 823   
 
 824  54
     protected void writeHiddenValues(IMarkupWriter writer)
 825   
     {
 826  54
         int count = Tapestry.size(_hiddenValues);
 827   
 
 828  54
         for (int i = 0; i < count; i++)
 829   
         {
 830  10
             HiddenValue hv = (HiddenValue) _hiddenValues.get(i);
 831   
 
 832  10
             writeHiddenField(writer, hv._name, hv._id, hv._value);
 833   
         }
 834   
     }
 835   
 }