/* $$ Clover has instrumented this file $$ */// Copyright 2004 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry.binding;
import java.util.Map;
import ognl.Ognl;
import ognl.OgnlException;
import ognl.TypeConverter;
import org.apache.hivemind.ClassResolver;
import org.apache.hivemind.Location;
import org.apache.tapestry.BindingException;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.spec.BeanLifecycle;
import org.apache.tapestry.spec.IBeanSpecification;
import org.apache.tapestry.spec.IApplicationSpecification;
import org.apache.tapestry.util.StringSplitter;
import org.apache.tapestry.util.prop.OgnlUtils;
/**
* Implements a dynamic binding, based on getting and fetching
* values using JavaBeans property access. This is built
* upon the <a href="http://www.ognl.org">OGNL</a> library.
*
* <p><b>Optimization of the Expression</b>
*
* <p>There's a lot of room for optimization here because we can
* count on some portions of the expression to be
* effectively static. Note that we type the root object as
* {@link IComponent}. We have some expectations that
* certain properties of the root (and properties reachable from the root)
* will be constant for the lifetime of the binding. For example,
* components never change thier page or container. This means
* that certain property prefixes can be optimized:
*
* <ul>
* <li>page
* <li>container
* <li>components.<i>name</i>
* </ul>
*
* <p>This means that once an ExpressionBinding has been triggered,
* the {@link #toString()} method may return different values for the root
* component and the expression than was originally set.
*
* <p><b>Identifying Invariants</b>
*
* <p>Most expressions are fully dynamic; they must be
* resolved each time they are accessed. This can be somewhat inefficient.
* Tapestry can identify certain paths as invariant:
*
* <ul>
* <li>A component within the page hierarchy
* <li>An {@link org.apache.tapestry.IAsset} from then assets map (property <code>assets</code>)
* <li>A {@link org.apache.tapestry.IActionListener}
* from the listener map (property <code>listeners</code>)
* <li>A bean with a {@link org.apache.tapestry.spec.BeanLifecycle#PAGE}
* lifecycle (property <code>beans</code>)
* <li>A binding (property <code>bindings</code>)
* </ul>
*
* <p>
* These optimizations have some inherent dangers; they assume that
* the components have not overidden the specified properties;
* the last one (concerning helper beans) assumes that the
* component does inherit from {@link org.apache.tapestry.AbstractComponent}.
* If this becomes a problem in the future, it may be necessary to
* have the component itself involved in these determinations.
*
* @author Howard Lewis Ship
* @since 2.2
*
**/
public class ExpressionBinding extends AbstractBinding
{public static com.cortexeb.tools.clover.d __CLOVER_52_0 = com.cortexeb.tools.clover.aq.getRecorder(new char[] {67,58,92,119,111,114,107,115,112,97,99,101,92,106,97,107,97,114,116,97,45,116,97,112,101,115,116,114,121,92,102,114,97,109,101,119,111,114,107,92,116,97,114,103,101,116,92,99,108,111,118,101,114,45,100,98},1096998272901L);
/**
* The root object against which the nested property name is evaluated.
*
**/
private IComponent _root;
/**
* The OGNL expression, as a string.
*
**/
private String _expression;
/**
* If true, then the binding is invariant, and cachedValue
* is the ultimate value.
*
**/
private boolean _invariant = false;
/**
* Stores the cached value for the binding, if invariant
* is true.
*
**/
private Object _cachedValue;
/**
* Parsed OGNL expression.
*
**/
private Object _parsedExpression;
/**
* Flag set once the binding has initialized.
* _cachedValue, _invariant and _final value
* for _expression
* are not valid until after initialization.
*
*
**/
private boolean _initialized;
private ClassResolver _resolver;
/**
* The OGNL context for this binding. It is retained
* for the lifespan of the binding once created.
*
**/
private Map _context;
/**
* Creates a {@link ExpressionBinding} from the root object
* and an OGNL expression.
*
**/
public ExpressionBinding(
ClassResolver resolver,
IComponent root,
String expression,
Location location)
{
super(location);__CLOVER_52_0.S[1200]++;try { __CLOVER_52_0.M[305]++;
__CLOVER_52_0.S[1201]++;_resolver = resolver;
__CLOVER_52_0.S[1202]++;_root = root;
__CLOVER_52_0.S[1203]++;_expression = expression;
} finally { }}
public String getExpression()
{try { __CLOVER_52_0.M[306]++;
__CLOVER_52_0.S[1204]++;return _expression;
} finally { }}
public IComponent getRoot()
{try { __CLOVER_52_0.M[307]++;
__CLOVER_52_0.S[1205]++;return _root;
} finally { }}
/**
* Gets the value of the property path, with the assistance of a
* OGNL.
*
* @throws BindingException if an exception is thrown accessing the property.
*
**/
public Object getObject()
{try { __CLOVER_52_0.M[308]++;
__CLOVER_52_0.S[1206]++;initialize();
__CLOVER_52_0.S[1207]++;if ((((_invariant) && (++__CLOVER_52_0.CT[235] != 0)) || (++__CLOVER_52_0.CF[235] == 0))){
__CLOVER_52_0.S[1208]++;return _cachedValue;}
__CLOVER_52_0.S[1209]++;return resolveProperty();
} finally { }}
private Object resolveProperty()
{try { __CLOVER_52_0.M[309]++;
__CLOVER_52_0.S[1210]++;try
{
__CLOVER_52_0.S[1211]++;return Ognl.getValue(_parsedExpression, getOgnlContext(), _root);
}
catch (Throwable t)
{
__CLOVER_52_0.S[1212]++;throw new BindingException(
Tapestry.format(
"ExpressionBinding.unable-to-resolve-expression",
_expression,
_root),
this,
t);
}
} finally { }}
/**
* Creates an OGNL context used to get or set a value.
* We may extend this in the future to set additional
* context variables (such as page, request cycle and engine).
* An optional type converter will be added to the OGNL context
* if it is specified as an application extension with the name
* {@link Tapestry#OGNL_TYPE_CONVERTER}.
*
**/
private Map getOgnlContext()
{try { __CLOVER_52_0.M[310]++;
__CLOVER_52_0.S[1213]++;if ((((_context == null) && (++__CLOVER_52_0.CT[236] != 0)) || (++__CLOVER_52_0.CF[236] == 0))){
__CLOVER_52_0.S[1214]++;_context = Ognl.createDefaultContext(_root, OgnlUtils.getOgnlClassResolver());}
__CLOVER_52_0.S[1215]++;if ((((_root.getPage() != null) && (++__CLOVER_52_0.CT[237] != 0)) || (++__CLOVER_52_0.CF[237] == 0))){
{
__CLOVER_52_0.S[1216]++;if ((((_root.getPage().getEngine() != null) && (++__CLOVER_52_0.CT[238] != 0)) || (++__CLOVER_52_0.CF[238] == 0))){
{
__CLOVER_52_0.S[1217]++;IApplicationSpecification appSpec = _root.getPage().getEngine().getSpecification();
__CLOVER_52_0.S[1218]++;if ((((appSpec != null && appSpec.checkExtension(Tapestry.OGNL_TYPE_CONVERTER)) && (++__CLOVER_52_0.CT[239] != 0)) || (++__CLOVER_52_0.CF[239] == 0))){
{
__CLOVER_52_0.S[1219]++;TypeConverter typeConverter =
(TypeConverter) appSpec.getExtension(
Tapestry.OGNL_TYPE_CONVERTER,
TypeConverter.class);
__CLOVER_52_0.S[1220]++;Ognl.setTypeConverter(_context, typeConverter);
}}
}}
}}
__CLOVER_52_0.S[1221]++;return _context;
} finally { }}
/**
* Returns true if the binding is expected to always
* return the same value.
*
*
**/
public boolean isInvariant()
{try { __CLOVER_52_0.M[311]++;
__CLOVER_52_0.S[1222]++;initialize();
__CLOVER_52_0.S[1223]++;return _invariant;
} finally { }}
public void setBoolean(boolean value)
{try { __CLOVER_52_0.M[312]++;
__CLOVER_52_0.S[1224]++;setObject((((value ) && (++__CLOVER_52_0.CT[240] != 0)) || (++__CLOVER_52_0.CF[240] == 0))? Boolean.TRUE : Boolean.FALSE);
} finally { }}
public void setInt(int value)
{try { __CLOVER_52_0.M[313]++;
__CLOVER_52_0.S[1225]++;setObject(new Integer(value));
} finally { }}
public void setDouble(double value)
{try { __CLOVER_52_0.M[314]++;
__CLOVER_52_0.S[1226]++;setObject(new Double(value));
} finally { }}
public void setString(String value)
{try { __CLOVER_52_0.M[315]++;
__CLOVER_52_0.S[1227]++;setObject(value);
} finally { }}
/**
* Sets up the helper object, but also optimizes the property path
* and determines if the binding is invarant.
*
**/
private void initialize()
{try { __CLOVER_52_0.M[316]++;
__CLOVER_52_0.S[1228]++;if ((((_initialized) && (++__CLOVER_52_0.CT[241] != 0)) || (++__CLOVER_52_0.CF[241] == 0))){
__CLOVER_52_0.S[1229]++;return;}
__CLOVER_52_0.S[1230]++;_initialized = true;
__CLOVER_52_0.S[1231]++;try
{
__CLOVER_52_0.S[1232]++;_parsedExpression = OgnlUtils.getParsedExpression(_expression);
}
catch (Exception ex)
{
__CLOVER_52_0.S[1233]++;throw new BindingException(ex.getMessage(), this, ex);
}
__CLOVER_52_0.S[1234]++;if ((((checkForConstant()) && (++__CLOVER_52_0.CT[242] != 0)) || (++__CLOVER_52_0.CF[242] == 0))){
__CLOVER_52_0.S[1235]++;return;}
__CLOVER_52_0.S[1236]++;try
{
__CLOVER_52_0.S[1237]++;if ((((!Ognl.isSimpleNavigationChain(_parsedExpression, getOgnlContext())) && (++__CLOVER_52_0.CT[243] != 0)) || (++__CLOVER_52_0.CF[243] == 0))){
__CLOVER_52_0.S[1238]++;return;}
}
catch (OgnlException ex)
{
__CLOVER_52_0.S[1239]++;throw new BindingException(ex.getMessage(), this, ex);
}
// Split the expression into individual property names.
// We then optimize what we can from the expression. This will
// shorten the expression and, in some cases, eliminate
// it. We also check to see if the binding can be an invariant.
__CLOVER_52_0.S[1240]++;String[] split = new StringSplitter('.').splitToArray(_expression);
__CLOVER_52_0.S[1241]++;int count = optimizeRootObject(split);
// We'ver removed some or all of the initial elements of split
// but have to account for anthing left over.
__CLOVER_52_0.S[1242]++;if ((((count == split.length) && (++__CLOVER_52_0.CT[244] != 0)) || (++__CLOVER_52_0.CF[244] == 0))){
{
// The property path was something like "page" or "component.foo"
// and was completely eliminated.
__CLOVER_52_0.S[1243]++;_expression = null;
__CLOVER_52_0.S[1244]++;_parsedExpression = null;
__CLOVER_52_0.S[1245]++;_invariant = true;
__CLOVER_52_0.S[1246]++;_cachedValue = _root;
__CLOVER_52_0.S[1247]++;return;
}}
__CLOVER_52_0.S[1248]++;_expression = reassemble(count, split);
__CLOVER_52_0.S[1249]++;_parsedExpression = OgnlUtils.getParsedExpression(_expression);
__CLOVER_52_0.S[1250]++;checkForInvariant(count, split);
} finally { }}
/**
* Looks for common prefixes on the expression (provided pre-split) that
* are recognized as references to other components.
*
* @return the number of leading elements of the split expression that
* have been removed.
*
**/
private int optimizeRootObject(String[] split)
{try { __CLOVER_52_0.M[317]++;
__CLOVER_52_0.S[1251]++;int i;
__CLOVER_52_0.S[1252]++;for (i = 0; (((i < split.length) && (++__CLOVER_52_0.CT[245] != 0)) || (++__CLOVER_52_0.CF[245] == 0)); i++){
{
__CLOVER_52_0.S[1253]++;if ((((split[i].equals("page")) && (++__CLOVER_52_0.CT[246] != 0)) || (++__CLOVER_52_0.CF[246] == 0))){
{
__CLOVER_52_0.S[1254]++;_root = _root.getPage();
__CLOVER_52_0.S[1255]++;continue;
}}
__CLOVER_52_0.S[1256]++;if ((((split[i].equals("container")) && (++__CLOVER_52_0.CT[247] != 0)) || (++__CLOVER_52_0.CF[247] == 0))){
{
__CLOVER_52_0.S[1257]++;_root = _root.getContainer();
__CLOVER_52_0.S[1258]++;continue;
}}
// Here's the tricky one ... if its of the form
// "components.foo" we can get the named component
// directly.
__CLOVER_52_0.S[1259]++;if ((((split[i].equals("components") && i + 1 < split.length) && (++__CLOVER_52_0.CT[248] != 0)) || (++__CLOVER_52_0.CF[248] == 0))){
{
__CLOVER_52_0.S[1260]++;_root = _root.getComponent(split[i + 1]);
__CLOVER_52_0.S[1261]++;i++;
__CLOVER_52_0.S[1262]++;continue;
}}
// Not a recognized prefix, break the loop
__CLOVER_52_0.S[1263]++;break;
}}
__CLOVER_52_0.S[1264]++;return i;
} finally { }}
private boolean checkForConstant()
{try { __CLOVER_52_0.M[318]++;
__CLOVER_52_0.S[1265]++;try
{
__CLOVER_52_0.S[1266]++;if ((((OgnlUtils.isConstant(_parsedExpression)) && (++__CLOVER_52_0.CT[249] != 0)) || (++__CLOVER_52_0.CF[249] == 0))){
{
__CLOVER_52_0.S[1267]++;_invariant = true;
__CLOVER_52_0.S[1268]++;_cachedValue = resolveProperty();
__CLOVER_52_0.S[1269]++;return true;
}}
}
catch (Exception ex)
{
__CLOVER_52_0.S[1270]++;throw new BindingException(
Tapestry.format(
"ExpressionBinding.unable-to-resolve-expression",
_expression,
_root),
this,
ex);
}
__CLOVER_52_0.S[1271]++;return false;
} finally { }}
/**
* Reassembles the remainder of the split property path
* from the start point.
*
**/
private String reassemble(int start, String[] split)
{try { __CLOVER_52_0.M[319]++;
__CLOVER_52_0.S[1272]++;int count = split.length - start;
__CLOVER_52_0.S[1273]++;if ((((count == 0) && (++__CLOVER_52_0.CT[250] != 0)) || (++__CLOVER_52_0.CF[250] == 0))){
__CLOVER_52_0.S[1274]++;return null;}
__CLOVER_52_0.S[1275]++;if ((((count == 1) && (++__CLOVER_52_0.CT[251] != 0)) || (++__CLOVER_52_0.CF[251] == 0))){
__CLOVER_52_0.S[1276]++;return split[split.length - 1];}
__CLOVER_52_0.S[1277]++;StringBuffer buffer = new StringBuffer();
__CLOVER_52_0.S[1278]++;for (int i = start; (((i < split.length) && (++__CLOVER_52_0.CT[252] != 0)) || (++__CLOVER_52_0.CF[252] == 0)); i++){
{
__CLOVER_52_0.S[1279]++;if ((((i > start) && (++__CLOVER_52_0.CT[253] != 0)) || (++__CLOVER_52_0.CF[253] == 0))){
__CLOVER_52_0.S[1280]++;buffer.append('.');}
__CLOVER_52_0.S[1281]++;buffer.append(split[i]);
}}
__CLOVER_52_0.S[1282]++;return buffer.toString();
} finally { }}
/**
* Checks to see if the binding can be converted to an invariant.
*
**/
private void checkForInvariant(int start, String[] split)
{try { __CLOVER_52_0.M[320]++;
// For now, all of our conditions are two properties
// from a root component.
__CLOVER_52_0.S[1283]++;if ((((split.length - start != 2) && (++__CLOVER_52_0.CT[254] != 0)) || (++__CLOVER_52_0.CF[254] == 0))){
__CLOVER_52_0.S[1284]++;return;}
__CLOVER_52_0.S[1285]++;try
{
__CLOVER_52_0.S[1286]++;if ((((!Ognl.isSimpleNavigationChain(_parsedExpression, getOgnlContext())) && (++__CLOVER_52_0.CT[255] != 0)) || (++__CLOVER_52_0.CF[255] == 0))){
__CLOVER_52_0.S[1287]++;return;}
}
catch (OgnlException ex)
{
__CLOVER_52_0.S[1288]++;throw new BindingException(
Tapestry.format(
"ExpressionBinding.unable-to-resolve-expression",
_expression,
_root),
this,
ex);
}
__CLOVER_52_0.S[1289]++;String first = split[start];
__CLOVER_52_0.S[1290]++;if ((((first.equals("listeners")) && (++__CLOVER_52_0.CT[256] != 0)) || (++__CLOVER_52_0.CF[256] == 0))){
{
__CLOVER_52_0.S[1291]++;_invariant = true;
// Could cast to AbstractComponent, get listenersMap, etc.,
// but this is easier.
__CLOVER_52_0.S[1292]++;_cachedValue = resolveProperty();
__CLOVER_52_0.S[1293]++;return;
}}
__CLOVER_52_0.S[1294]++;if ((((first.equals("assets")) && (++__CLOVER_52_0.CT[257] != 0)) || (++__CLOVER_52_0.CF[257] == 0))){
{
__CLOVER_52_0.S[1295]++;String name = split[start + 1];
__CLOVER_52_0.S[1296]++;_invariant = true;
__CLOVER_52_0.S[1297]++;_cachedValue = _root.getAsset(name);
__CLOVER_52_0.S[1298]++;return;
}}
__CLOVER_52_0.S[1299]++;if ((((first.equals("beans")) && (++__CLOVER_52_0.CT[258] != 0)) || (++__CLOVER_52_0.CF[258] == 0))){
{
__CLOVER_52_0.S[1300]++;String name = split[start + 1];
__CLOVER_52_0.S[1301]++;IBeanSpecification bs = _root.getSpecification().getBeanSpecification(name);
__CLOVER_52_0.S[1302]++;if ((((bs == null || bs.getLifecycle() != BeanLifecycle.PAGE) && (++__CLOVER_52_0.CT[259] != 0)) || (++__CLOVER_52_0.CF[259] == 0))){
__CLOVER_52_0.S[1303]++;return;}
// Again, could cast to AbstractComponent, but this
// is easier.
__CLOVER_52_0.S[1304]++;_invariant = true;
__CLOVER_52_0.S[1305]++;_cachedValue = resolveProperty();
__CLOVER_52_0.S[1306]++;return;
}}
__CLOVER_52_0.S[1307]++;if ((((first.equals("bindings")) && (++__CLOVER_52_0.CT[260] != 0)) || (++__CLOVER_52_0.CF[260] == 0))){
{
__CLOVER_52_0.S[1308]++;String name = split[start + 1];
__CLOVER_52_0.S[1309]++;_invariant = true;
__CLOVER_52_0.S[1310]++;_cachedValue = _root.getBinding(name);
__CLOVER_52_0.S[1311]++;return;
}}
// Not a recognized pattern for conversion
// to invariant.
} finally { }}
/**
* Updates the property for the binding to the given value.
*
* @throws BindingException if the property can't be updated (typically
* due to an security problem, or a missing mutator method).
* @throws ReadOnlyBindingException if the binding is invariant.
**/
public void setObject(Object value)
{try { __CLOVER_52_0.M[321]++;
__CLOVER_52_0.S[1312]++;initialize();
__CLOVER_52_0.S[1313]++;if ((((_invariant) && (++__CLOVER_52_0.CT[261] != 0)) || (++__CLOVER_52_0.CF[261] == 0))){
__CLOVER_52_0.S[1314]++;throw createReadOnlyBindingException(this);}
__CLOVER_52_0.S[1315]++;try
{
__CLOVER_52_0.S[1316]++;Ognl.setValue(_parsedExpression, getOgnlContext(), _root, value);
}
catch (Throwable ex)
{
__CLOVER_52_0.S[1317]++;throw new BindingException(
Tapestry.format(
"ExpressionBinding.unable-to-update-expression",
_expression,
_root,
value),
this,
ex);
}
} finally { }}
/**
* Returns the a String representing the property path. This includes
* the {@link IComponent#getExtendedId() extended id} of the root component
* and the property path ... once the binding is used, these may change
* due to optimization of the property path.
*
**/
public String toString()
{try { __CLOVER_52_0.M[322]++;
__CLOVER_52_0.S[1318]++;StringBuffer buffer = new StringBuffer();
__CLOVER_52_0.S[1319]++;buffer.append("ExpressionBinding[");
__CLOVER_52_0.S[1320]++;buffer.append(_root.getExtendedId());
__CLOVER_52_0.S[1321]++;if ((((_expression != null) && (++__CLOVER_52_0.CT[262] != 0)) || (++__CLOVER_52_0.CF[262] == 0))){
{
__CLOVER_52_0.S[1322]++;buffer.append(' ');
__CLOVER_52_0.S[1323]++;buffer.append(_expression);
}}
__CLOVER_52_0.S[1324]++;if ((((_invariant) && (++__CLOVER_52_0.CT[263] != 0)) || (++__CLOVER_52_0.CF[263] == 0))){
{
__CLOVER_52_0.S[1325]++;buffer.append(" cachedValue=");
__CLOVER_52_0.S[1326]++;buffer.append(_cachedValue);
}}
__CLOVER_52_0.S[1327]++;buffer.append(']');
__CLOVER_52_0.S[1328]++;return buffer.toString();
} finally { }}
}