/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you 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.myfaces.view.facelets.tag; | |
import java.util.Arrays; | |
import javax.el.ELException; | |
import javax.el.ExpressionFactory; | |
import javax.el.MethodExpression; | |
import javax.el.ValueExpression; | |
import javax.faces.view.Location; | |
import javax.faces.view.facelets.FaceletContext; | |
import javax.faces.view.facelets.TagAttribute; | |
import javax.faces.view.facelets.TagAttributeException; | |
import org.apache.myfaces.util.ExternalSpecifications; | |
import org.apache.myfaces.view.facelets.AbstractFaceletContext; | |
import org.apache.myfaces.view.facelets.el.CompositeComponentELUtils; | |
import org.apache.myfaces.view.facelets.el.ContextAwareTagMethodExpression; | |
import org.apache.myfaces.view.facelets.el.ContextAwareTagValueExpression; | |
import org.apache.myfaces.view.facelets.el.ContextAwareTagValueExpressionUEL; | |
import org.apache.myfaces.view.facelets.el.ELText; | |
import org.apache.myfaces.view.facelets.el.LocationMethodExpression; | |
import org.apache.myfaces.view.facelets.el.LocationValueExpression; | |
import org.apache.myfaces.view.facelets.el.LocationValueExpressionUEL; | |
import org.apache.myfaces.view.facelets.el.TagMethodExpression; | |
import org.apache.myfaces.view.facelets.el.TagValueExpression; | |
import org.apache.myfaces.view.facelets.el.TagValueExpressionUEL; | |
import org.apache.myfaces.view.facelets.el.ValueExpressionMethodExpression; | |
/** | |
* Representation of a Tag's attribute in a Facelet File | |
* | |
* @author Jacob Hookom | |
* @version $Id: TagAttribute.java,v 1.9 2008/07/13 19:01:35 rlubke Exp $ | |
*/ | |
public final class TagAttributeImpl extends TagAttribute | |
{ | |
private final static int EL_LITERAL = 1; | |
private final static int EL_CC = 2; | |
private final static int EL_CC_ATTR_ME = 4; | |
private final int capabilities; | |
private final String localName; | |
private final Location location; | |
private final String namespace; | |
private final String qName; | |
private final String value; | |
private String string; | |
/** | |
* This variable is used to cache created expressions using | |
* getValueExpression or getMethodExpression methods. It uses | |
* a racy single check strategy, because if the expression can be | |
* cached the same instance will be built. | |
*/ | |
private volatile Object[] cachedExpression; | |
public TagAttributeImpl(Location location, String ns, String localName, String qName, String value) | |
{ | |
boolean literal; | |
boolean compositeComponentExpression; | |
boolean compositeComponentAttrMethodExpression; | |
this.location = location; | |
this.namespace = ns; | |
this.localName = localName; | |
this.qName = qName; | |
this.value = value; | |
try | |
{ | |
literal = ELText.isLiteral(this.value); | |
} | |
catch (ELException e) | |
{ | |
throw new TagAttributeException(this, e); | |
} | |
compositeComponentExpression = !literal ? | |
CompositeComponentELUtils.isCompositeComponentExpression(this.value) : | |
false; | |
compositeComponentAttrMethodExpression = compositeComponentExpression ? | |
CompositeComponentELUtils.isCompositeComponentAttrsMethodExpression(this.value) : | |
false; | |
this.capabilities = (literal ? EL_LITERAL : 0) | (compositeComponentExpression ? EL_CC : 0) | (compositeComponentAttrMethodExpression ? EL_CC_ATTR_ME : 0); | |
} | |
/** | |
* If literal, return {@link Boolean#getBoolean(java.lang.String) Boolean.getBoolean(java.lang.String)} passing our | |
* value, otherwise call {@link #getObject(FaceletContext, Class) getObject(FaceletContext, Class)}. | |
* | |
* @see Boolean#getBoolean(java.lang.String) | |
* @see #getObject(FaceletContext, Class) | |
* @param ctx | |
* FaceletContext to use | |
* @return boolean value | |
*/ | |
public boolean getBoolean(FaceletContext ctx) | |
{ | |
if ((this.capabilities & EL_LITERAL) != 0) | |
{ | |
return Boolean.valueOf(this.value).booleanValue(); | |
} | |
else | |
{ | |
return ((Boolean) this.getObject(ctx, Boolean.class)).booleanValue(); | |
} | |
} | |
/** | |
* If literal, call {@link Integer#parseInt(java.lang.String) Integer.parseInt(String)}, otherwise call | |
* {@link #getObject(FaceletContext, Class) getObject(FaceletContext, Class)}. | |
* | |
* @see Integer#parseInt(java.lang.String) | |
* @see #getObject(FaceletContext, Class) | |
* @param ctx | |
* FaceletContext to use | |
* @return int value | |
*/ | |
public int getInt(FaceletContext ctx) | |
{ | |
if ((this.capabilities & EL_LITERAL) != 0) | |
{ | |
return Integer.parseInt(this.value); | |
} | |
else | |
{ | |
return ((Number) this.getObject(ctx, Integer.class)).intValue(); | |
} | |
} | |
/** | |
* Local name of this attribute | |
* | |
* @return local name of this attribute | |
*/ | |
public String getLocalName() | |
{ | |
return this.localName; | |
} | |
/** | |
* The location of this attribute in the FaceletContext | |
* | |
* @return the TagAttribute's location | |
*/ | |
public Location getLocation() | |
{ | |
return this.location; | |
} | |
/** | |
* Create a MethodExpression, using this attribute's value as the expression String. | |
* | |
* @see ExpressionFactory#createMethodExpression(javax.el.ELContext, java.lang.String, java.lang.Class, | |
* java.lang.Class[]) | |
* @see MethodExpression | |
* @param ctx | |
* FaceletContext to use | |
* @param type | |
* expected return type | |
* @param paramTypes | |
* parameter type | |
* @return a MethodExpression instance | |
*/ | |
public MethodExpression getMethodExpression(FaceletContext ctx, Class type, Class[] paramTypes) | |
{ | |
AbstractFaceletContext actx = (AbstractFaceletContext) ctx; | |
//volatile reads are atomic, so take the tuple to later comparison. | |
Object[] localCachedExpression = cachedExpression; | |
if (actx.isAllowCacheELExpressions() && localCachedExpression != null && (localCachedExpression.length % 3 == 0)) | |
{ | |
//If the expected type and paramTypes are the same return the cached one | |
for (int i = 0; i < (localCachedExpression.length/3); i++) | |
{ | |
if ( ((type == null && localCachedExpression[(i*3)] == null ) || | |
(type != null && type.equals(localCachedExpression[(i*3)])) ) && | |
(Arrays.equals(paramTypes, (Class[]) localCachedExpression[(i*3)+1])) ) | |
{ | |
return (MethodExpression) localCachedExpression[(i*3)+2]; | |
} | |
} | |
} | |
actx.beforeConstructELExpression(); | |
try | |
{ | |
MethodExpression methodExpression = null; | |
// From this point we can suppose this attribute contains a ELExpression | |
// Now we have to check if the expression points to a composite component attribute map | |
// and if so deal with it as an indirection. | |
// NOTE that we have to check if the expression refers to cc.attrs for a MethodExpression | |
// (#{cc.attrs.myMethod}) or only for MethodExpression parameters (#{bean.method(cc.attrs.value)}). | |
if ((this.capabilities & EL_CC_ATTR_ME) != 0) | |
{ | |
// The MethodExpression is on parent composite component attribute map. | |
// create a pointer that are referred to the real one that is created in other side | |
// (see VDL.retargetMethodExpressions for details) | |
// check for params in the the MethodExpression | |
if (ExternalSpecifications.isUnifiedELAvailable() && this.value.contains("(")) | |
{ | |
// if we don't throw this exception here, another ELException will be | |
// thrown later, because #{cc.attrs.method(param)} will not work as a | |
// ValueExpression pointing to a MethodExpression | |
throw new ELException("Cannot add parameters to a MethodExpression " | |
+ "pointing to cc.attrs"); | |
} | |
ValueExpression valueExpr = this.getValueExpression(ctx, Object.class); | |
methodExpression = new ValueExpressionMethodExpression(valueExpr); | |
} | |
else | |
{ | |
ExpressionFactory f = ctx.getExpressionFactory(); | |
methodExpression = f.createMethodExpression(ctx, this.value, type, paramTypes); | |
// if the MethodExpression contains a reference to the current composite | |
// component, the Location also has to be stored in the MethodExpression | |
// to be able to resolve the right composite component (the one that was | |
// created from the file the Location is pointing to) later. | |
// (see MYFACES-2561 for details) | |
if ((this.capabilities & EL_CC) != 0) | |
{ | |
methodExpression = new LocationMethodExpression(getLocation(), methodExpression); | |
} | |
} | |
if (actx.getFaceletCompositionContext().isWrapTagExceptionsAsContextAware()) | |
{ | |
methodExpression = new ContextAwareTagMethodExpression(this, methodExpression); | |
} | |
else | |
{ | |
methodExpression = new TagMethodExpression(this, methodExpression); | |
} | |
if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved()) | |
{ | |
if (localCachedExpression != null && (localCachedExpression.length % 3 == 0)) | |
{ | |
// If you use a racy single check, assign | |
// the volatile variable at the end. | |
Object[] array = new Object[localCachedExpression.length+3]; | |
array[0] = type; | |
array[1] = paramTypes; | |
array[2] = methodExpression; | |
for (int i = 0; i < localCachedExpression.length; i++) | |
{ | |
array[i+3] = localCachedExpression[i]; | |
} | |
cachedExpression = array; | |
} | |
else | |
{ | |
cachedExpression = new Object[]{type, paramTypes, methodExpression}; | |
} | |
} | |
return methodExpression; | |
} | |
catch (Exception e) | |
{ | |
throw new TagAttributeException(this, e); | |
} | |
finally | |
{ | |
actx.afterConstructELExpression(); | |
} | |
} | |
/** | |
* The resolved Namespace for this attribute | |
* | |
* @return resolved Namespace | |
*/ | |
public String getNamespace() | |
{ | |
return this.namespace; | |
} | |
/** | |
* Delegates to getObject with Object.class as a param | |
* | |
* @see #getObject(FaceletContext, Class) | |
* @param ctx | |
* FaceletContext to use | |
* @return Object representation of this attribute's value | |
*/ | |
public Object getObject(FaceletContext ctx) | |
{ | |
return this.getObject(ctx, Object.class); | |
} | |
/** | |
* The qualified name for this attribute | |
* | |
* @return the qualified name for this attribute | |
*/ | |
public String getQName() | |
{ | |
return this.qName; | |
} | |
/** | |
* Return the literal value of this attribute | |
* | |
* @return literal value | |
*/ | |
public String getValue() | |
{ | |
return this.value; | |
} | |
/** | |
* If literal, then return our value, otherwise delegate to getObject, passing String.class. | |
* | |
* @see #getObject(FaceletContext, Class) | |
* @param ctx | |
* FaceletContext to use | |
* @return String value of this attribute | |
*/ | |
public String getValue(FaceletContext ctx) | |
{ | |
if ((this.capabilities & EL_LITERAL) != 0) | |
{ | |
return this.value; | |
} | |
else | |
{ | |
return (String) this.getObject(ctx, String.class); | |
} | |
} | |
/** | |
* If literal, simply coerce our String literal value using an ExpressionFactory, otherwise create a ValueExpression | |
* and evaluate it. | |
* | |
* @see ExpressionFactory#coerceToType(java.lang.Object, java.lang.Class) | |
* @see ExpressionFactory#createValueExpression(javax.el.ELContext, java.lang.String, java.lang.Class) | |
* @see ValueExpression | |
* @param ctx | |
* FaceletContext to use | |
* @param type | |
* expected return type | |
* @return Object value of this attribute | |
*/ | |
public Object getObject(FaceletContext ctx, Class type) | |
{ | |
if ((this.capabilities & EL_LITERAL) != 0) | |
{ | |
if (String.class.equals(type)) | |
{ | |
return this.value; | |
} | |
else | |
{ | |
try | |
{ | |
return ctx.getExpressionFactory().coerceToType(this.value, type); | |
} | |
catch (Exception e) | |
{ | |
throw new TagAttributeException(this, e); | |
} | |
} | |
} | |
else | |
{ | |
ValueExpression ve = this.getValueExpression(ctx, type); | |
try | |
{ | |
return ve.getValue(ctx); | |
} | |
catch (Exception e) | |
{ | |
throw new TagAttributeException(this, e); | |
} | |
} | |
} | |
/** | |
* Create a ValueExpression, using this attribute's literal value and the passed expected type. | |
* | |
* @see ExpressionFactory#createValueExpression(javax.el.ELContext, java.lang.String, java.lang.Class) | |
* @see ValueExpression | |
* @param ctx | |
* FaceletContext to use | |
* @param type | |
* expected return type | |
* @return ValueExpression instance | |
*/ | |
public ValueExpression getValueExpression(FaceletContext ctx, Class type) | |
{ | |
AbstractFaceletContext actx = (AbstractFaceletContext) ctx; | |
//volatile reads are atomic, so take the tuple to later comparison. | |
Object[] localCachedExpression = cachedExpression; | |
if (actx.isAllowCacheELExpressions() && localCachedExpression != null && localCachedExpression.length == 2) | |
{ | |
//If the expected type is the same return the cached one | |
if (localCachedExpression[0] == null && type == null) | |
{ | |
return (ValueExpression) localCachedExpression[1]; | |
} | |
else if (localCachedExpression[0] != null && localCachedExpression[0].equals(type)) | |
{ | |
return (ValueExpression) localCachedExpression[1]; | |
} | |
} | |
actx.beforeConstructELExpression(); | |
try | |
{ | |
ExpressionFactory f = ctx.getExpressionFactory(); | |
ValueExpression valueExpression = f.createValueExpression(ctx, this.value, type); | |
if (ExternalSpecifications.isUnifiedELAvailable()) | |
{ | |
if (actx.getFaceletCompositionContext().isWrapTagExceptionsAsContextAware()) | |
{ | |
valueExpression = new ContextAwareTagValueExpressionUEL(this, valueExpression); | |
} | |
else | |
{ | |
valueExpression = new TagValueExpressionUEL(this, valueExpression); | |
} | |
} | |
else | |
{ | |
if (actx.getFaceletCompositionContext().isWrapTagExceptionsAsContextAware()) | |
{ | |
valueExpression = new ContextAwareTagValueExpression(this, valueExpression); | |
} | |
else | |
{ | |
valueExpression = new TagValueExpression(this, valueExpression); | |
} | |
} | |
// if the ValueExpression contains a reference to the current composite | |
// component, the Location also has to be stored in the ValueExpression | |
// to be able to resolve the right composite component (the one that was | |
// created from the file the Location is pointing to) later. | |
// (see MYFACES-2561 for details) | |
if ((this.capabilities & EL_CC) != 0) | |
{ | |
if (ExternalSpecifications.isUnifiedELAvailable()) | |
{ | |
valueExpression = new LocationValueExpressionUEL(getLocation(), valueExpression); | |
} | |
else | |
{ | |
valueExpression = new LocationValueExpression(getLocation(), valueExpression); | |
} | |
} | |
if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved()) | |
{ | |
cachedExpression = new Object[]{type, valueExpression}; | |
} | |
return valueExpression; | |
} | |
catch (Exception e) | |
{ | |
throw new TagAttributeException(this, e); | |
} | |
finally | |
{ | |
actx.afterConstructELExpression(); | |
} | |
} | |
/** | |
* If this TagAttribute is literal (not #{..} or ${..}) | |
* | |
* @return true if this attribute is literal | |
*/ | |
public boolean isLiteral() | |
{ | |
return (this.capabilities & EL_LITERAL) != 0; | |
} | |
/* | |
* (non-Javadoc) | |
* | |
* @see java.lang.Object#toString() | |
*/ | |
public String toString() | |
{ | |
if (this.string == null) | |
{ | |
this.string = this.location + " " + this.qName + "=\"" + this.value + "\""; | |
} | |
return this.string; | |
} | |
} |