Clover coverage report - Code Coverage for tapestry release 4.0.1
Coverage timestamp: Fri Mar 31 2006 09:12:14 EST
file stats: LOC: 744   Methods: 28
NCLOC: 395   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
FormSupportImpl.java 98.6% 97.6% 96.4% 97.7%
coverage coverage
 1    // Copyright 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.Collections;
 20    import java.util.HashMap;
 21    import java.util.HashSet;
 22    import java.util.Iterator;
 23    import java.util.List;
 24    import java.util.Map;
 25    import java.util.Set;
 26   
 27    import org.apache.hivemind.ApplicationRuntimeException;
 28    import org.apache.hivemind.HiveMind;
 29    import org.apache.hivemind.Location;
 30    import org.apache.hivemind.Resource;
 31    import org.apache.hivemind.util.ClasspathResource;
 32    import org.apache.hivemind.util.Defense;
 33    import org.apache.tapestry.IComponent;
 34    import org.apache.tapestry.IForm;
 35    import org.apache.tapestry.IMarkupWriter;
 36    import org.apache.tapestry.IRender;
 37    import org.apache.tapestry.IRequestCycle;
 38    import org.apache.tapestry.NestedMarkupWriter;
 39    import org.apache.tapestry.PageRenderSupport;
 40    import org.apache.tapestry.StaleLinkException;
 41    import org.apache.tapestry.Tapestry;
 42    import org.apache.tapestry.TapestryUtils;
 43    import org.apache.tapestry.engine.ILink;
 44    import org.apache.tapestry.services.ServiceConstants;
 45    import org.apache.tapestry.util.IdAllocator;
 46    import org.apache.tapestry.valid.IValidationDelegate;
 47   
 48    /**
 49    * Encapsulates most of the behavior of a Form component.
 50    *
 51    * @author Howard M. Lewis Ship
 52    * @since 4.0
 53    */
 54    public class FormSupportImpl implements FormSupport
 55    {
 56    /**
 57    * Name of query parameter storing the ids alloocated while rendering the form, as a comma
 58    * seperated list. This information is used when the form is submitted, to ensure that the
 59    * rewind allocates the exact same sequence of ids.
 60    */
 61   
 62    public static final String FORM_IDS = "formids";
 63   
 64    /**
 65    * Names of additional ids that were pre-reserved, as a comma-sepereated list. These are names
 66    * beyond that standard set. Certain engine services include extra parameter values that must be
 67    * accounted for, and page properties may be encoded as additional query parameters.
 68    */
 69   
 70    public static final String RESERVED_FORM_IDS = "reservedids";
 71   
 72    /**
 73    * Indicates why the form was submitted: whether for normal ("submit"), refresh, or because the
 74    * form was canceled.
 75    */
 76   
 77    public static final String SUBMIT_MODE = "submitmode";
 78   
 79    public static final String SCRIPT = "/org/apache/tapestry/form/Form.js";
 80   
 81    private final static Set _standardReservedIds;
 82   
 83    /**
 84    * Attribute set to true when a field has been focused; used to prevent conflicting JavaScript
 85    * for field focusing from being emitted.
 86    */
 87   
 88    public static final String FIELD_FOCUS_ATTRIBUTE = "org.apache.tapestry.field-focused";
 89   
 90    static
 91    {
 92  2 Set set = new HashSet();
 93   
 94  2 set.addAll(Arrays.asList(ServiceConstants.RESERVED_IDS));
 95  2 set.add(FORM_IDS);
 96  2 set.add(RESERVED_FORM_IDS);
 97  2 set.add(SUBMIT_MODE);
 98  2 set.add(FormConstants.SUBMIT_NAME_PARAMETER);
 99   
 100  2 _standardReservedIds = Collections.unmodifiableSet(set);
 101    }
 102   
 103    private final static Set _submitModes;
 104   
 105    static
 106    {
 107  2 Set set = new HashSet();
 108  2 set.add(FormConstants.SUBMIT_CANCEL);
 109  2 set.add(FormConstants.SUBMIT_NORMAL);
 110  2 set.add(FormConstants.SUBMIT_REFRESH);
 111   
 112  2 _submitModes = Collections.unmodifiableSet(set);
 113    }
 114   
 115    /**
 116    * Used when rewinding the form to figure to match allocated ids (allocated during the rewind)
 117    * against expected ids (allocated in the previous request cycle, when the form was rendered).
 118    */
 119   
 120    private int _allocatedIdIndex;
 121   
 122    /**
 123    * The list of allocated ids for form elements within this form. This list is constructed when a
 124    * form renders, and is validated against when the form is rewound.
 125    */
 126   
 127    private final List _allocatedIds = new ArrayList();
 128   
 129    private final IRequestCycle _cycle;
 130   
 131    private final IdAllocator _elementIdAllocator = new IdAllocator();
 132   
 133    private String _encodingType;
 134   
 135    private final List _deferredRunnables = new ArrayList();
 136   
 137    /**
 138    * Map keyed on extended component id, value is the pre-rendered markup for that component.
 139    */
 140   
 141    private final Map _prerenderMap = new HashMap();
 142   
 143    /**
 144    * {@link Map}, keyed on {@link FormEventType}. Values are either a String (the function name
 145    * of a single event handler), or a List of Strings (a sequence of event handler function
 146    * names).
 147    */
 148   
 149    private Map _events;
 150   
 151    private final IForm _form;
 152   
 153    private final List _hiddenValues = new ArrayList();
 154   
 155    private final boolean _rewinding;
 156   
 157    private final IMarkupWriter _writer;
 158   
 159    private final Resource _script;
 160   
 161    private final IValidationDelegate _delegate;
 162   
 163    private final PageRenderSupport _pageRenderSupport;
 164   
 165  186 public FormSupportImpl(IMarkupWriter writer, IRequestCycle cycle, IForm form)
 166    {
 167  186 Defense.notNull(writer, "writer");
 168  186 Defense.notNull(cycle, "cycle");
 169  186 Defense.notNull(form, "form");
 170   
 171  186 _writer = writer;
 172  186 _cycle = cycle;
 173  186 _form = form;
 174  186 _delegate = form.getDelegate();
 175   
 176  186 _rewinding = cycle.isRewound(form);
 177  186 _allocatedIdIndex = 0;
 178   
 179  186 _script = new ClasspathResource(cycle.getEngine().getClassResolver(), SCRIPT);
 180   
 181  186 _pageRenderSupport = TapestryUtils.getOptionalPageRenderSupport(cycle);
 182    }
 183   
 184    /**
 185    * Alternate constructor used for testing only.
 186    *
 187    * @param cycle
 188    */
 189  2 FormSupportImpl(IRequestCycle cycle)
 190    {
 191  2 _cycle = cycle;
 192  2 _form = null;
 193  2 _rewinding = false;
 194  2 _writer = null;
 195  2 _delegate = null;
 196  2 _pageRenderSupport = null;
 197  2 _script = null;
 198    }
 199   
 200    /**
 201    * Adds an event handler for the form, of the given type.
 202    */
 203   
 204  12 public void addEventHandler(FormEventType type, String functionName)
 205    {
 206  12 if (_events == null)
 207  6 _events = new HashMap();
 208   
 209  12 List functionList = (List) _events.get(type);
 210   
 211    // The value can either be a String, or a List of String. Since
 212    // it is rare for there to be more than one event handling function,
 213    // we start with just a String.
 214   
 215  12 if (functionList == null)
 216    {
 217  6 functionList = new ArrayList();
 218   
 219  6 _events.put(type, functionList);
 220    }
 221   
 222  12 functionList.add(functionName);
 223    }
 224   
 225    /**
 226    * Adds hidden fields for parameters provided by the {@link ILink}. These parameters define the
 227    * information needed to dispatch the request, plus state information. The names of these
 228    * parameters must be reserved so that conflicts don't occur that could disrupt the request
 229    * processing. For example, if the id 'page' is not reserved, then a conflict could occur with a
 230    * component whose id is 'page'. A certain number of ids are always reserved, and we find any
 231    * additional ids beyond that set.
 232    */
 233   
 234  116 private void addHiddenFieldsForLinkParameters(ILink link)
 235    {
 236  116 String[] names = link.getParameterNames();
 237  116 int count = Tapestry.size(names);
 238   
 239  116 StringBuffer extraIds = new StringBuffer();
 240  116 String sep = "";
 241  116 boolean hasExtra = false;
 242   
 243    // All the reserved ids, which are essential for
 244    // dispatching the request, are automatically reserved.
 245    // Thus, if you have a component with an id of 'service', its element id
 246    // will likely be 'service$0'.
 247   
 248  116 preallocateReservedIds();
 249   
 250  116 for (int i = 0; i < count; i++)
 251    {
 252  586 String name = names[i];
 253   
 254    // Reserve the name.
 255   
 256  586 if (!_standardReservedIds.contains(name))
 257    {
 258  24 _elementIdAllocator.allocateId(name);
 259   
 260  24 extraIds.append(sep);
 261  24 extraIds.append(name);
 262   
 263  24 sep = ",";
 264  24 hasExtra = true;
 265    }
 266   
 267  586 addHiddenFieldsForLinkParameter(link, name);
 268    }
 269   
 270  116 if (hasExtra)
 271  24 addHiddenValue(RESERVED_FORM_IDS, extraIds.toString());
 272    }
 273   
 274  686 public void addHiddenValue(String name, String value)
 275    {
 276  686 _hiddenValues.add(new HiddenFieldData(name, value));
 277    }
 278   
 279  10 public void addHiddenValue(String name, String id, String value)
 280    {
 281  10 _hiddenValues.add(new HiddenFieldData(name, id, value));
 282    }
 283   
 284    /**
 285    * Converts the allocateIds property into a string, a comma-separated list of ids. This is
 286    * included as a hidden field in the form and is used to identify discrepencies when the form is
 287    * submitted.
 288    */
 289   
 290  106 private String buildAllocatedIdList()
 291    {
 292  106 StringBuffer buffer = new StringBuffer();
 293  106 int count = _allocatedIds.size();
 294   
 295  106 for (int i = 0; i < count; i++)
 296    {
 297  122 if (i > 0)
 298  46 buffer.append(',');
 299   
 300  122 buffer.append(_allocatedIds.get(i));
 301    }
 302   
 303  106 return buffer.toString();
 304    }
 305   
 306  106 private void emitEventHandlers(String formId)
 307    {
 308  106 if (_events == null || _events.isEmpty())
 309  100 return;
 310   
 311  6 StringBuffer buffer = new StringBuffer();
 312   
 313  6 Iterator i = _events.entrySet().iterator();
 314   
 315  6 while (i.hasNext())
 316    {
 317  6 Map.Entry entry = (Map.Entry) i.next();
 318  6 FormEventType type = (FormEventType) entry.getKey();
 319  6 Object value = entry.getValue();
 320   
 321  6 buffer.append("Tapestry.");
 322  6 buffer.append(type.getAddHandlerFunctionName());
 323  6 buffer.append("('");
 324  6 buffer.append(formId);
 325  6 buffer.append("', function (event)\n{");
 326   
 327  6 List l = (List) value;
 328  6 int count = l.size();
 329   
 330  6 for (int j = 0; j < count; j++)
 331    {
 332  12 String functionName = (String) l.get(j);
 333   
 334  12 if (j > 0)
 335    {
 336  6 buffer.append(";");
 337    }
 338   
 339  12 buffer.append("\n ");
 340  12 buffer.append(functionName);
 341   
 342    // It's supposed to be function names, but some of Paul's validation code
 343    // adds inline code to be executed instead.
 344   
 345  12 if (!functionName.endsWith(")"))
 346    {
 347  10 buffer.append("()");
 348    }
 349    }
 350   
 351  6 buffer.append(";\n});\n");
 352    }
 353   
 354    // TODO: If PRS is null ...
 355   
 356  6 _pageRenderSupport.addInitializationScript(buffer.toString());
 357    }
 358   
 359    /**
 360    * Constructs a unique identifier (within the Form). The identifier consists of the component's
 361    * id, with an index number added to ensure uniqueness.
 362    * <p>
 363    * Simply invokes
 364    * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the
 365    * component's id.
 366    */
 367   
 368  40 public String getElementId(IFormComponent component)
 369    {
 370  40 return getElementId(component, component.getId());
 371    }
 372   
 373    /**
 374    * Constructs a unique identifier (within the Form). The identifier consists of the component's
 375    * id, with an index number added to ensure uniqueness.
 376    * <p>
 377    * Simply invokes
 378    * {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}with the
 379    * component's id.
 380    */
 381   
 382  232 public String getElementId(IFormComponent component, String baseId)
 383    {
 384    // $ is not a valid character in an XML/XHTML id, so convert it to an underscore.
 385   
 386  232 String filteredId = TapestryUtils.convertTapestryIdToNMToken(baseId);
 387   
 388  232 String result = _elementIdAllocator.allocateId(filteredId);
 389   
 390  232 if (_rewinding)
 391    {
 392  106 if (_allocatedIdIndex >= _allocatedIds.size())
 393    {
 394  2 throw new StaleLinkException(FormMessages.formTooManyIds(_form, _allocatedIds
 395    .size(), component), component);
 396    }
 397   
 398  104 String expected = (String) _allocatedIds.get(_allocatedIdIndex);
 399   
 400  104 if (!result.equals(expected))
 401  4 throw new StaleLinkException(FormMessages.formIdMismatch(
 402    _form,
 403    _allocatedIdIndex,
 404    expected,
 405    result,
 406    component), component);
 407    }
 408    else
 409    {
 410  126 _allocatedIds.add(result);
 411    }
 412   
 413  226 _allocatedIdIndex++;
 414   
 415  226 component.setName(result);
 416   
 417  226 return result;
 418    }
 419   
 420  308 public boolean isRewinding()
 421    {
 422  308 return _rewinding;
 423    }
 424   
 425  184 private void preallocateReservedIds()
 426    {
 427  184 for (int i = 0; i < ServiceConstants.RESERVED_IDS.length; i++)
 428  1104 _elementIdAllocator.allocateId(ServiceConstants.RESERVED_IDS[i]);
 429    }
 430   
 431    /**
 432    * Invoked when rewinding a form to re-initialize the _allocatedIds and _elementIdAllocator.
 433    * Converts a string passed as a parameter (and containing a comma separated list of ids) back
 434    * into the allocateIds property. In addition, return the state of the ID allocater back to
 435    * where it was at the start of the render.
 436    *
 437    * @see #buildAllocatedIdList()
 438    * @since 3.0
 439    */
 440   
 441  68 private void reinitializeIdAllocatorForRewind()
 442    {
 443  68 String allocatedFormIds = _cycle.getParameter(FORM_IDS);
 444   
 445  68 String[] ids = TapestryUtils.split(allocatedFormIds);
 446   
 447  68 for (int i = 0; i < ids.length; i++)
 448  114 _allocatedIds.add(ids[i]);
 449   
 450    // Now, reconstruct the the initial state of the
 451    // id allocator.
 452   
 453  68 preallocateReservedIds();
 454   
 455  68 String extraReservedIds = _cycle.getParameter(RESERVED_FORM_IDS);
 456   
 457  68 ids = TapestryUtils.split(extraReservedIds);
 458   
 459  68 for (int i = 0; i < ids.length; i++)
 460  6 _elementIdAllocator.allocateId(ids[i]);
 461    }
 462   
 463  116 public void render(String method, IRender informalParametersRenderer, ILink link,
 464    String scheme, Integer port)
 465    {
 466  116 String formId = _form.getName();
 467   
 468  116 emitEventManagerInitialization(formId);
 469   
 470    // Convert the link's query parameters into a series of
 471    // hidden field values (that will be rendered later).
 472   
 473  116 addHiddenFieldsForLinkParameters(link);
 474   
 475    // Create a hidden field to store the submission mode, in case
 476    // client-side JavaScript forces an update.
 477   
 478  116 addHiddenValue(SUBMIT_MODE, null);
 479   
 480    // And another for the name of the component that
 481    // triggered the submit.
 482   
 483  116 addHiddenValue(FormConstants.SUBMIT_NAME_PARAMETER, null);
 484   
 485  116 IMarkupWriter nested = _writer.getNestedWriter();
 486   
 487  116 _form.renderBody(nested, _cycle);
 488   
 489  106 runDeferredRunnables();
 490   
 491  106 int portI = (port == null) ? 0 : port.intValue();
 492  106 writeTag(_writer, method, link.getURL(scheme, null, portI, null, false));
 493   
 494    // For HTML compatibility
 495  106 _writer.attribute("name", formId);
 496   
 497    // For XHTML compatibility
 498  106 _writer.attribute("id", formId);
 499   
 500  106 if (_encodingType != null)
 501  6 _writer.attribute("enctype", _encodingType);
 502   
 503    // Write out event handlers collected during the rendering.
 504   
 505  106 emitEventHandlers(formId);
 506   
 507  106 informalParametersRenderer.render(_writer, _cycle);
 508   
 509    // Finish the <form> tag
 510   
 511  106 _writer.println();
 512   
 513  106 writeHiddenFields();
 514   
 515    // Close the nested writer, inserting its contents.
 516   
 517  106 nested.close();
 518   
 519    // Close the <form> tag.
 520   
 521  106 _writer.end();
 522   
 523  106 String fieldId = _delegate.getFocusField();
 524   
 525  106 if (fieldId == null || _pageRenderSupport == null)
 526  84 return;
 527   
 528    // If the form doesn't support focus, or the focus has already been set by a different form,
 529    // then do nothing.
 530   
 531  22 if (!_form.getFocus() || _cycle.getAttribute(FIELD_FOCUS_ATTRIBUTE) != null)
 532  4 return;
 533   
 534  18 _pageRenderSupport.addInitializationScript("Tapestry.set_focus('" + fieldId + "');");
 535   
 536  18 _cycle.setAttribute(FIELD_FOCUS_ATTRIBUTE, Boolean.TRUE);
 537    }
 538   
 539    /**
 540    * Pre-renders the form, setting up some client-side form support. Returns the name of the
 541    * client-side form event manager variable.
 542    */
 543  116 protected void emitEventManagerInitialization(String formId)
 544    {
 545  116 if (_pageRenderSupport == null)
 546  16 return;
 547   
 548  100 _pageRenderSupport.addExternalScript(_script);
 549   
 550  100 _pageRenderSupport.addInitializationScript("Tapestry.register_form('" + formId + "');");
 551    }
 552   
 553  70 public String rewind()
 554    {
 555  70 _form.getDelegate().clear();
 556   
 557  70 String mode = _cycle.getParameter(SUBMIT_MODE);
 558   
 559    // On a cancel, don't bother rendering the body or anything else at all.
 560   
 561  70 if (FormConstants.SUBMIT_CANCEL.equals(mode))
 562  2 return mode;
 563   
 564  68 reinitializeIdAllocatorForRewind();
 565   
 566  68 _form.renderBody(_writer, _cycle);
 567   
 568  60 int expected = _allocatedIds.size();
 569   
 570    // The other case, _allocatedIdIndex > expected, is
 571    // checked for inside getElementId(). Remember that
 572    // _allocatedIdIndex is incremented after allocating.
 573   
 574  60 if (_allocatedIdIndex < expected)
 575    {
 576  2 String nextExpectedId = (String) _allocatedIds.get(_allocatedIdIndex);
 577   
 578  2 throw new StaleLinkException(FormMessages.formTooFewIds(_form, expected
 579    - _allocatedIdIndex, nextExpectedId), _form);
 580    }
 581   
 582  58 runDeferredRunnables();
 583   
 584  58 if (_submitModes.contains(mode))
 585  10 return mode;
 586   
 587    // Either something wacky on the client side, or a client without
 588    // javascript enabled.
 589   
 590  48 return FormConstants.SUBMIT_NORMAL;
 591   
 592    }
 593   
 594  164 private void runDeferredRunnables()
 595    {
 596  164 Iterator i = _deferredRunnables.iterator();
 597  164 while (i.hasNext())
 598    {
 599  4 Runnable r = (Runnable) i.next();
 600   
 601  4 r.run();
 602    }
 603    }
 604   
 605  10 public void setEncodingType(String encodingType)
 606    {
 607   
 608  10 if (_encodingType != null && !_encodingType.equals(encodingType))
 609  2 throw new ApplicationRuntimeException(FormMessages.encodingTypeContention(
 610    _form,
 611    _encodingType,
 612    encodingType), _form, null, null);
 613   
 614  8 _encodingType = encodingType;
 615    }
 616   
 617    /**
 618    * Overwridden by {@link org.apache.tapestry.wml.GoFormSupportImpl} (WML).
 619    */
 620  664 protected void writeHiddenField(IMarkupWriter writer, String name, String id, String value)
 621    {
 622  664 writer.beginEmpty("input");
 623  664 writer.attribute("type", "hidden");
 624  664 writer.attribute("name", name);
 625   
 626  664 if (HiveMind.isNonBlank(id))
 627  4 writer.attribute("id", id);
 628   
 629  664 writer.attribute("value", value == null ? "" : value);
 630  664 writer.println();
 631    }
 632   
 633  748 private void writeHiddenField(String name, String id, String value)
 634    {
 635  748 writeHiddenField(_writer, name, id, value);
 636    }
 637   
 638    /**
 639    * Writes out all hidden values previously added by
 640    * {@link #addHiddenValue(String, String, String)}. Writes a &lt;div&gt; tag around
 641    * {@link #writeHiddenFieldList()}. Overriden by
 642    * {@link org.apache.tapestry.wml.GoFormSupportImpl}.
 643    */
 644   
 645  94 protected void writeHiddenFields()
 646    {
 647  94 _writer.begin("div");
 648  94 _writer.attribute("style", "display:none;");
 649   
 650  94 writeHiddenFieldList();
 651   
 652  94 _writer.end();
 653    }
 654   
 655    /**
 656    * Writes out all hidden values previously added by
 657    * {@link #addHiddenValue(String, String, String)}, plus the allocated id list.
 658    */
 659   
 660  106 protected void writeHiddenFieldList()
 661    {
 662  106 writeHiddenField(FORM_IDS, null, buildAllocatedIdList());
 663   
 664  106 Iterator i = _hiddenValues.iterator();
 665  106 while (i.hasNext())
 666    {
 667  642 HiddenFieldData data = (HiddenFieldData) i.next();
 668   
 669  642 writeHiddenField(data.getName(), data.getId(), data.getValue());
 670    }
 671    }
 672   
 673  586 private void addHiddenFieldsForLinkParameter(ILink link, String parameterName)
 674    {
 675  586 String[] values = link.getParameterValues(parameterName);
 676   
 677    // In some cases, there are no values, but a space is "reserved" for the provided name.
 678   
 679  586 if (values == null)
 680  218 return;
 681   
 682  368 for (int i = 0; i < values.length; i++)
 683    {
 684  368 addHiddenValue(parameterName, values[i]);
 685    }
 686    }
 687   
 688  94 protected void writeTag(IMarkupWriter writer, String method, String url)
 689    {
 690  94 writer.begin("form");
 691  94 writer.attribute("method", method);
 692  94 writer.attribute("action", url);
 693    }
 694   
 695  4 public void prerenderField(IMarkupWriter writer, IComponent field, Location location)
 696    {
 697  4 Defense.notNull(writer, "writer");
 698  4 Defense.notNull(field, "field");
 699   
 700  4 String key = field.getExtendedId();
 701   
 702  4 if (_prerenderMap.containsKey(key))
 703  2 throw new ApplicationRuntimeException(FormMessages.fieldAlreadyPrerendered(field),
 704    field, location, null);
 705   
 706  2 NestedMarkupWriter nested = writer.getNestedWriter();
 707   
 708  2 field.render(nested, _cycle);
 709   
 710  2 _prerenderMap.put(key, nested.getBuffer());
 711    }
 712   
 713  150 public boolean wasPrerendered(IMarkupWriter writer, IComponent field)
 714    {
 715  150 String key = field.getExtendedId();
 716   
 717    // During a rewind, if the form is pre-rendered, the buffer will be null,
 718    // so do the check based on the key, not a non-null value.
 719   
 720  150 if (!_prerenderMap.containsKey(key))
 721  150 return false;
 722   
 723  0 String buffer = (String) _prerenderMap.get(key);
 724   
 725  0 writer.printRaw(buffer);
 726   
 727  0 _prerenderMap.remove(key);
 728   
 729  0 return true;
 730    }
 731   
 732  4 public void addDeferredRunnable(Runnable runnable)
 733    {
 734  4 Defense.notNull(runnable, "runnable");
 735   
 736  4 _deferredRunnables.add(runnable);
 737    }
 738   
 739  0 public void registerForFocus(IFormComponent field, int priority)
 740    {
 741  0 _delegate.registerForFocus(field, priority);
 742    }
 743   
 744    }