001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.components;
016    
017    import java.util.ArrayList;
018    import java.util.Collections;
019    import java.util.HashMap;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.tapestry.IBinding;
025    import org.apache.tapestry.IForm;
026    import org.apache.tapestry.IMarkupWriter;
027    import org.apache.tapestry.IRequestCycle;
028    import org.apache.tapestry.Tapestry;
029    import org.apache.tapestry.TapestryUtils;
030    import org.apache.tapestry.coerce.ValueConverter;
031    import org.apache.tapestry.form.AbstractFormComponent;
032    import org.apache.tapestry.services.DataSqueezer;
033    import org.apache.tapestry.services.ExpressionEvaluator;
034    
035    /**
036     * @author mb
037     */
038    public abstract class ForBean extends AbstractFormComponent {
039            // constants
040        private static final char DESC_VALUE = 'V';
041            private static final char DESC_PRIMARY_KEY = 'P';
042    
043        private final RepSource COMPLETE_REP_SOURCE = new CompleteRepSource();
044        private final RepSource KEY_EXPRESSION_REP_SOURCE = new KeyExpressionRepSource();
045        
046            // parameters
047        public abstract String getElement();
048        public abstract String getKeyExpression();
049        public abstract IPrimaryKeyConverter getConverter();
050        public abstract Object getDefaultValue();
051        public abstract boolean getMatch();
052        public abstract boolean getVolatile();
053    
054        // injects
055        public abstract DataSqueezer getDataSqueezer();
056        public abstract ValueConverter getValueConverter();
057        public abstract ExpressionEvaluator getExpressionEvaluator();
058    
059        // intermediate members
060        private Object _value;
061        private int _index;
062        private boolean _rendering;
063    
064        
065        /**
066         *  Gets the source binding and iterates through
067         *  its values.  For each, it updates the value binding and render's its wrapped elements.
068         *
069         **/
070        protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
071        {
072            // form may be null if component is not located in a form
073            IForm form = (IForm) cycle.getAttribute(TapestryUtils.FORM_ATTRIBUTE);
074    
075            // If the cycle is rewinding, but not this particular form,
076            // then do nothing (don't even render the body).
077            boolean cycleRewinding = cycle.isRewinding();
078            if (cycleRewinding && form != null && !form.isRewinding())
079                return;
080    
081            // Get the data to be iterated upon. Store in form if needed.
082            Iterator dataSource = getData(cycle, form);
083            
084            // Do not iterate if dataSource is null. 
085            // The dataSource was either not convertable to Iterator, or was empty. 
086            if (dataSource == null)
087                return;
088    
089            String element = getElement();
090    
091            // Perform the iterations
092            try
093            {
094                _index = 0;
095                _rendering = true;
096    
097                while (dataSource.hasNext())
098                {
099                    // Get current value
100                    _value = dataSource.next();
101    
102                    // Update output component parameters
103                    updateOutputParameters();
104                    
105                    // Render component
106                    if (element != null)
107                    {
108                        writer.begin(element);
109                        renderInformalParameters(writer, cycle);
110                    }
111    
112                    renderBody(writer, cycle);
113    
114                    if (element != null)
115                        writer.end();
116    
117                    _index++;
118                }
119            }
120            finally
121            {
122                _rendering = false;
123                _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        public final Object getValue()
136        {
137            if (!_rendering)
138                throw Tapestry.createRenderOnlyPropertyException(this, "value");
139      
140            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        public int getIndex()
152        {
153            if (!_rendering)
154                throw Tapestry.createRenderOnlyPropertyException(this, "index");
155            
156            return _index;
157        }
158    
159        public boolean isDisabled()
160        {
161            return false;
162        }
163    
164        /**
165         * Updates the index and value output parameters if bound.
166         */
167        protected void updateOutputParameters()
168        {
169            IBinding indexBinding = getBinding("index");
170            if (indexBinding != null)
171                    indexBinding.setObject(new Integer(_index));
172    
173            IBinding valueBinding = getBinding("value");
174            if (valueBinding != null)
175                    valueBinding.setObject(_value);
176        }
177    
178        /**
179         * Updates the primaryKeys parameter if bound.
180         */
181        protected void updatePrimaryKeysParameter(String[] stringReps)
182        {
183            IBinding primaryKeysBinding = getBinding("primaryKeys");
184            if (primaryKeysBinding == null)
185                    return;
186    
187            DataSqueezer squeezer = getDataSqueezer();
188            
189            int repsCount = stringReps.length;
190            List primaryKeys = new ArrayList(repsCount);
191            for (int i = 0; i < stringReps.length; i++) {
192                            String rep = stringReps[i];
193                            if (rep.length() == 0 || rep.charAt(0) != DESC_PRIMARY_KEY)
194                                    continue;
195                            Object primaryKey = squeezer.unsqueeze(rep.substring(1));
196                            primaryKeys.add(primaryKey);
197                    }
198            
199            primaryKeysBinding.setObject(primaryKeys);
200        }
201        
202            // Do nothing in those methods, but make the JVM happy
203        protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle) { }
204        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        private Iterator getData(IRequestCycle cycle, IForm form) {
225            if (form == null || getVolatile())
226                    return evaluateSourceIterator();
227            
228            String name = form.getElementId(this);
229            if (cycle.isRewinding())
230                    return getStoredData(cycle, name);
231            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        protected Iterator getStoredData(IRequestCycle cycle, String name)
243        {
244            String[] stringReps = cycle.getParameters(name);
245            if (stringReps == null)
246                    return null;
247            
248            updatePrimaryKeysParameter(stringReps);
249            
250            Iterator sourceIterator = evaluateSourceIterator();
251            Iterator fullSourceIterator = evaluateFullSourceIterator();
252            Map repToValueMap = new HashMap();
253            
254            int valueCount = stringReps.length;
255            List values = new ArrayList(valueCount);
256            for (int i = 0; i < valueCount; i++) {
257                            String rep = stringReps[i];
258                            Object value = getValueFromStringRep(sourceIterator, fullSourceIterator, repToValueMap, rep);
259                            values.add(value);
260                    }
261            
262            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        protected Iterator storeSourceData(IForm form, String name)
275        {
276            List values = new ArrayList();
277            
278            Iterator it = evaluateSourceIterator();
279            while (it.hasNext()) {
280                    Object value = it.next();
281                    values.add(value);
282                    
283                    String rep = getStringRepFromValue(value);
284                    form.addHiddenValue(name, rep);
285            }
286            
287            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        protected String getStringRepFromValue(Object value) {
301            String rep;
302            DataSqueezer squeezer = getDataSqueezer();
303            
304            // try to extract the primary key from the value
305            Object pk = getPrimaryKeyFromValue(value);
306            if (pk != null)
307                    // Primary key was extracted successfully. 
308                    rep = DESC_PRIMARY_KEY + squeezer.squeeze(pk);
309            else
310                    // primary key could not be extracted. squeeze value.
311                    rep = DESC_VALUE + squeezer.squeeze(value);
312            
313            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        protected Object getPrimaryKeyFromValue(Object value) {
324            if (value == null)
325                    return null;
326            
327            Object primaryKey = getKeyExpressionFromValue(value);
328                    if (primaryKey == null)
329                            primaryKey = getConverterFromValue(value);
330    
331            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        protected Object getKeyExpressionFromValue(Object value) {
342            String keyExpression = getKeyExpression();
343                    if (keyExpression == null)
344                            return null;
345                    
346                    Object primaryKey = getExpressionEvaluator().read(value, keyExpression);
347                    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        protected Object getConverterFromValue(Object value) {
358            IPrimaryKeyConverter converter = getConverter();
359                    if (converter == null)
360                            return null;
361                    
362                    Object primaryKey = converter.getPrimaryKey(value);
363                    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        protected Object getValueFromStringRep(Iterator sourceIterator, Iterator fullSourceIterator, 
378                    Map repToValueMap, String rep) {
379            Object value = null;
380            DataSqueezer squeezer = getDataSqueezer();
381            
382            // Check if the string rep is empty. If so, just return the default value.
383            if (rep == null || rep.length() == 0)
384                    return getDefaultValue();
385            
386            // If required, find a value with an equivalent string representation and return it 
387            boolean match = getMatch();
388                    if (match) {
389                            value = findValueWithStringRep(sourceIterator, fullSourceIterator, repToValueMap, rep, COMPLETE_REP_SOURCE);
390                            if (value != null)
391                                    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                    char desc = rep.charAt(0);
397                    String squeezed = rep.substring(1);
398            switch (desc) {
399                    case DESC_VALUE:
400                            // If the string rep is just the value itself, unsqueeze it
401                            value = squeezer.unsqueeze(squeezed);
402                            break;
403                            
404                    case DESC_PRIMARY_KEY:
405                            // Perform keyExpression match if not already attempted
406                            if (!match && getKeyExpression() != null)
407                                    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                            if (value == null) {
411                                    IPrimaryKeyConverter converter = getConverter();
412                                    if (converter != null) {
413                                            Object pk = squeezer.unsqueeze(squeezed);
414                                            value = converter.getValue(pk);
415                                    }
416                            }
417                            break;
418            }
419            
420            if (value == null)
421                    value = getDefaultValue();
422            
423            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        protected Object findValueWithStringRep(Iterator sourceIterator, Iterator fullSourceIterator, 
439                    Map repToValueMap, String rep, RepSource repSource) {
440            Object value = repToValueMap.get(rep);
441            if (value != null)
442                    return value;
443    
444                    value = findValueWithStringRepInIterator(sourceIterator, repToValueMap, rep, repSource);
445            if (value != null)
446                    return value;
447                    
448                    value = findValueWithStringRepInIterator(fullSourceIterator, repToValueMap, rep, repSource);
449                    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        protected Object findValueWithStringRepInIterator(Iterator it, Map repToValueMap, String rep, RepSource repSource) {
466                    while (it.hasNext()) {
467                    Object sourceValue = it.next();
468                    if (sourceValue == null)
469                            continue;
470                    
471                    String sourceRep = repSource.getStringRep(sourceValue);
472                    repToValueMap.put(sourceRep, sourceValue);
473                    
474                    if (rep.equals(sourceRep))
475                            return sourceValue;
476                    }
477                    
478                    return null;
479        }
480            
481        /**
482         * Returns a new iterator of the values in 'source'. 
483         * 
484         * @return the 'source' iterator
485         */
486        protected Iterator evaluateSourceIterator()
487        {
488            Iterator it = null;
489            Object source = null;
490            
491            IBinding sourceBinding = getBinding("source");
492            if (sourceBinding != null)
493                    source = sourceBinding.getObject();
494    
495            if (source != null)
496                    it = (Iterator) getValueConverter().coerceValue(source, Iterator.class);
497    
498            if (it == null)
499                            it = Collections.EMPTY_LIST.iterator();
500            
501                    return it;
502        }
503        
504        /**
505         * Returns a new iterator of the values in 'fullSource'. 
506         * 
507         * @return the 'fullSource' iterator
508         */
509        protected Iterator evaluateFullSourceIterator()
510        {
511            Iterator it = null;
512            Object fullSource = null;
513            
514            IBinding fullSourceBinding = getBinding("fullSource");
515            if (fullSourceBinding != null)
516                    fullSource = fullSourceBinding.getObject();
517    
518            if (fullSource != null)
519                    it = (Iterator) getValueConverter().coerceValue(fullSource, Iterator.class);
520    
521            if (it == null)
522                            it = Collections.EMPTY_LIST.iterator();
523            
524                    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            public String getStringRep(Object value) {
540                    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            public String getStringRep(Object value) {
550                    Object pk = getKeyExpressionFromValue(value);
551                    return DESC_PRIMARY_KEY + getDataSqueezer().squeeze(pk);
552            }
553        }
554    
555        /**
556         * For component can not take focus.
557         */
558        protected boolean getCanTakeFocus() {
559            return false;
560        }
561        
562        public String getClientId()
563        {
564            return null;
565        }
566        
567        public String getDisplayName()
568        {
569            return null;
570        }
571        
572        
573    }