blob: 9825d802b555ad3eb85e6b74e48d5e0835f74035 [file] [log] [blame]
package org.apache.commons.ognl;
/*
* 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.
*/
import org.apache.commons.ognl.enhance.ExpressionCompiler;
import org.apache.commons.ognl.enhance.UnsupportedCompilationException;
import java.beans.IndexedPropertyDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Iterator;
/**
* $Id$
*/
public class ASTProperty
extends SimpleNode
implements NodeType
{
private boolean indexedAccess = false;
private Class getterClass;
private Class setterClass;
public ASTProperty( int id )
{
super( id );
}
public void setIndexedAccess( boolean value )
{
indexedAccess = value;
}
/**
* Returns true if this property is itself an index reference.
*
* @return Returns true if this property is itself an index reference.
*/
public boolean isIndexedAccess()
{
return indexedAccess;
}
/**
* Returns true if this property is described by an IndexedPropertyDescriptor and that if followed by an index
* specifier it will call the index get/set methods rather than go through property accessors.
*
* @param context The context
* @param source The object source
* @return true, if this property is described by an IndexedPropertyDescriptor
* @throws OgnlException if an error occurs
*/
public int getIndexedPropertyType( OgnlContext context, Object source )
throws OgnlException
{
Class type = context.getCurrentType();
Class prevType = context.getPreviousType();
try
{
if ( !isIndexedAccess() )
{
Object property = getProperty( context, source );
if ( property instanceof String )
{
return OgnlRuntime.getIndexedPropertyType( context, ( source == null )
? null
: OgnlRuntime.getCompiler( context ).getInterfaceClass( source.getClass() ),
(String) property );
}
}
return OgnlRuntime.INDEXED_PROPERTY_NONE;
}
finally
{
context.setCurrentObject( source );
context.setCurrentType( type );
context.setPreviousType( prevType );
}
}
public Object getProperty( OgnlContext context, Object source )
throws OgnlException
{
return children[0].getValue( context, context.getRoot() );
}
protected Object getValueBody( OgnlContext context, Object source )
throws OgnlException
{
Object property = getProperty( context, source );
Object result = OgnlRuntime.getProperty( context, source, property );
if ( result == null )
{
result =
OgnlRuntime.getNullHandler( OgnlRuntime.getTargetClass( source ) ).nullPropertyValue( context, source,
property );
}
return result;
}
protected void setValueBody( OgnlContext context, Object target, Object value )
throws OgnlException
{
OgnlRuntime.setProperty( context, target, getProperty( context, target ), value );
}
public boolean isNodeSimpleProperty( OgnlContext context )
throws OgnlException
{
return ( children != null ) && ( children.length == 1 ) && ( (SimpleNode) children[0] ).isConstant( context );
}
public Class getGetterClass()
{
return getterClass;
}
public Class getSetterClass()
{
return setterClass;
}
public String toGetSourceString( OgnlContext context, Object target )
{
if ( context.getCurrentObject() == null )
{
throw new UnsupportedCompilationException( "Current target is null." );
}
String result = "";
Method m = null;
try
{
/*
* System.out.println("astproperty is indexed? : " + isIndexedAccess() + " child: " +
* _children[0].getClass().getName() + " target: " + target.getClass().getName() + " current object: " +
* context.getCurrentObject().getClass().getName());
*/
Node child = children[0];
if ( isIndexedAccess() )
{
Object value = child.getValue( context, context.getRoot() );
if ( value == null || DynamicSubscript.class.isAssignableFrom( value.getClass() ) )
{
throw new UnsupportedCompilationException(
"Value passed as indexed property was null or not supported." );
}
// Get root cast string if the child is a type that needs it (like a nested ASTProperty)
String srcString = getSourceString( context, child );
if ( context.get( "_indexedMethod" ) != null )
{
m = (Method) context.remove( "_indexedMethod" );
getterClass = m.getReturnType();
Object indexedValue = OgnlRuntime.callMethod( context, target, m.getName(), new Object[]{ value } );
context.setCurrentType( getterClass );
context.setCurrentObject( indexedValue );
context.setCurrentAccessor(
OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
return "." + m.getName() + "(" + srcString + ")";
}
PropertyAccessor propertyAccessor = OgnlRuntime.getPropertyAccessor( target.getClass() );
// System.out.println("child value : " + _children[0].getValue(context, context.getCurrentObject())
// + " using propaccessor " + p.getClass().getName()
// + " and srcString " + srcString + " on target: " + target);
Object currentObject = context.getCurrentObject();
if ( ASTConst.class.isInstance( child ) && Number.class.isInstance( currentObject ) )
{
context.setCurrentType( OgnlRuntime.getPrimitiveWrapperClass( currentObject.getClass() ) );
}
Object indexValue = propertyAccessor.getProperty( context, target, value );
result = propertyAccessor.getSourceAccessor( context, target, srcString );
getterClass = context.getCurrentType();
context.setCurrentObject( indexValue );
return result;
}
String name = ( (ASTConst) child ).getValue().toString();
target = getTarget( context, target, name );
PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor( context.getCurrentObject().getClass(), name );
if ( pd != null && pd.getReadMethod() != null && !context.getMemberAccess().isAccessible( context,
context.getCurrentObject(),
pd.getReadMethod(),
name ) )
{
throw new UnsupportedCompilationException( "Member access forbidden for property " + name + " on class "
+ context.getCurrentObject().getClass() );
}
if ( this.getIndexedPropertyType( context, context.getCurrentObject() ) > 0 && pd != null )
{
// if an indexed method accessor need to use special property descriptors to find methods
if ( pd instanceof IndexedPropertyDescriptor )
{
m = ( (IndexedPropertyDescriptor) pd ).getIndexedReadMethod();
}
else
{
if ( !(pd instanceof ObjectIndexedPropertyDescriptor) ) {
throw new OgnlException( "property '" + name + "' is not an indexed property" );
}
m = ( (ObjectIndexedPropertyDescriptor) pd ).getIndexedReadMethod();
}
if ( parent == null )
{
// the above pd will be the wrong result sometimes, such as methods like getValue(int) vs String[]
// getValue()
m = OgnlRuntime.getReadMethod( context.getCurrentObject().getClass(), name );
result = m.getName() + "()";
getterClass = m.getReturnType();
}
else
{
context.put( "_indexedMethod", m );
}
}
else
{
/*
* System.out.println("astproperty trying to get " + name + " on object target: " +
* context.getCurrentObject().getClass().getName() + " current type " + context.getCurrentType() +
* " current accessor " + context.getCurrentAccessor() + " prev type " + context.getPreviousType() +
* " prev accessor " + context.getPreviousAccessor());
*/
PropertyAccessor pa = OgnlRuntime.getPropertyAccessor( context.getCurrentObject().getClass() );
if ( context.getCurrentObject().getClass().isArray() )
{
if ( pd == null )
{
pd = OgnlRuntime.getProperty( context.getCurrentObject().getClass(), name );
if ( pd != null && pd.getReadMethod() != null )
{
m = pd.getReadMethod();
result = pd.getName();
}
else
{
getterClass = int.class;
context.setCurrentAccessor( context.getCurrentObject().getClass() );
context.setCurrentType( int.class );
result = "." + name;
}
}
}
else
{
if ( pd != null && pd.getReadMethod() != null )
{
m = pd.getReadMethod();
result = "." + m.getName() + "()";
}
else if ( pa != null )
{
Object currObj = context.getCurrentObject();
Class currType = context.getCurrentType();
Class prevType = context.getPreviousType();
String srcString = child.toGetSourceString( context, context.getRoot() );
if ( ASTConst.class.isInstance( child ) && String.class.isInstance(
context.getCurrentObject() ) )
{
srcString = "\"" + srcString + "\"";
}
context.setCurrentObject( currObj );
context.setCurrentType( currType );
context.setPreviousType( prevType );
result = pa.getSourceAccessor( context, context.getCurrentObject(), srcString );
getterClass = context.getCurrentType();
}
}
}
}
catch ( Throwable t )
{
throw OgnlOps.castToRuntime( t );
}
// set known property types for NodeType interface when possible
if ( m != null )
{
getterClass = m.getReturnType();
context.setCurrentType( m.getReturnType() );
context.setCurrentAccessor(
OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
}
context.setCurrentObject( target );
return result;
}
Object getTarget( OgnlContext context, Object target, String name )
throws OgnlException
{
Class<?> clazz = context.getCurrentObject().getClass();
if ( !Iterator.class.isAssignableFrom( clazz ) || ( Iterator.class.isAssignableFrom( clazz ) && !name.contains(
"next" ) ) )
{
Object currObj = target;
try
{
target = getValue( context, context.getCurrentObject() );
}
catch ( NoSuchPropertyException e )
{
try
{
target = getValue( context, context.getRoot() );
}
catch ( NoSuchPropertyException ex )
{
// ignore
}
}
finally
{
context.setCurrentObject( currObj );
}
}
return target;
}
Method getIndexedWriteMethod( PropertyDescriptor pd )
{
if ( IndexedPropertyDescriptor.class.isInstance( pd ) )
{
return ( (IndexedPropertyDescriptor) pd ).getIndexedWriteMethod();
}
if ( ObjectIndexedPropertyDescriptor.class.isInstance( pd ) )
{
return ( (ObjectIndexedPropertyDescriptor) pd ).getIndexedWriteMethod();
}
return null;
}
public String toSetSourceString( OgnlContext context, Object target )
{
String result = "";
Method m = null;
if ( context.getCurrentObject() == null )
{
throw new UnsupportedCompilationException( "Current target is null." );
}
/*
* System.out.println("astproperty(setter) is indexed? : " + isIndexedAccess() + " child: " +
* _children[0].getClass().getName() + " target: " + target.getClass().getName() + " children length: " +
* _children.length);
*/
try
{
Node child = children[0];
if ( isIndexedAccess() )
{
Object value = child.getValue( context, context.getRoot() );
if ( value == null )
{
throw new UnsupportedCompilationException(
"Value passed as indexed property is null, can't enhance statement to bytecode." );
}
String srcString = getSourceString( context, child );
// System.out.println("astproperty setter using indexed value " + value + " and srcString: " +
// srcString);
if ( context.get( "_indexedMethod" ) == null ) {
PropertyAccessor propertyAccessor = OgnlRuntime.getPropertyAccessor( target.getClass() );
Object currentObject = context.getCurrentObject();
if ( ASTConst.class.isInstance( child ) && Number.class.isInstance( currentObject ) )
{
context.setCurrentType( OgnlRuntime.getPrimitiveWrapperClass( currentObject.getClass() ) );
}
Object indexValue = propertyAccessor.getProperty( context, target, value );
result = lastChild( context )
? propertyAccessor.getSourceSetter( context, target, srcString )
: propertyAccessor.getSourceAccessor( context, target, srcString );
/*
* System.out.println("ASTProperty using propertyaccessor and isLastChild? " + lastChild(context) +
* " generated source of: " + result + " using accessor class: " + p.getClass().getName());
*/
// result = p.getSourceAccessor(context, target, srcString);
getterClass = context.getCurrentType();
context.setCurrentObject( indexValue );
/*
* PropertyAccessor p = OgnlRuntime.getPropertyAccessor(target.getClass()); if
* (ASTConst.class.isInstance(_children[0]) && Number.class.isInstance(context.getCurrentObject()))
* {
* context.setCurrentType(OgnlRuntime.getPrimitiveWrapperClass(context.getCurrentObject().getClass(
* ))); } result = p.getSourceSetter(context, target, srcString); context.setCurrentObject(value);
* context.setCurrentType(getterClass);
*/
return result;
}
m = (Method) context.remove( "_indexedMethod" );
PropertyDescriptor pd = (PropertyDescriptor) context.remove( "_indexedDescriptor" );
boolean lastChild = lastChild( context );
if ( lastChild )
{
m = getIndexedWriteMethod( pd );
if ( m == null )
{
throw new UnsupportedCompilationException(
"Indexed property has no corresponding write method." );
}
}
setterClass = m.getParameterTypes()[0];
Object indexedValue = null;
if ( !lastChild )
{
indexedValue = OgnlRuntime.callMethod( context, target, m.getName(), new Object[]{ value } );
}
context.setCurrentType( setterClass );
context.setCurrentAccessor(
OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
if ( !lastChild )
{
context.setCurrentObject( indexedValue );
return "." + m.getName() + "(" + srcString + ")";
}
return "." + m.getName() + "(" + srcString + ", $3)";
}
String name = ( (ASTConst) child ).getValue().toString();
// System.out.println(" astprop(setter) : trying to set " + name + " on object target " +
// context.getCurrentObject().getClass().getName());
target = getTarget( context, target, name );
PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor(
OgnlRuntime.getCompiler( context ).getInterfaceClass( context.getCurrentObject().getClass() ), name );
if ( pd != null )
{
Method pdMethod = lastChild( context ) ? pd.getWriteMethod() : pd.getReadMethod();
if ( pdMethod != null && !context.getMemberAccess().isAccessible( context, context.getCurrentObject(),
pdMethod, name ) )
{
throw new UnsupportedCompilationException(
"Member access forbidden for property " + name + " on class "
+ context.getCurrentObject().getClass() );
}
}
if ( pd != null && this.getIndexedPropertyType( context, context.getCurrentObject() ) > 0 )
{
// if an indexed method accessor need to use special property descriptors to find methods
if ( pd instanceof IndexedPropertyDescriptor )
{
IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
m = lastChild( context ) ? ipd.getIndexedWriteMethod() : ipd.getIndexedReadMethod();
}
else
{
if ( !(pd instanceof ObjectIndexedPropertyDescriptor) ) {
throw new OgnlException( "property '" + name + "' is not an indexed property" );
}
ObjectIndexedPropertyDescriptor opd = (ObjectIndexedPropertyDescriptor) pd;
m = lastChild( context ) ? opd.getIndexedWriteMethod() : opd.getIndexedReadMethod();
}
if ( parent == null )
{
// the above pd will be the wrong result sometimes, such as methods like getValue(int) vs String[]
// getValue()
m = OgnlRuntime.getWriteMethod( context.getCurrentObject().getClass(), name );
Class parm = m.getParameterTypes()[0];
String cast = parm.isArray() ? ExpressionCompiler.getCastString( parm ) : parm.getName();
result = m.getName() + "((" + cast + ")$3)";
setterClass = parm;
}
else
{
context.put( "_indexedMethod", m );
context.put( "_indexedDescriptor", pd );
}
}
else
{
PropertyAccessor pa = OgnlRuntime.getPropertyAccessor( context.getCurrentObject().getClass() );
/*
* System.out.println("astproperty trying to set " + name + " on object target: " +
* context.getCurrentObject().getClass().getName() + " using propertyaccessor type: " + pa);
*/
if ( target != null )
{
setterClass = target.getClass();
}
if ( parent != null && pd != null && pa == null )
{
m = pd.getReadMethod();
result = m.getName() + "()";
}
else
{
if ( context.getCurrentObject().getClass().isArray() )
{
result = "";
}
else if ( pa != null )
{
Object currObj = context.getCurrentObject();
// Class currType = context.getCurrentType();
// Class prevType = context.getPreviousType();
String srcString = child.toGetSourceString( context, context.getRoot() );
if ( ASTConst.class.isInstance( child ) && String.class.isInstance(
context.getCurrentObject() ) )
{
srcString = "\"" + srcString + "\"";
}
context.setCurrentObject( currObj );
// context.setCurrentType(currType);
// context.setPreviousType(prevType);
if ( !lastChild( context ) )
{
result = pa.getSourceAccessor( context, context.getCurrentObject(), srcString );
}
else
{
result = pa.getSourceSetter( context, context.getCurrentObject(), srcString );
}
getterClass = context.getCurrentType();
}
}
}
}
catch ( Throwable t )
{
throw OgnlOps.castToRuntime( t );
}
context.setCurrentObject( target );
if ( m != null )
{
context.setCurrentType( m.getReturnType() );
context.setCurrentAccessor(
OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
}
return result;
}
public <R, P> R accept( NodeVisitor<? extends R, ? super P> visitor, P data )
throws OgnlException
{
return visitor.visit( this, data );
}
private static String getSourceString( OgnlContext context, Node child )
{
String srcString = child.toGetSourceString( context, context.getRoot() );
srcString = ExpressionCompiler.getRootExpression( child, context.getRoot(), context ) + srcString;
if ( ASTChain.class.isInstance( child ) )
{
String cast = (String) context.remove( ExpressionCompiler.PRE_CAST );
if ( cast != null )
{
srcString = cast + srcString;
}
}
if ( ASTConst.class.isInstance( child ) && String.class.isInstance( context.getCurrentObject() ) )
{
srcString = "\"" + srcString + "\"";
}
// System.out.println("indexed getting with child srcString: " + srcString + " value class: " +
// value.getClass() + " and child: " + _children[0].getClass());
return srcString;
}
}