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