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
015package org.apache.tapestry.binding;
016
017import org.apache.hivemind.Location;
018import org.apache.tapestry.BindingException;
019import org.apache.tapestry.IComponent;
020import org.apache.tapestry.coerce.ValueConverter;
021import org.apache.tapestry.services.ExpressionCache;
022import org.apache.tapestry.services.ExpressionEvaluator;
023
024/**
025 * Implements a dynamic binding, based on evaluating an expression using an expression language.
026 * Tapestry's default expression language is the <a href="http://www.ognl.org/">Object Graph
027 * Navigation Language </a>.
028 * 
029 * @see org.apache.tapestry.services.ExpressionEvaluator
030 * @author Howard Lewis Ship
031 * @since 2.2
032 */
033
034public class ExpressionBinding extends AbstractBinding
035{
036    /**
037     * The root object against which the nested property name is evaluated.
038     */
039
040    private final IComponent _root;
041
042    /**
043     * The OGNL expression, as a string.
044     */
045
046    private String _expression;
047
048    /**
049     * If true, then the binding is invariant.
050     */
051
052    private boolean _invariant = false;
053
054    /**
055     * Parsed OGNL expression.
056     */
057
058    private Object _parsedExpression;
059
060    /**
061     * Flag set to true once the binding has initialized.
062     */
063
064    private boolean _initialized;
065
066    /**
067     * @since 4.0
068     */
069
070    private ExpressionEvaluator _evaluator;
071
072    /** @since 4.0 */
073
074    private ExpressionCache _cache;
075
076    /**
077     * Creates a {@link ExpressionBinding}from the root object and an OGNL expression.
078     */
079
080    public ExpressionBinding(String description, Location location, ValueConverter valueConverter,
081            IComponent root, String expression, ExpressionEvaluator evaluator,
082            ExpressionCache cache)
083    {
084        super(description, valueConverter, location);
085
086        _root = root;
087        _expression = expression;
088        _evaluator = evaluator;
089        _cache = cache;
090    }
091
092    /**
093     * Gets the value of the property path, with the assistance of the {@link ExpressionEvaluator}.
094     * 
095     * @throws BindingException
096     *             if an exception is thrown accessing the property.
097     */
098
099    public Object getObject()
100    {
101        initialize();
102
103        return resolveExpression();
104    }
105
106    private Object resolveExpression()
107    {
108        try
109        {
110            return _evaluator.readCompiled(_root, _parsedExpression);
111        }
112        catch (Throwable t)
113        {
114            throw new BindingException(t.getMessage(), this, t);
115        }
116    }
117
118    /**
119     * Returns true if the binding is expected to always return the same value.
120     */
121
122    public boolean isInvariant()
123    {
124        initialize();
125
126        return _invariant;
127    }
128
129    /**
130     * Sets up the helper object, but also optimizes the property path and determines if the binding
131     * is invarant.
132     */
133
134    private void initialize()
135    {
136        if (_initialized)
137            return;
138
139        _initialized = true;
140
141        try
142        {
143            _parsedExpression = _cache.getCompiledExpression(_expression);
144
145            _invariant = _evaluator.isConstant(_expression);
146        }
147        catch (Exception ex)
148        {
149            throw new BindingException(ex.getMessage(), this, ex);
150        }
151    }
152
153    /**
154     * Updates the property for the binding to the given value.
155     * 
156     * @throws BindingException
157     *             if the property can't be updated (typically due to an security problem, or a
158     *             missing mutator method).
159     * @throws ReadOnlyBindingException
160     *             if the binding is invariant.
161     */
162
163    public void setObject(Object value)
164    {
165        initialize();
166
167        if (_invariant)
168            throw createReadOnlyBindingException(this);
169
170        try
171        {
172            _evaluator.writeCompiled(_root, _parsedExpression, value);
173        }
174        catch (Throwable ex)
175        {
176            throw new BindingException(ex.getMessage(), this, ex);
177        }
178    }
179
180    /**
181     * Returns the a String representing the property path. This includes the
182     * {@link IComponent#getExtendedId() extended id}of the root component and the property path
183     * ... once the binding is used, these may change due to optimization of the property path.
184     */
185
186    public String toString()
187    {
188        StringBuffer buffer = new StringBuffer();
189
190        buffer.append("ExpressionBinding[");
191        buffer.append(_root.getExtendedId());
192
193        if (_expression != null)
194        {
195            buffer.append(' ');
196            buffer.append(_expression);
197        }
198
199        buffer.append(']');
200
201        return buffer.toString();
202    }
203
204    /** @since 4.0 */
205    public Object getComponent()
206    {
207        return _root;
208    }
209}