Clover coverage report - Code Coverage for tapestry release 4.0-beta-12
Coverage timestamp: Sun Oct 30 2005 16:22:01 EST
file stats: LOC: 688   Methods: 33
NCLOC: 362   Classes: 6
 
 Source file Conditionals Statements Methods TOTAL
ForBean.java 73.7% 81% 72.7% 78%
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.components;
 16   
 17    import java.util.ArrayList;
 18    import java.util.Collections;
 19    import java.util.HashMap;
 20    import java.util.Iterator;
 21    import java.util.List;
 22    import java.util.Map;
 23   
 24    import org.apache.tapestry.IBinding;
 25    import org.apache.tapestry.IForm;
 26    import org.apache.tapestry.IMarkupWriter;
 27    import org.apache.tapestry.IRequestCycle;
 28    import org.apache.tapestry.Tapestry;
 29    import org.apache.tapestry.TapestryUtils;
 30    import org.apache.tapestry.coerce.ValueConverter;
 31    import org.apache.tapestry.form.AbstractFormComponent;
 32    import org.apache.tapestry.services.DataSqueezer;
 33    import org.apache.tapestry.services.ExpressionEvaluator;
 34   
 35    /**
 36    * @author mb
 37    * @since 4.0
 38    * @see org.apache.tapestry.components.IPrimaryKeyConverter
 39    * @see org.apache.tapestry.util.DefaultPrimaryKeyConverter
 40    */
 41    public abstract class ForBean extends AbstractFormComponent
 42    {
 43    // constants
 44   
 45    /**
 46    * Prefix on the hidden value stored into the field to indicate the the actual value is stored
 47    * (this is used when there is no primary key converter). The remainder of the string is a
 48    * {@link DataSqueezer squeezed} representation of the value.
 49    */
 50    private static final char DESC_VALUE = 'V';
 51   
 52    /**
 53    * Prefix on the hidden value stored into the field that indicates the primary key of the
 54    * iterated value is stored; the remainder of the string is a {@link DataSqueezer squeezed}
 55    * representation of the primary key. The {@link IPrimaryKeyConverter converter} is used to
 56    * obtain the value from this key.
 57    */
 58    private static final char DESC_PRIMARY_KEY = 'P';
 59   
 60    private final RepSource COMPLETE_REP_SOURCE = new CompleteRepSource();
 61   
 62    private final RepSource KEY_EXPRESSION_REP_SOURCE = new KeyExpressionRepSource();
 63   
 64    // parameters
 65    public abstract String getElement();
 66   
 67    public abstract String getKeyExpression();
 68   
 69    public abstract IPrimaryKeyConverter getConverter();
 70   
 71    public abstract Object getDefaultValue();
 72   
 73    public abstract boolean getMatch();
 74   
 75    public abstract boolean getVolatile();
 76   
 77    // injects
 78    public abstract DataSqueezer getDataSqueezer();
 79   
 80    public abstract ValueConverter getValueConverter();
 81   
 82    public abstract ExpressionEvaluator getExpressionEvaluator();
 83   
 84    // intermediate members
 85    private Object _value;
 86   
 87    private int _index;
 88   
 89    private boolean _rendering;
 90   
 91    /**
 92    * Gets the source binding and iterates through its values. For each, it updates the value
 93    * binding and render's its wrapped elements.
 94    */
 95  21 protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
 96    {
 97    // form may be null if component is not located in a form
 98  21 IForm form = (IForm) cycle.getAttribute(TapestryUtils.FORM_ATTRIBUTE);
 99   
 100    // If the cycle is rewinding, but not this particular form,
 101    // then do nothing (don't even render the body).
 102  21 boolean cycleRewinding = cycle.isRewinding();
 103  21 if (cycleRewinding && form != null && !form.isRewinding())
 104  0 return;
 105   
 106    // Get the data to be iterated upon. Store in form if needed.
 107  21 Iterator dataSource = getData(cycle, form);
 108   
 109    // Do not iterate if dataSource is null.
 110    // The dataSource was either not convertable to Iterator, or was empty.
 111  21 if (dataSource == null)
 112  0 return;
 113   
 114  21 String element = getElement();
 115   
 116    // Perform the iterations
 117  21 try
 118    {
 119  21 _index = 0;
 120  21 _rendering = true;
 121   
 122  21 while (dataSource.hasNext())
 123    {
 124    // Get current value
 125  57 _value = dataSource.next();
 126   
 127    // Update output component parameters
 128  57 updateOutputParameters();
 129   
 130    // Render component
 131  57 if (element != null)
 132    {
 133  22 writer.begin(element);
 134  22 renderInformalParameters(writer, cycle);
 135    }
 136   
 137  57 renderBody(writer, cycle);
 138   
 139  57 if (element != null)
 140  22 writer.end();
 141   
 142  57 _index++;
 143    }
 144    }
 145    finally
 146    {
 147  21 _rendering = false;
 148  21 _value = null;
 149    }
 150    }
 151   
 152    /**
 153    * Returns the most recent value extracted from the source parameter.
 154    *
 155    * @throws org.apache.tapestry.ApplicationRuntimeException
 156    * if the For is not currently rendering.
 157    */
 158   
 159  46 public final Object getValue()
 160    {
 161  46 if (!_rendering)
 162  0 throw Tapestry.createRenderOnlyPropertyException(this, "value");
 163   
 164  46 return _value;
 165    }
 166   
 167    /**
 168    * The index number, within the {@link #getSource() source}, of the the current value.
 169    *
 170    * @throws org.apache.tapestry.ApplicationRuntimeException
 171    * if the For is not currently rendering.
 172    */
 173   
 174  46 public int getIndex()
 175    {
 176  46 if (!_rendering)
 177  0 throw Tapestry.createRenderOnlyPropertyException(this, "index");
 178   
 179  46 return _index;
 180    }
 181   
 182  0 public boolean isDisabled()
 183    {
 184  0 return false;
 185    }
 186   
 187    /**
 188    * Updates the index and value output parameters if bound.
 189    */
 190  57 protected void updateOutputParameters()
 191    {
 192  57 IBinding indexBinding = getBinding("index");
 193  57 if (indexBinding != null)
 194  31 indexBinding.setObject(new Integer(_index));
 195   
 196  57 IBinding valueBinding = getBinding("value");
 197  57 if (valueBinding != null)
 198  31 valueBinding.setObject(_value);
 199    }
 200   
 201    /**
 202    * Updates the primaryKeys parameter if bound.
 203    */
 204  5 protected void updatePrimaryKeysParameter(String[] stringReps)
 205    {
 206  5 IBinding primaryKeysBinding = getBinding("primaryKeys");
 207  5 if (primaryKeysBinding == null)
 208  5 return;
 209   
 210  0 DataSqueezer squeezer = getDataSqueezer();
 211   
 212  0 int repsCount = stringReps.length;
 213  0 List primaryKeys = new ArrayList(repsCount);
 214  0 for (int i = 0; i < stringReps.length; i++)
 215    {
 216  0 String rep = stringReps[i];
 217  0 if (rep.length() == 0 || rep.charAt(0) != DESC_PRIMARY_KEY)
 218  0 continue;
 219  0 Object primaryKey = squeezer.unsqueeze(rep.substring(1));
 220  0 primaryKeys.add(primaryKey);
 221    }
 222   
 223  0 primaryKeysBinding.setObject(primaryKeys);
 224    }
 225   
 226    // Do nothing in those methods, but make the JVM happy
 227  0 protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
 228    {
 229    }
 230   
 231  0 protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
 232    {
 233    }
 234   
 235    /**
 236    * Returns a list with the values to be iterated upon. The list is obtained in different ways: -
 237    * If the component is not located in a form or 'volatile' is set to true, then the simply the
 238    * values passed to 'source' are returned (same as Foreach) - If the component is in a form, and
 239    * the form is rewinding, the values stored in the form are returned -- rewind is then always
 240    * the same as render. - If the component is in a form, and the form is being rendered, the
 241    * values are stored in the form as Hidden fields.
 242    *
 243    * @param cycle
 244    * The current request cycle
 245    * @param form
 246    * The form within which the component is located (if any)
 247    * @return An iterator with the values to be cycled upon
 248    */
 249  21 private Iterator getData(IRequestCycle cycle, IForm form)
 250    {
 251  21 if (form == null || getVolatile())
 252  6 return evaluateSourceIterator();
 253   
 254  15 String name = form.getElementId(this);
 255  15 if (cycle.isRewinding())
 256  5 return getStoredData(cycle, name);
 257  10 return storeSourceData(form, name);
 258    }
 259   
 260    /**
 261    * Returns a list of the values stored as Hidden fields in the form. A conversion is performed
 262    * if the primary key of the value is stored.
 263    *
 264    * @param cycle
 265    * The current request cycle
 266    * @param name
 267    * The name of the HTTP parameter whether the values
 268    * @return an iterator with the values stored in the provided Hidden fields
 269    */
 270  5 protected Iterator getStoredData(IRequestCycle cycle, String name)
 271    {
 272  5 String[] stringReps = cycle.getParameters(name);
 273  5 if (stringReps == null)
 274  0 return null;
 275   
 276  5 updatePrimaryKeysParameter(stringReps);
 277   
 278  5 return new ReadSourceDataIterator(stringReps);
 279    }
 280   
 281    /**
 282    * Pulls data from successive strings (posted by client-side hidden fields); each string
 283    * representation may be either a value or a primary key.
 284    */
 285    private class ReadSourceDataIterator implements Iterator
 286    {
 287    private final Iterator _sourceIterator = evaluateSourceIterator();
 288   
 289    private final Iterator _fullSourceIterator = evaluateFullSourceIterator();
 290   
 291    private final String[] _stringReps;
 292   
 293    private int _index = 0;
 294   
 295    private final Map _repToValueMap = new HashMap();
 296   
 297  5 ReadSourceDataIterator(String[] stringReps)
 298    {
 299  5 _stringReps = stringReps;
 300    }
 301   
 302  16 public boolean hasNext()
 303    {
 304  16 return _index < _stringReps.length;
 305    }
 306   
 307  11 public Object next()
 308    {
 309  11 String rep = _stringReps[_index++];
 310   
 311  11 return getValueFromStringRep(_sourceIterator, _fullSourceIterator, _repToValueMap, rep);
 312    }
 313   
 314  0 public void remove()
 315    {
 316  0 throw new UnsupportedOperationException("remove()");
 317    }
 318   
 319    }
 320   
 321    /**
 322    * Stores the provided data in the form and then returns the data as an iterator. If the primary
 323    * key of the value can be determined, then that primary key is saved instead.
 324    *
 325    * @param form
 326    * The form where the data will be stored
 327    * @param name
 328    * The name under which the data will be stored
 329    * @return an iterator with the bound values stored in the form
 330    */
 331  10 protected Iterator storeSourceData(IForm form, String name)
 332    {
 333  10 return new StoreSourceDataIterator(form, name, evaluateSourceIterator());
 334    }
 335   
 336    /**
 337    * Iterates over a set of values, using {@link ForBean#getStringRepFromValue(Object)} to obtain
 338    * the correct client-side string representation, and working with the form to store each
 339    * successive value into the form.
 340    */
 341    private class StoreSourceDataIterator implements Iterator
 342    {
 343    private final IForm _form;
 344   
 345    private final String _name;
 346   
 347    private final Iterator _delegate;
 348   
 349  10 StoreSourceDataIterator(IForm form, String name, Iterator delegate)
 350    {
 351  10 _form = form;
 352  10 _name = name;
 353  10 _delegate = delegate;
 354    }
 355   
 356  30 public boolean hasNext()
 357    {
 358  30 return _delegate.hasNext();
 359    }
 360   
 361  20 public Object next()
 362    {
 363  20 Object value = _delegate.next();
 364   
 365  20 String rep = getStringRepFromValue(value);
 366   
 367  20 _form.addHiddenValue(_name, rep);
 368   
 369  20 return value;
 370    }
 371   
 372  0 public void remove()
 373    {
 374  0 throw new UnsupportedOperationException("remove()");
 375    }
 376    }
 377   
 378    /**
 379    * Returns the string representation of the value. The first letter of the string representation
 380    * shows whether a value or a primary key is being described.
 381    *
 382    * @param value
 383    * @return
 384    */
 385  71 protected String getStringRepFromValue(Object value)
 386    {
 387  71 String rep;
 388  71 DataSqueezer squeezer = getDataSqueezer();
 389   
 390    // try to extract the primary key from the value
 391  71 Object pk = getPrimaryKeyFromValue(value);
 392  71 if (pk != null)
 393    // Primary key was extracted successfully.
 394  50 rep = DESC_PRIMARY_KEY + squeezer.squeeze(pk);
 395    else
 396    // primary key could not be extracted. squeeze value.
 397  21 rep = DESC_VALUE + squeezer.squeeze(value);
 398   
 399  71 return rep;
 400    }
 401   
 402    /**
 403    * Returns the primary key of the given value. Uses the 'keyExpression' or the 'converter' (if
 404    * either is provided).
 405    *
 406    * @param value
 407    * The value from which the primary key should be extracted
 408    * @return The primary key of the value, or null if such cannot be extracted.
 409    */
 410  71 protected Object getPrimaryKeyFromValue(Object value)
 411    {
 412  71 if (value == null)
 413  0 return null;
 414   
 415  71 Object primaryKey = getKeyExpressionFromValue(value);
 416  71 if (primaryKey == null)
 417  21 primaryKey = getConverterFromValue(value);
 418   
 419  71 return primaryKey;
 420    }
 421   
 422    /**
 423    * Uses the 'keyExpression' parameter to determine the primary key of the given value
 424    *
 425    * @param value
 426    * The value from which the primary key should be extracted
 427    * @return The primary key of the value as defined by 'keyExpression', or null if such cannot be
 428    * extracted.
 429    */
 430  71 protected Object getKeyExpressionFromValue(Object value)
 431    {
 432  71 String keyExpression = getKeyExpression();
 433  71 if (keyExpression == null)
 434  21 return null;
 435   
 436  50 Object primaryKey = getExpressionEvaluator().read(value, keyExpression);
 437  50 return primaryKey;
 438    }
 439   
 440    /**
 441    * Uses the 'converter' parameter to determine the primary key of the given value
 442    *
 443    * @param value
 444    * The value from which the primary key should be extracted
 445    * @return The primary key of the value as provided by the converter, or null if such cannot be
 446    * extracted.
 447    */
 448  21 protected Object getConverterFromValue(Object value)
 449    {
 450  21 IPrimaryKeyConverter converter = getConverter();
 451  21 if (converter == null)
 452  21 return null;
 453   
 454  0 Object primaryKey = converter.getPrimaryKey(value);
 455  0 return primaryKey;
 456    }
 457   
 458    /**
 459    * Determines the value that corresponds to the given string representation. If the 'match'
 460    * parameter is true, attempt to find a value in 'source' or 'fullSource' that generates the
 461    * same string representation. Otherwise, create a new value from the string representation.
 462    *
 463    * @param rep
 464    * the string representation for which a value should be returned
 465    * @return the value that corresponds to the provided string representation
 466    */
 467  11 protected Object getValueFromStringRep(Iterator sourceIterator, Iterator fullSourceIterator,
 468    Map repToValueMap, String rep)
 469    {
 470  11 Object value = null;
 471  11 DataSqueezer squeezer = getDataSqueezer();
 472   
 473    // Check if the string rep is empty. If so, just return the default value.
 474  11 if (rep == null || rep.length() == 0)
 475  0 return getDefaultValue();
 476   
 477    // If required, find a value with an equivalent string representation and return it
 478  11 boolean match = getMatch();
 479  11 if (match)
 480    {
 481  9 value = findValueWithStringRep(
 482    sourceIterator,
 483    fullSourceIterator,
 484    repToValueMap,
 485    rep,
 486    COMPLETE_REP_SOURCE);
 487  9 if (value != null)
 488  7 return value;
 489    }
 490   
 491    // Matching of the string representation was not successful or was disabled.
 492    // Use the standard approaches to obtain the value from the rep.
 493  4 char desc = rep.charAt(0);
 494  4 String squeezed = rep.substring(1);
 495  4 switch (desc)
 496    {
 497  3 case DESC_VALUE:
 498    // If the string rep is just the value itself, unsqueeze it
 499  3 value = squeezer.unsqueeze(squeezed);
 500  3 break;
 501   
 502  1 case DESC_PRIMARY_KEY:
 503    // Perform keyExpression match if not already attempted
 504  1 if (!match && getKeyExpression() != null)
 505  0 value = findValueWithStringRep(
 506    sourceIterator,
 507    fullSourceIterator,
 508    repToValueMap,
 509    rep,
 510    KEY_EXPRESSION_REP_SOURCE);
 511   
 512    // If 'converter' is defined, try to perform conversion from primary key to value
 513  1 if (value == null)
 514    {
 515  1 IPrimaryKeyConverter converter = getConverter();
 516  1 if (converter != null)
 517    {
 518  0 Object pk = squeezer.unsqueeze(squeezed);
 519  0 value = converter.getValue(pk);
 520    }
 521    }
 522  1 break;
 523    }
 524   
 525  4 if (value == null)
 526  1 value = getDefaultValue();
 527   
 528  4 return value;
 529    }
 530   
 531    /**
 532    * Attempt to find a value in 'source' or 'fullSource' that generates the provided string
 533    * representation. Use the RepSource interface to determine what the string representation of a
 534    * particular value is.
 535    *
 536    * @param rep
 537    * the string representation for which a value should be returned
 538    * @param repSource
 539    * an interface providing the string representation of a given value
 540    * @return the value in 'source' or 'fullSource' that corresponds to the provided string
 541    * representation
 542    */
 543  9 protected Object findValueWithStringRep(Iterator sourceIterator, Iterator fullSourceIterator,
 544    Map repToValueMap, String rep, RepSource repSource)
 545    {
 546  9 Object value = repToValueMap.get(rep);
 547  9 if (value != null)
 548  1 return value;
 549   
 550  8 value = findValueWithStringRepInIterator(sourceIterator, repToValueMap, rep, repSource);
 551  8 if (value != null)
 552  4 return value;
 553   
 554  4 value = findValueWithStringRepInIterator(fullSourceIterator, repToValueMap, rep, repSource);
 555  4 return value;
 556    }
 557   
 558    /**
 559    * Attempt to find a value in the provided collection that generates the required string
 560    * representation. Use the RepSource interface to determine what the string representation of a
 561    * particular value is.
 562    *
 563    * @param rep
 564    * the string representation for which a value should be returned
 565    * @param repSource
 566    * an interface providing the string representation of a given value
 567    * @param it
 568    * the iterator of the collection in which a value should be searched
 569    * @return the value in the provided collection that corresponds to the required string
 570    * representation
 571    */
 572  12 protected Object findValueWithStringRepInIterator(Iterator it, Map repToValueMap, String rep,
 573    RepSource repSource)
 574    {
 575  12 while (it.hasNext())
 576    {
 577  51 Object sourceValue = it.next();
 578  51 if (sourceValue == null)
 579  0 continue;
 580   
 581  51 String sourceRep = repSource.getStringRep(sourceValue);
 582  51 repToValueMap.put(sourceRep, sourceValue);
 583   
 584  51 if (rep.equals(sourceRep))
 585  6 return sourceValue;
 586    }
 587   
 588  6 return null;
 589    }
 590   
 591    /**
 592    * Returns a new iterator of the values in 'source'.
 593    *
 594    * @return the 'source' iterator
 595    */
 596  21 protected Iterator evaluateSourceIterator()
 597    {
 598  21 Iterator it = null;
 599  21 Object source = null;
 600   
 601  21 IBinding sourceBinding = getBinding("source");
 602  21 if (sourceBinding != null)
 603  21 source = sourceBinding.getObject();
 604   
 605  21 if (source != null)
 606  21 it = (Iterator) getValueConverter().coerceValue(source, Iterator.class);
 607   
 608  21 if (it == null)
 609  0 it = Collections.EMPTY_LIST.iterator();
 610   
 611  21 return it;
 612    }
 613   
 614    /**
 615    * Returns a new iterator of the values in 'fullSource'.
 616    *
 617    * @return the 'fullSource' iterator
 618    */
 619  5 protected Iterator evaluateFullSourceIterator()
 620    {
 621  5 Iterator it = null;
 622  5 Object fullSource = null;
 623   
 624  5 IBinding fullSourceBinding = getBinding("fullSource");
 625  5 if (fullSourceBinding != null)
 626  2 fullSource = fullSourceBinding.getObject();
 627   
 628  5 if (fullSource != null)
 629  2 it = (Iterator) getValueConverter().coerceValue(fullSource, Iterator.class);
 630   
 631  5 if (it == null)
 632  3 it = Collections.EMPTY_LIST.iterator();
 633   
 634  5 return it;
 635    }
 636   
 637    /**
 638    * An interface that provides the string representation of a given value
 639    */
 640    protected interface RepSource
 641    {
 642    String getStringRep(Object value);
 643    }
 644   
 645    /**
 646    * An implementation of RepSource that provides the string representation of the given value
 647    * using all methods.
 648    */
 649    protected class CompleteRepSource implements RepSource
 650    {
 651  51 public String getStringRep(Object value)
 652    {
 653  51 return getStringRepFromValue(value);
 654    }
 655    }
 656   
 657    /**
 658    * An implementation of RepSource that provides the string representation of the given value
 659    * using just the 'keyExpression' parameter.
 660    */
 661    protected class KeyExpressionRepSource implements RepSource
 662    {
 663  0 public String getStringRep(Object value)
 664    {
 665  0 Object pk = getKeyExpressionFromValue(value);
 666  0 return DESC_PRIMARY_KEY + getDataSqueezer().squeeze(pk);
 667    }
 668    }
 669   
 670    /**
 671    * For component can not take focus.
 672    */
 673  0 protected boolean getCanTakeFocus()
 674    {
 675  0 return false;
 676    }
 677   
 678  0 public String getClientId()
 679    {
 680  0 return null;
 681    }
 682   
 683  0 public String getDisplayName()
 684    {
 685  0 return null;
 686    }
 687   
 688    }