Clover coverage report - Code Coverage for tapestry release 4.0-beta-6
Coverage timestamp: Wed Sep 7 2005 18:41:34 EDT
file stats: LOC: 579   Methods: 25
NCLOC: 293   Classes: 4
 
 Source file Conditionals Statements Methods TOTAL
ForBean.java 75.7% 81.7% 72% 79.1%
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   
 261  5 updatePrimaryKeysParameter(stringReps);
 262   
 263  5 int valueCount = stringReps.length;
 264  5 List values = new ArrayList(valueCount);
 265  5 for (int i = 0; i < valueCount; i++) {
 266  11 String rep = stringReps[i];
 267  11 Object value = getValueFromStringRep(rep);
 268  11 values.add(value);
 269    }
 270   
 271  5 return values.iterator();
 272    }
 273   
 274    /**
 275    * Stores the provided data in the form and then returns the data as an iterator.
 276    * If the primary key of the value can be determined,
 277    * then that primary key is saved instead.
 278    *
 279    * @param form The form where the data will be stored
 280    * @param name The name under which the data will be stored
 281    * @return an iterator with the bound values stored in the form
 282    **/
 283  10 protected Iterator storeSourceData(IForm form, String name)
 284    {
 285  10 List values = new ArrayList();
 286   
 287  10 Iterator it = getSourceIteratorValue();
 288  10 while (it.hasNext()) {
 289  20 Object value = it.next();
 290  20 values.add(value);
 291   
 292  20 String rep = getStringRepFromValue(value);
 293  20 form.addHiddenValue(name, rep);
 294    }
 295   
 296  10 return values.iterator();
 297    }
 298   
 299   
 300    /**
 301    * Returns the string representation of the value.
 302    *
 303    * The first letter of the string representation shows whether a value
 304    * or a primary key is being described.
 305    *
 306    * @param value
 307    * @return
 308    */
 309  71 protected String getStringRepFromValue(Object value) {
 310  71 String rep;
 311  71 DataSqueezer squeezer = getDataSqueezer();
 312   
 313    // try to extract the primary key from the value
 314  71 Object pk = getPrimaryKeyFromValue(value);
 315  71 if (pk != null)
 316    // Primary key was extracted successfully.
 317  50 rep = DESC_PRIMARY_KEY + squeezer.squeeze(pk);
 318    else
 319    // primary key could not be extracted. squeeze value.
 320  21 rep = DESC_VALUE + squeezer.squeeze(value);
 321   
 322  71 return rep;
 323    }
 324   
 325    /**
 326    * Returns the primary key of the given value.
 327    * Uses the 'keyExpression' or the 'converter' (if either is provided).
 328    *
 329    * @param value The value from which the primary key should be extracted
 330    * @return The primary key of the value, or null if such cannot be extracted.
 331    */
 332  71 protected Object getPrimaryKeyFromValue(Object value) {
 333  71 if (value == null)
 334  0 return null;
 335   
 336  71 Object primaryKey = getKeyExpressionFromValue(value);
 337  71 if (primaryKey == null)
 338  21 primaryKey = getConverterFromValue(value);
 339   
 340  71 return primaryKey;
 341    }
 342   
 343    /**
 344    * Uses the 'keyExpression' parameter to determine the primary key of the given value
 345    *
 346    * @param value The value from which the primary key should be extracted
 347    * @return The primary key of the value as defined by 'keyExpression',
 348    * or null if such cannot be extracted.
 349    */
 350  71 protected Object getKeyExpressionFromValue(Object value) {
 351  71 String keyExpression = getKeyExpression();
 352  71 if (keyExpression == null)
 353  21 return null;
 354   
 355  50 Object primaryKey = getExpressionEvaluator().read(value, keyExpression);
 356  50 return primaryKey;
 357    }
 358   
 359    /**
 360    * Uses the 'converter' parameter to determine the primary key of the given value
 361    *
 362    * @param value The value from which the primary key should be extracted
 363    * @return The primary key of the value as provided by the converter,
 364    * or null if such cannot be extracted.
 365    */
 366  21 protected Object getConverterFromValue(Object value) {
 367  21 IPrimaryKeyConverter converter = getConverter();
 368  21 if (converter == null)
 369  21 return null;
 370   
 371  0 Object primaryKey = converter.getPrimaryKey(value);
 372  0 return primaryKey;
 373    }
 374   
 375    /**
 376    * Determines the value that corresponds to the given string representation.
 377    *
 378    * If the 'match' parameter is true, attempt to find a value in 'source'
 379    * or 'fullSource' that generates the same string representation.
 380    *
 381    * Otherwise, create a new value from the string representation.
 382    *
 383    * @param rep the string representation for which a value should be returned
 384    * @return the value that corresponds to the provided string representation
 385    */
 386  11 protected Object getValueFromStringRep(String rep) {
 387  11 Object value = null;
 388  11 DataSqueezer squeezer = getDataSqueezer();
 389   
 390    // Check if the string rep is empty. If so, just return the default value.
 391  11 if (rep == null || rep.length() == 0)
 392  0 return getDefaultValue();
 393   
 394    // If required, find a value with an equivalent string representation and return it
 395  11 boolean match = getMatch();
 396  11 if (match) {
 397  9 value = findValueWithStringRep(rep, COMPLETE_REP_SOURCE);
 398  9 if (value != null)
 399  7 return value;
 400    }
 401   
 402    // Matching of the string representation was not successful or was disabled.
 403    // Use the standard approaches to obtain the value from the rep.
 404  4 char desc = rep.charAt(0);
 405  4 String squeezed = rep.substring(1);
 406  4 switch (desc) {
 407  3 case DESC_VALUE:
 408    // If the string rep is just the value itself, unsqueeze it
 409  3 value = squeezer.unsqueeze(squeezed);
 410  3 break;
 411   
 412  1 case DESC_PRIMARY_KEY:
 413    // Perform keyExpression match if not already attempted
 414  1 if (!match && getKeyExpression() != null)
 415  0 value = findValueWithStringRep(rep, KEY_EXPRESSION_REP_SOURCE);
 416   
 417    // If 'converter' is defined, try to perform conversion from primary key to value
 418  1 if (value == null) {
 419  1 IPrimaryKeyConverter converter = getConverter();
 420  1 if (converter != null) {
 421  0 Object pk = squeezer.unsqueeze(squeezed);
 422  0 value = converter.getValue(pk);
 423    }
 424    }
 425  1 break;
 426    }
 427   
 428  4 if (value == null)
 429  1 value = getDefaultValue();
 430   
 431  4 return value;
 432    }
 433   
 434    /**
 435    * Attempt to find a value in 'source' or 'fullSource' that generates
 436    * the provided string representation.
 437    *
 438    * Use the RepSource interface to determine what the string representation
 439    * of a particular value is.
 440    *
 441    * @param rep the string representation for which a value should be returned
 442    * @param repSource an interface providing the string representation of a given value
 443    * @return the value in 'source' or 'fullSource' that corresponds
 444    * to the provided string representation
 445    */
 446  9 protected Object findValueWithStringRep(String rep, RepSource repSource) {
 447  9 Map repToValueMap = getRepToValueMap();
 448   
 449  9 Object value = repToValueMap.get(rep);
 450  9 if (value != null)
 451  1 return value;
 452   
 453  8 Iterator it = getSourceIteratorValue();
 454  8 value = findValueWithStringRepInIterator(rep, repSource, it);
 455  8 if (value != null)
 456  4 return value;
 457   
 458  4 it = getFullSourceIteratorValue();
 459  4 value = findValueWithStringRepInIterator(rep, repSource, it);
 460  4 return value;
 461    }
 462   
 463    /**
 464    * Attempt to find a value in the provided collection that generates
 465    * the required string representation.
 466    *
 467    * Use the RepSource interface to determine what the string representation
 468    * of a particular value is.
 469    *
 470    * @param rep the string representation for which a value should be returned
 471    * @param repSource an interface providing the string representation of a given value
 472    * @param it the iterator of the collection in which a value should be searched
 473    * @return the value in the provided collection that corresponds
 474    * to the required string representation
 475    */
 476  12 protected Object findValueWithStringRepInIterator(String rep, RepSource repSource, Iterator it) {
 477  12 Map repToValueMap = getRepToValueMap();
 478   
 479  12 while (it.hasNext()) {
 480  51 Object sourceValue = it.next();
 481  51 if (sourceValue == null)
 482  0 continue;
 483   
 484  51 String sourceRep = repSource.getStringRep(sourceValue);
 485  51 repToValueMap.put(sourceRep, sourceValue);
 486   
 487  51 if (rep.equals(sourceRep))
 488  6 return sourceValue;
 489    }
 490   
 491  6 return null;
 492    }
 493   
 494    /**
 495    * Returns the cached 'source' iterator. The value is initialized
 496    * with the iterator provided by the 'source' parameter
 497    *
 498    * @return the cached 'source' iterator
 499    */
 500  24 protected Iterator getSourceIteratorValue()
 501    {
 502  24 Iterator it = getSourceIterator();
 503  24 if (it == null) {
 504  20 it = (Iterator) getValueConverter().coerceValue(getSource(), Iterator.class);
 505  20 if (it == null)
 506  0 it = Collections.EMPTY_LIST.iterator();
 507  20 setSourceIterator(it);
 508    }
 509   
 510  24 return it;
 511    }
 512   
 513    /**
 514    * Returns the cached 'fullSource' iterator. The value is initialized
 515    * with the iterator provided by the 'fullSource' parameter
 516    *
 517    * @return the cached 'fullSource' iterator
 518    */
 519  4 protected Iterator getFullSourceIteratorValue()
 520    {
 521  4 Iterator it = getFullSourceIterator();
 522  4 if (it == null) {
 523  3 it = (Iterator) getValueConverter().coerceValue(getFullSource(), Iterator.class);
 524  3 if (it == null)
 525  0 it = Collections.EMPTY_LIST.iterator();
 526  3 setFullSourceIterator(it);
 527    }
 528   
 529  4 return it;
 530    }
 531   
 532    /**
 533    * An interface that provides the string representation of a given value
 534    */
 535    protected interface RepSource {
 536    String getStringRep(Object value);
 537    }
 538   
 539    /**
 540    * An implementation of RepSource that provides the string representation
 541    * of the given value using all methods.
 542    */
 543    protected class CompleteRepSource implements RepSource {
 544  51 public String getStringRep(Object value) {
 545  51 return getStringRepFromValue(value);
 546    }
 547    }
 548   
 549    /**
 550    * An implementation of RepSource that provides the string representation
 551    * of the given value using just the 'keyExpression' parameter.
 552    */
 553    protected class KeyExpressionRepSource implements RepSource {
 554  0 public String getStringRep(Object value) {
 555  0 Object pk = getKeyExpressionFromValue(value);
 556  0 return DESC_PRIMARY_KEY + getDataSqueezer().squeeze(pk);
 557    }
 558    }
 559   
 560    /**
 561    * For component can not take focus.
 562    */
 563  0 protected boolean getCanTakeFocus() {
 564  0 return false;
 565    }
 566   
 567  0 public String getClientId()
 568    {
 569    // TODO Auto-generated method stub
 570  0 return null;
 571    }
 572  0 public String getDisplayName()
 573    {
 574    // TODO Auto-generated method stub
 575  0 return null;
 576    }
 577   
 578   
 579    }