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