Rename modules {org.springframework.*=>spring-*}

This renaming more intuitively expresses the relationship between
subprojects and the JAR artifacts they produce.

Tracking history across these renames is possible, but it requires
use of the --follow flag to `git log`, for example

    $ git log spring-aop/src/main/java/org/springframework/aop/Advisor.java

will show history up until the renaming event, where

    $ git log --follow spring-aop/src/main/java/org/springframework/aop/Advisor.java

will show history for all changes to the file, before and after the
renaming.

See http://chrisbeams.com/git-diff-across-renamed-directories
This commit is contained in:
Chris Beams
2012-01-20 22:51:02 +01:00
parent b6cb514d38
commit 02a4473c62
5671 changed files with 20 additions and 32 deletions

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* An AccessException is thrown by an accessor if it has an unexpected problem.
*
* @author Andy Clement
* @since 3.0
*/
public class AccessException extends Exception {
/**
* Create an AccessException with a specific message and cause.
* @param message the message
* @param cause the cause
*/
public AccessException(String message, Exception cause) {
super(message, cause);
}
/**
* Create an AccessException with a specific message.
* @param message the message
*/
public AccessException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression;
/**
* A bean resolver can be registered with the evaluation context
* and will kick in for <code>@myBeanName</code> still expressions.
*
* @author Andy Clement
* @since 3.0.3
*/
public interface BeanResolver {
/**
* Look up the named bean and return it.
* @param context the current evaluation context
* @param beanName the name of the bean to lookup
* @return an object representing the bean
* @throws AccessException if there is an unexpected problem resolving the named bean
*/
Object resolve(EvaluationContext context, String beanName) throws AccessException;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
// TODO Is the resolver/executor model too pervasive in this package?
/**
* Executors are built by resolvers and can be cached by the infrastructure to repeat an operation quickly without going
* back to the resolvers. For example, the particular constructor to run on a class may be discovered by the reflection
* constructor resolver - it will then build a ConstructorExecutor that executes that constructor and the
* ConstructorExecutor can be reused without needing to go back to the resolver to discover the constructor again.
*
* They can become stale, and in that case should throw an AccessException - this will cause the infrastructure to go
* back to the resolvers to ask for a new one.
*
* @author Andy Clement
* @since 3.0
*/
public interface ConstructorExecutor {
/**
* Execute a constructor in the specified context using the specified arguments.
* @param context the evaluation context in which the command is being executed
* @param arguments the arguments to the constructor call, should match (in terms of number and type) whatever the
* command will need to run
* @return the new object
* @throws AccessException if there is a problem executing the command or the CommandExecutor is no longer valid
*/
TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
/**
* A constructor resolver attempts locate a constructor and returns a ConstructorExecutor that can be used to invoke
* that constructor. The ConstructorExecutor will be cached but if it 'goes stale' the resolvers will be called again.
*
* @author Andy Clement
* @since 3.0
*/
public interface ConstructorResolver {
/**
* Within the supplied context determine a suitable constructor on the supplied type that can handle the
* specified arguments. Return a ConstructorExecutor that can be used to invoke that constructor
* (or <code>null</code> if no constructor could be found).
* @param context the current evaluation context
* @param typeName the type upon which to look for the constructor
* @param argumentTypes the arguments that the constructor must be able to handle
* @return a ConstructorExecutor that can invoke the constructor, or null if non found
*/
ConstructorExecutor resolve(EvaluationContext context, String typeName, List<TypeDescriptor> argumentTypes)
throws AccessException;
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression;
import java.util.List;
/**
* Expressions are executed in an evaluation context. It is in this context that references
* are resolved when encountered during expression evaluation.
*
* <p>There is a default implementation of the EvaluationContext,
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}
* that can be extended, rather than having to implement everything.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public interface EvaluationContext {
/**
* @return the default root context object against which unqualified properties/methods/etc
* should be resolved. This can be overridden when evaluating an expression.
*/
TypedValue getRootObject();
/**
* @return a list of resolvers that will be asked in turn to locate a constructor
*/
List<ConstructorResolver> getConstructorResolvers();
/**
* @return a list of resolvers that will be asked in turn to locate a method
*/
List<MethodResolver> getMethodResolvers();
/**
* @return a list of accessors that will be asked in turn to read/write a property
*/
List<PropertyAccessor> getPropertyAccessors();
/**
* @return a type locator that can be used to find types, either by short or fully qualified name.
*/
TypeLocator getTypeLocator();
/**
* @return a type converter that can convert (or coerce) a value from one type to another.
*/
TypeConverter getTypeConverter();
/**
* @return a type comparator for comparing pairs of objects for equality.
*/
TypeComparator getTypeComparator();
/**
* @return an operator overloader that may support mathematical operations
* between more than the standard set of types
*/
OperatorOverloader getOperatorOverloader();
/**
* @return a bean resolver that can look up beans by name
*/
BeanResolver getBeanResolver();
/**
* Set a named variable within this evaluation context to a specified value.
* @param name variable to set
* @param value value to be placed in the variable
*/
void setVariable(String name, Object value);
/**
* Look up a named variable within this evaluation context.
* @param name variable to lookup
* @return the value of the variable
*/
Object lookupVariable(String name);
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* Represent an exception that occurs during expression evaluation.
*
* @author Andy Clement
* @since 3.0
*/
public class EvaluationException extends ExpressionException {
/**
* Creates a new expression evaluation exception.
* @param position the position in the expression where the problem occurred
* @param message description of the problem that occurred
*/
public EvaluationException(int position, String message) {
super(position, message);
}
/**
* Creates a new expression evaluation exception.
* @param expressionString the expression that could not be evaluated
* @param message description of the problem that occurred
*/
public EvaluationException(String expressionString, String message) {
super(expressionString, message);
}
/**
* Creates a new expression evaluation exception.
* @param position the position in the expression where the problem occurred
* @param message description of the problem that occurred
* @param cause the underlying cause of this exception
*/
public EvaluationException(int position, String message, Throwable cause) {
super(position, message, cause);
}
/**
* Creates a new expression evaluation exception.
* @param message description of the problem that occurred
*/
public EvaluationException(String message) {
super(message);
}
public EvaluationException(String message, Throwable cause) {
super(message,cause);
}
}

View File

@@ -0,0 +1,241 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression;
import org.springframework.core.convert.TypeDescriptor;
/**
* An expression capable of evaluating itself against context objects.
* Encapsulates the details of a previously parsed expression string.
* Provides a common abstraction for expression evaluation independent
* of any language like OGNL or the Unified EL.
*
* @author Keith Donald
* @author Andy Clement
* @since 3.0
*/
public interface Expression {
/**
* Evaluate this expression in the default standard context.
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
Object getValue() throws EvaluationException;
/**
* Evaluate this expression against the specified root object
* @param rootObject the root object against which properties/etc will be resolved
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
Object getValue(Object rootObject) throws EvaluationException;
/**
* Evaluate the expression in the default context. If the result of the evaluation does not match (and
* cannot be converted to) the expected result type then an exception will be returned.
* @param desiredResultType the class the caller would like the result to be
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
<T> T getValue(Class<T> desiredResultType) throws EvaluationException;
/**
* Evaluate the expression in the default context against the specified root object. If the
* result of the evaluation does not match (and cannot be converted to) the expected result type
* then an exception will be returned.
* @param rootObject the root object against which properties/etc will be resolved
* @param desiredResultType the class the caller would like the result to be
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
<T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException;
/**
* Evaluate this expression in the provided context and return the result of evaluation.
* @param context the context in which to evaluate the expression
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
Object getValue(EvaluationContext context) throws EvaluationException;
/**
* Evaluate this expression in the provided context and return the result of evaluation, but use
* the supplied root context as an override for any default root object specified in the context.
* @param context the context in which to evaluate the expression
* @param rootObject the root object against which properties/etc will be resolved
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException;
/**
* Evaluate the expression in a specified context which can resolve references to properties, methods, types, etc -
* the type of the evaluation result is expected to be of a particular class and an exception will be thrown if it
* is not and cannot be converted to that type.
* @param context the context in which to evaluate the expression
* @param desiredResultType the class the caller would like the result to be
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
<T> T getValue(EvaluationContext context, Class<T> desiredResultType) throws EvaluationException;
/**
* Evaluate the expression in a specified context which can resolve references to properties, methods, types, etc -
* the type of the evaluation result is expected to be of a particular class and an exception will be thrown if it
* is not and cannot be converted to that type. The supplied root object overrides any default specified on the
* supplied context.
* @param context the context in which to evaluate the expression
* @param rootObject the root object against which properties/etc will be resolved
* @param desiredResultType the class the caller would like the result to be
* @return the evaluation result
* @throws EvaluationException if there is a problem during evaluation
*/
<T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)}
* method using the default context.
* @return the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
Class getValueType() throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)}
* method using the default context.
* @param rootObject the root object against which to evaluate the expression
* @return the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
Class getValueType(Object rootObject) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)}
* method for the given context.
* @param context the context in which to evaluate the expression
* @return the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
Class getValueType(EvaluationContext context) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)}
* method for the given context. The supplied root object overrides any specified in the context.
* @param context the context in which to evaluate the expression
* @param rootObject the root object against which to evaluate the expression
* @return the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)}
* method using the default context.
* @return a type descriptor for the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
TypeDescriptor getValueTypeDescriptor() throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)}
* method using the default context.
* @param rootObject the root object against which to evaluate the expression
* @return a type descriptor for the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)}
* method for the given context.
* @param context the context in which to evaluate the expression
* @return a type descriptor for the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException;
/**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
* the given context. The supplied root object overrides any specified in the context.
* @param context the context in which to evaluate the expression
* @param rootObject the root object against which to evaluate the expression
* @return a type descriptor for the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type
*/
TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException;
/**
* Determine if an expression can be written to, i.e. setValue() can be called.
* @param context the context in which the expression should be checked
* @return true if the expression is writable
* @throws EvaluationException if there is a problem determining if it is writable
*/
boolean isWritable(EvaluationContext context) throws EvaluationException;
/**
* Determine if an expression can be written to, i.e. setValue() can be called.
* The supplied root object overrides any specified in the context.
* @param context the context in which the expression should be checked
* @param rootObject the root object against which to evaluate the expression
* @return true if the expression is writable
* @throws EvaluationException if there is a problem determining if it is writable
*/
boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException;
/**
* Determine if an expression can be written to, i.e. setValue() can be called.
* @param rootObject the root object against which to evaluate the expression
* @return true if the expression is writable
* @throws EvaluationException if there is a problem determining if it is writable
*/
boolean isWritable(Object rootObject) throws EvaluationException;
/**
* Set this expression in the provided context to the value provided.
*
* @param context the context in which to set the value of the expression
* @param value the new value
* @throws EvaluationException if there is a problem during evaluation
*/
void setValue(EvaluationContext context, Object value) throws EvaluationException;
/**
* Set this expression in the provided context to the value provided.
* @param rootObject the root object against which to evaluate the expression
* @param value the new value
* @throws EvaluationException if there is a problem during evaluation
*/
void setValue(Object rootObject, Object value) throws EvaluationException;
/**
* Set this expression in the provided context to the value provided.
* The supplied root object overrides any specified in the context.
* @param context the context in which to set the value of the expression
* @param rootObject the root object against which to evaluate the expression
* @param value the new value
* @throws EvaluationException if there is a problem during evaluation
*/
void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException;
/**
* Returns the original string used to create this expression, unmodified.
* @return the original expression string
*/
String getExpressionString();
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* Super class for exceptions that can occur whilst processing expressions
*
* @author Andy Clement
* @since 3.0
*/
public class ExpressionException extends RuntimeException {
protected String expressionString;
protected int position; // -1 if not known - but should be known in all reasonable cases
/**
* Creates a new expression exception.
* @param expressionString the expression string
* @param message a descriptive message
*/
public ExpressionException(String expressionString, String message) {
super(message);
this.position = -1;
this.expressionString = expressionString;
}
/**
* Creates a new expression exception.
* @param expressionString the expression string
* @param position the position in the expression string where the problem occurred
* @param message a descriptive message
*/
public ExpressionException(String expressionString, int position, String message) {
super(message);
this.position = position;
this.expressionString = expressionString;
}
/**
* Creates a new expression exception.
* @param position the position in the expression string where the problem occurred
* @param message a descriptive message
*/
public ExpressionException(int position, String message) {
super(message);
this.position = position;
}
/**
* Creates a new expression exception.
* @param position the position in the expression string where the problem occurred
* @param message a descriptive message
* @param cause the underlying cause of this exception
*/
public ExpressionException(int position, String message, Throwable cause) {
super(message,cause);
this.position = position;
}
/**
* Creates a new expression exception.
* @param message a descriptive message
*/
public ExpressionException(String message) {
super(message);
}
public ExpressionException(String message, Throwable cause) {
super(message,cause);
}
public String toDetailedString() {
StringBuilder output = new StringBuilder();
if (expressionString!=null) {
output.append("Expression '");
output.append(expressionString);
output.append("'");
if (position!=-1) {
output.append(" @ ");
output.append(position);
}
output.append(": ");
}
output.append(getMessage());
return output.toString();
}
public final String getExpressionString() {
return this.expressionString;
}
public final int getPosition() {
return position;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression;
/**
* This exception wraps (as cause) a checked exception thrown by some method that SpEL invokes.
* It differs from a SpelEvaluationException because this indicates the occurrence of a checked exception
* that the invoked method was defined to throw. SpelEvaluationExceptions are for handling (and wrapping)
* unexpected exceptions.
*
* @author Andy Clement
* @since 3.0.3
*/
public class ExpressionInvocationTargetException extends EvaluationException {
public ExpressionInvocationTargetException(int position, String message, Throwable cause) {
super(position, message, cause);
}
public ExpressionInvocationTargetException(int position, String message) {
super(position, message);
}
public ExpressionInvocationTargetException(String expressionString, String message) {
super(expressionString, message);
}
public ExpressionInvocationTargetException(String message, Throwable cause) {
super(message, cause);
}
public ExpressionInvocationTargetException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* Parses expression strings into compiled expressions that can be evaluated.
* Supports parsing templates as well as standard expression strings.
*
* @author Keith Donald
* @author Andy Clement
* @since 3.0
*/
public interface ExpressionParser {
/**
* Parse the expression string and return an Expression object you can use for repeated evaluation.
* <p>Some examples:
* <pre>
* 3 + 4
* name.firstName
* </pre>
* @param expressionString the raw expression string to parse
* @return an evaluator for the parsed expression
* @throws ParseException an exception occurred during parsing
*/
Expression parseExpression(String expressionString) throws ParseException;
/**
* Parse the expression string and return an Expression object you can use for repeated evaluation.
* <p>Some examples:
* <pre>
* 3 + 4
* name.firstName
* </pre>
* @param expressionString the raw expression string to parse
* @param context a context for influencing this expression parsing routine (optional)
* @return an evaluator for the parsed expression
* @throws ParseException an exception occurred during parsing
*/
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* MethodExecutors are built by the resolvers and can be cached by the infrastructure to repeat an operation quickly
* without going back to the resolvers. For example, the particular method to run on an object may be discovered by the
* reflection method resolver - it will then build a MethodExecutor that executes that method and the MethodExecutor can
* be reused without needing to go back to the resolver to discover the method again.
*
* <p>They can become stale, and in that case should throw an AccessException - this will cause the infrastructure to go
* back to the resolvers to ask for a new one.
*
* @author Andy Clement
* @since 3.0
*/
public interface MethodExecutor {
/**
* Execute a command using the specified arguments, and using the specified expression state.
* @param context the evaluation context in which the command is being executed
* @param target the target object of the call - null for static methods
* @param arguments the arguments to the executor, should match (in terms of number and type) whatever the
* command will need to run
* @return the value returned from execution
* @throws AccessException if there is a problem executing the command or the MethodExecutor is no longer valid
*/
TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException;
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
import java.lang.reflect.Method;
import java.util.List;
/**
* MethodFilter instances allow SpEL users to fine tune the behaviour of the method resolution
* process. Method resolution (which translates from a method name in an expression to a real
* method to invoke) will normally retrieve candidate methods for invocation via a simple call
* to 'Class.getMethods()' and will choose the first one that is suitable for the
* input parameters. By registering a MethodFilter the user can receive a callback
* and change the methods that will be considered suitable.
*
* @author Andy Clement
* @since 3.0.1
*/
public interface MethodFilter {
/**
* Called by the method resolver to allow the SpEL user to organize the list of candidate
* methods that may be invoked. The filter can remove methods that should not be
* considered candidates and it may sort the results. The resolver will then search
* through the methods as returned from the filter when looking for a suitable
* candidate to invoke.
*
* @param methods the full list of methods the resolver was going to choose from
* @return a possible subset of input methods that may be sorted by order of relevance
*/
List<Method> filter(List<Method> methods);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
/**
* A method resolver attempts locate a method and returns a command executor that can be used to invoke that method.
* The command executor will be cached but if it 'goes stale' the resolvers will be called again.
*
* @author Andy Clement
* @since 3.0
*/
public interface MethodResolver {
/**
* Within the supplied context determine a suitable method on the supplied object that can handle the
* specified arguments. Return a MethodExecutor that can be used to invoke that method
* (or <code>null</code> if no method could be found).
* @param context the current evaluation context
* @param targetObject the object upon which the method is being called
* @param argumentTypes the arguments that the constructor must be able to handle
* @return a MethodExecutor that can invoke the method, or null if the method cannot be found
*/
MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
List<TypeDescriptor> argumentTypes) throws AccessException;
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* Supported operations that an {@link OperatorOverloader} can implement for any pair of operands.
*
* @author Andy Clement
* @since 3.0
*/
public enum Operation {
ADD, SUBTRACT, DIVIDE, MULTIPLY, MODULUS, POWER
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* By default the mathematical operators {@link Operation} support simple types like numbers. By providing an
* implementation of OperatorOverloader, a user of the expression language can support these operations on other types.
*
* @author Andy Clement
* @since 3.0
*/
public interface OperatorOverloader {
/**
* Return true if the operator overloader supports the specified operation
* between the two operands and so should be invoked to handle it.
* @param operation the operation to be performed
* @param leftOperand the left operand
* @param rightOperand the right operand
* @return true if the OperatorOverloader supports the specified operation between the two operands
* @throws EvaluationException if there is a problem performing the operation
*/
boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand)
throws EvaluationException;
/**
* Execute the specified operation on two operands, returning a result.
* See {@link Operation} for supported operations.
* @param operation the operation to be performed
* @param leftOperand the left operand
* @param rightOperand the right operand
* @return the result of performing the operation on the two operands
* @throws EvaluationException if there is a problem performing the operation
*/
Object operate(Operation operation, Object leftOperand, Object rightOperand)
throws EvaluationException;
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* Represent an exception that occurs during expression parsing.
*
* @author Andy Clement
* @since 3.0
*/
public class ParseException extends ExpressionException {
/**
* Creates a new expression parsing exception.
* @param expressionString the expression string that could not be parsed
* @param position the position in the expression string where the problem occurred
* @param message description of the problem that occurred
*/
public ParseException(String expressionString, int position, String message) {
super(expressionString, position, message);
}
/**
* Creates a new expression parsing exception.
* @param position the position in the expression string where the problem occurred
* @param message description of the problem that occurred
* @param cause the underlying cause of this exception
*/
public ParseException(int position, String message, Throwable cause) {
super(position, message, cause);
}
/**
* Creates a new expression parsing exception.
* @param position the position in the expression string where the problem occurred
* @param message description of the problem that occurred
*/
public ParseException(int position, String message) {
super(position, message);
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* Input provided to an expression parser that can influence an expression parsing/compilation routine.
*
* @author Keith Donald
* @author Andy Clement
* @since 3.0
*/
public interface ParserContext {
/**
* Whether or not the expression being parsed is a template. A template expression consists of literal text that can
* be mixed with evaluatable blocks. Some examples:
*
* <pre>
* Some literal text
* Hello #{name.firstName}!
* #{3 + 4}
* </pre>
*
* @return true if the expression is a template, false otherwise
*/
boolean isTemplate();
/**
* For template expressions, returns the prefix that identifies the start of an expression block within a string.
* For example: "${"
*
* @return the prefix that identifies the start of an expression
*/
String getExpressionPrefix();
/**
* For template expressions, return the prefix that identifies the end of an expression block within a string.
* For example: "}"
*
* @return the suffix that identifies the end of an expression
*/
String getExpressionSuffix();
/**
* The default ParserContext implementation that enables template expression parsing mode.
* The expression prefix is #{ and the expression suffix is }.
* @see #isTemplate()
*/
public static final ParserContext TEMPLATE_EXPRESSION = new ParserContext() {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
};
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* A property accessor is able to read (and possibly write) to object properties. The interface places no restrictions
* and so implementors are free to access properties directly as fields or through getters or in any other way they see
* as appropriate. A resolver can optionally specify an array of target classes for which it should be called - but if
* it returns null from getSpecificTargetClasses() then it will be called for all property references and given a chance
* to determine if it can read or write them. Property resolvers are considered to be ordered and each will be called in
* turn. The only rule that affects the call order is that any naming the target class directly in
* getSpecifiedTargetClasses() will be called first, before the general resolvers.
*
* @author Andy Clement
* @since 3.0
*/
public interface PropertyAccessor {
/**
* Return an array of classes for which this resolver should be called. Returning null indicates this is a general
* resolver that can be called in an attempt to resolve a property on any type.
* @return an array of classes that this resolver is suitable for (or null if a general resolver)
*/
Class[] getSpecificTargetClasses();
/**
* Called to determine if a resolver instance is able to access a specified property on a specified target object.
* @param context the evaluation context in which the access is being attempted
* @param target the target object upon which the property is being accessed
* @param name the name of the property being accessed
* @return true if this resolver is able to read the property
* @throws AccessException if there is any problem determining whether the property can be read
*/
boolean canRead(EvaluationContext context, Object target, String name) throws AccessException;
/**
* Called to read a property from a specified target object
* @param context the evaluation context in which the access is being attempted
* @param target the target object upon which the property is being accessed
* @param name the name of the property being accessed
* @return a TypedValue object wrapping the property value read and a type descriptor for it
* @throws AccessException if there is any problem accessing the property value
*/
TypedValue read(EvaluationContext context, Object target, String name) throws AccessException;
/**
* Called to determine if a resolver instance is able to write to a specified property on a specified target object.
* @param context the evaluation context in which the access is being attempted
* @param target the target object upon which the property is being accessed
* @param name the name of the property being accessed
* @return true if this resolver is able to write to the property
* @throws AccessException if there is any problem determining whether the property can be written to
*/
boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException;
/**
* Called to write to a property on a specified target object. Should only succeed if canWrite() also returns true.
* @param context the evaluation context in which the access is being attempted
* @param target the target object upon which the property is being accessed
* @param name the name of the property being accessed
* @param newValue the new value for the property
* @throws AccessException if there is any problem writing to the property value
*/
void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException;
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* Instances of a type comparator should be able to compare pairs of objects for equality, the specification of the
* return value is the same as for {@link Comparable}.
*
* @author Andy Clement
* @since 3.0
*/
public interface TypeComparator {
/**
* Compare two objects.
* @param firstObject the first object
* @param secondObject the second object
* @return 0 if they are equal, <0 if the first is smaller than the second, or >0 if the first is larger than the
* second
* @throws EvaluationException if a problem occurs during comparison (or they are not comparable)
*/
int compare(Object firstObject, Object secondObject) throws EvaluationException;
/**
* Return true if the comparator can compare these two objects
* @param firstObject the first object
* @param secondObject the second object
* @return true if the comparator can compare these objects
*/
boolean canCompare(Object firstObject, Object secondObject);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression;
import org.springframework.core.convert.TypeDescriptor;
/**
* A type converter can convert values between different types encountered
* during expression evaluation. This is an SPI for the expression parser;
* see {@link org.springframework.core.convert.ConversionService} for the
* primary user API to Spring's conversion facilities.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public interface TypeConverter {
/**
* Return true if the type converter can convert the specified type to the desired target type.
* @param sourceType a type descriptor that describes the source type
* @param targetType a type descriptor that describes the requested result type
* @return true if that conversion can be performed
*/
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
/**
* Convert (may coerce) a value from one type to another, for example from a boolean to a string.
* The typeDescriptor parameter enables support for typed collections - if the caller really wishes they
* can have a List&lt;Integer&gt; for example, rather than simply a List.
* @param value the value to be converted
* @param sourceType a type descriptor that supplies extra information about the source object
* @param targetType a type descriptor that supplies extra information about the requested result type
* @return the converted value
* @throws EvaluationException if conversion is not possible
*/
Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression;
/**
* Implementors of this interface are expected to be able to locate types. They may use custom classloaders
* or the and deal with common package prefixes (java.lang, etc) however they wish. See
* {@link org.springframework.expression.spel.support.StandardTypeLocator} for an example implementation.
*
* @author Andy Clement
* @since 3.0
*/
public interface TypeLocator {
/**
* Find a type by name. The name may or may not be fully qualified (eg. String or java.lang.String)
* @param typename the type to be located
* @return the class object representing that type
* @throws EvaluationException if there is a problem finding it
*/
Class<?> findType(String typename) throws EvaluationException;
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression;
import org.springframework.core.convert.TypeDescriptor;
/**
* Encapsulates an object and a type descriptor that describes it.
* The type descriptor can hold generic information that would not be
* accessible through a simple <code>getClass()</code> call on the object.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class TypedValue {
public static final TypedValue NULL = new TypedValue(null);
private final Object value;
private TypeDescriptor typeDescriptor;
/**
* Create a TypedValue for a simple object. The type descriptor is inferred
* from the object, so no generic information is preserved.
* @param value the object value
*/
public TypedValue(Object value) {
this.value = value;
this.typeDescriptor = null; // initialized when/if requested
}
/**
* Create a TypedValue for a particular value with a particular type descriptor.
* @param value the object value
* @param typeDescriptor a type descriptor describing the type of the value
*/
public TypedValue(Object value, TypeDescriptor typeDescriptor) {
this.value = value;
this.typeDescriptor = typeDescriptor;
}
public Object getValue() {
return this.value;
}
public TypeDescriptor getTypeDescriptor() {
if (this.typeDescriptor == null) {
this.typeDescriptor = TypeDescriptor.forObject(this.value);
}
return this.typeDescriptor;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("TypedValue: '").append(this.value).append("' of [").append(getTypeDescriptor() + "]");
return str.toString();
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.common;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
/**
* Represents a template expression broken into pieces. Each piece will be an Expression but pure text parts to the
* template will be represented as LiteralExpression objects. An example of a template expression might be:
*
* <pre class="code">
* &quot;Hello ${getName()}&quot;</pre>
*
* which will be represented as a CompositeStringExpression of two parts. The first part being a
* LiteralExpression representing 'Hello ' and the second part being a real expression that will
* call <code>getName()</code> when invoked.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class CompositeStringExpression implements Expression {
private final String expressionString;
/** The array of expressions that make up the composite expression */
private final Expression[] expressions;
public CompositeStringExpression(String expressionString, Expression[] expressions) {
this.expressionString = expressionString;
this.expressions = expressions;
}
public final String getExpressionString() {
return this.expressionString;
}
public String getValue() throws EvaluationException {
StringBuilder sb = new StringBuilder();
for (Expression expression : this.expressions) {
String value = expression.getValue(String.class);
if (value != null) {
sb.append(value);
}
}
return sb.toString();
}
public String getValue(Object rootObject) throws EvaluationException {
StringBuilder sb = new StringBuilder();
for (Expression expression : this.expressions) {
String value = expression.getValue(rootObject, String.class);
if (value != null) {
sb.append(value);
}
}
return sb.toString();
}
public String getValue(EvaluationContext context) throws EvaluationException {
StringBuilder sb = new StringBuilder();
for (Expression expression : this.expressions) {
String value = expression.getValue(context, String.class);
if (value != null) {
sb.append(value);
}
}
return sb.toString();
}
public String getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
StringBuilder sb = new StringBuilder();
for (Expression expression : this.expressions) {
String value = expression.getValue(context, rootObject, String.class);
if (value != null) {
sb.append(value);
}
}
return sb.toString();
}
public Class getValueType(EvaluationContext context) {
return String.class;
}
public Class getValueType() {
return String.class;
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) {
return TypeDescriptor.valueOf(String.class);
}
public TypeDescriptor getValueTypeDescriptor() {
return TypeDescriptor.valueOf(String.class);
}
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression");
}
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
Object value = getValue(context);
return ExpressionUtils.convert(context, value, expectedResultType);
}
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
Object value = getValue();
return ExpressionUtils.convert(null, value, expectedResultType);
}
public boolean isWritable(EvaluationContext context) {
return false;
}
public Expression[] getExpressions() {
return expressions;
}
public <T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
Object value = getValue(rootObject);
return ExpressionUtils.convert(null, value, desiredResultType);
}
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType)
throws EvaluationException {
Object value = getValue(context,rootObject);
return ExpressionUtils.convert(context, value, desiredResultType);
}
public Class getValueType(Object rootObject) throws EvaluationException {
return String.class;
}
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
return String.class;
}
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
return TypeDescriptor.valueOf(String.class);
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException {
return TypeDescriptor.valueOf(String.class);
}
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
return false;
}
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression");
}
public boolean isWritable(Object rootObject) throws EvaluationException {
return false;
}
public void setValue(Object rootObject, Object value) throws EvaluationException {
throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression");
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.common;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.util.ClassUtils;
/**
* Common utility functions that may be used by any Expression Language provider.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public abstract class ExpressionUtils {
/**
* Determines if there is a type converter available in the specified context and attempts to use it to convert the
* supplied value to the specified type. Throws an exception if conversion is not possible.
* @param context the evaluation context that may define a type converter
* @param value the value to convert (may be null)
* @param targetType the type to attempt conversion to
* @return the converted value
* @throws EvaluationException if there is a problem during conversion or conversion of the value to the specified
* type is not supported
*/
public static <T> T convert(EvaluationContext context, Object value, Class<T> targetType) throws EvaluationException {
// TODO remove this function over time and use the one it delegates to
return convertTypedValue(context,new TypedValue(value),targetType);
}
/**
* Determines if there is a type converter available in the specified context and attempts to use it to convert the
* supplied value to the specified type. Throws an exception if conversion is not possible.
* @param context the evaluation context that may define a type converter
* @param typedValue the value to convert and a type descriptor describing it
* @param targetType the type to attempt conversion to
* @return the converted value
* @throws EvaluationException if there is a problem during conversion or conversion of the value to the specified
* type is not supported
*/
@SuppressWarnings("unchecked")
public static <T> T convertTypedValue(EvaluationContext context, TypedValue typedValue, Class<T> targetType) {
Object value = typedValue.getValue();
if (targetType == null || ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
if (context != null) {
return (T) context.getTypeConverter().convertValue(value, typedValue.getTypeDescriptor(), TypeDescriptor.valueOf(targetType));
}
throw new EvaluationException("Cannot convert value '" + value + "' to type '" + targetType.getName() + "'");
}
/**
* Attempt to convert a typed value to an int using the supplied type converter.
*/
public static int toInt(TypeConverter typeConverter, TypedValue typedValue) {
return (Integer) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(),
TypeDescriptor.valueOf(Integer.class));
}
/**
* Attempt to convert a typed value to a boolean using the supplied type converter.
*/
public static boolean toBoolean(TypeConverter typeConverter, TypedValue typedValue) {
return (Boolean) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(),
TypeDescriptor.valueOf(Boolean.class));
}
/**
* Attempt to convert a typed value to a double using the supplied type converter.
*/
public static double toDouble(TypeConverter typeConverter, TypedValue typedValue) {
return (Double) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(),
TypeDescriptor.valueOf(Double.class));
}
/**
* Attempt to convert a typed value to a long using the supplied type converter.
*/
public static long toLong(TypeConverter typeConverter, TypedValue typedValue) {
return (Long) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(), TypeDescriptor
.valueOf(Long.class));
}
/**
* Attempt to convert a typed value to a char using the supplied type converter.
*/
public static char toChar(TypeConverter typeConverter, TypedValue typedValue) {
return (Character) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(),
TypeDescriptor.valueOf(Character.class));
}
/**
* Attempt to convert a typed value to a short using the supplied type converter.
*/
public static short toShort(TypeConverter typeConverter, TypedValue typedValue) {
return (Short) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(), TypeDescriptor
.valueOf(Short.class));
}
/**
* Attempt to convert a typed value to a float using the supplied type converter.
*/
public static float toFloat(TypeConverter typeConverter, TypedValue typedValue) {
return (Float) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(), TypeDescriptor
.valueOf(Float.class));
}
/**
* Attempt to convert a typed value to a byte using the supplied type converter.
*/
public static byte toByte(TypeConverter typeConverter, TypedValue typedValue) {
return (Byte) typeConverter.convertValue(typedValue.getValue(), typedValue.getTypeDescriptor(), TypeDescriptor
.valueOf(Byte.class));
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.common;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
/**
* A very simple hardcoded implementation of the Expression interface that represents a string literal.
* It is used with CompositeStringExpression when representing a template expression which is made up
* of pieces - some being real expressions to be handled by an EL implementation like Spel, and some
* being just textual elements.
*
* @author Andy Clement
* @since 3.0
*/
public class LiteralExpression implements Expression {
/** Fixed literal value of this expression */
private final String literalValue;
public LiteralExpression(String literalValue) {
this.literalValue = literalValue;
}
public final String getExpressionString() {
return this.literalValue;
}
public String getValue() {
return this.literalValue;
}
public String getValue(EvaluationContext context) {
return this.literalValue;
}
public String getValue(Object rootObject) {
return this.literalValue;
}
public Class getValueType(EvaluationContext context) {
return String.class;
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) {
return TypeDescriptor.valueOf(String.class);
}
public TypeDescriptor getValueTypeDescriptor() {
return TypeDescriptor.valueOf(String.class);
}
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
throw new EvaluationException(literalValue, "Cannot call setValue() on a LiteralExpression");
}
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
Object value = getValue(context);
return ExpressionUtils.convert(context, value, expectedResultType);
}
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
Object value = getValue();
return ExpressionUtils.convert(null, value, expectedResultType);
}
public boolean isWritable(EvaluationContext context) {
return false;
}
public Class getValueType() {
return String.class;
}
public <T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
Object value = getValue(rootObject);
return ExpressionUtils.convert(null, value, desiredResultType);
}
public String getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
return this.literalValue;
}
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType) throws EvaluationException {
Object value = getValue(context, rootObject);
return ExpressionUtils.convert(null, value, desiredResultType);
}
public Class getValueType(Object rootObject) throws EvaluationException {
return String.class;
}
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
return String.class;
}
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
return TypeDescriptor.valueOf(String.class);
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException {
return TypeDescriptor.valueOf(String.class);
}
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
return false;
}
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
throw new EvaluationException(literalValue, "Cannot call setValue() on a LiteralExpression");
}
public boolean isWritable(Object rootObject) throws EvaluationException {
return false;
}
public void setValue(Object rootObject, Object value) throws EvaluationException {
throw new EvaluationException(literalValue, "Cannot call setValue() on a LiteralExpression");
}
}

View File

@@ -0,0 +1,279 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression.common;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
/**
* An expression parser that understands templates. It can be subclassed
* by expression parsers that do not offer first class support for templating.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Andy Clement
* @since 3.0
*/
public abstract class TemplateAwareExpressionParser implements ExpressionParser {
/**
* Default ParserContext instance for non-template expressions.
*/
private static final ParserContext NON_TEMPLATE_PARSER_CONTEXT = new ParserContext() {
public String getExpressionPrefix() {
return null;
}
public String getExpressionSuffix() {
return null;
}
public boolean isTemplate() {
return false;
}
};
public Expression parseExpression(String expressionString) throws ParseException {
return parseExpression(expressionString, NON_TEMPLATE_PARSER_CONTEXT);
}
public Expression parseExpression(String expressionString, ParserContext context) throws ParseException {
if (context == null) {
context = NON_TEMPLATE_PARSER_CONTEXT;
}
if (context.isTemplate()) {
return parseTemplate(expressionString, context);
} else {
return doParseExpression(expressionString, context);
}
}
private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException {
if (expressionString.length() == 0) {
return new LiteralExpression("");
}
Expression[] expressions = parseExpressions(expressionString, context);
if (expressions.length == 1) {
return expressions[0];
} else {
return new CompositeStringExpression(expressionString, expressions);
}
}
/**
* Helper that parses given expression string using the configured parser. The expression string can contain any
* number of expressions all contained in "${...}" markers. For instance: "foo${expr0}bar${expr1}". The static
* pieces of text will also be returned as Expressions that just return that static piece of text. As a result,
* evaluating all returned expressions and concatenating the results produces the complete evaluated string.
* Unwrapping is only done of the outermost delimiters found, so the string 'hello ${foo${abc}}' would break into
* the pieces 'hello ' and 'foo${abc}'. This means that expression languages that used ${..} as part of their
* functionality are supported without any problem.
* The parsing is aware of the structure of an embedded expression. It assumes that parentheses '(',
* square brackets '[' and curly brackets '}' must be in pairs within the expression unless they are within a
* string literal and a string literal starts and terminates with a single quote '.
*
* @param expressionString the expression string
* @return the parsed expressions
* @throws ParseException when the expressions cannot be parsed
*/
private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParseException {
List<Expression> expressions = new LinkedList<Expression>();
String prefix = context.getExpressionPrefix();
String suffix = context.getExpressionSuffix();
int startIdx = 0;
while (startIdx < expressionString.length()) {
int prefixIndex = expressionString.indexOf(prefix,startIdx);
if (prefixIndex >= startIdx) {
// an inner expression was found - this is a composite
if (prefixIndex > startIdx) {
expressions.add(createLiteralExpression(context,expressionString.substring(startIdx, prefixIndex)));
}
int afterPrefixIndex = prefixIndex + prefix.length();
int suffixIndex = skipToCorrectEndSuffix(prefix,suffix,expressionString,afterPrefixIndex);
if (suffixIndex == -1) {
throw new ParseException(expressionString, prefixIndex, "No ending suffix '" + suffix +
"' for expression starting at character " + prefixIndex + ": " +
expressionString.substring(prefixIndex));
}
if (suffixIndex == afterPrefixIndex) {
throw new ParseException(expressionString, prefixIndex, "No expression defined within delimiter '" +
prefix + suffix + "' at character " + prefixIndex);
} else {
String expr = expressionString.substring(prefixIndex + prefix.length(), suffixIndex);
expr = expr.trim();
if (expr.length()==0) {
throw new ParseException(expressionString, prefixIndex, "No expression defined within delimiter '" +
prefix + suffix + "' at character " + prefixIndex);
}
expressions.add(doParseExpression(expr, context));
startIdx = suffixIndex + suffix.length();
}
} else {
// no more ${expressions} found in string, add rest as static text
expressions.add(createLiteralExpression(context,expressionString.substring(startIdx)));
startIdx = expressionString.length();
}
}
return expressions.toArray(new Expression[expressions.size()]);
}
private Expression createLiteralExpression(ParserContext context, String text) {
return new LiteralExpression(text);
}
/**
* Return true if the specified suffix can be found at the supplied position in the supplied expression string.
* @param expressionString the expression string which may contain the suffix
* @param pos the start position at which to check for the suffix
* @param suffix the suffix string
* @return
*/
private boolean isSuffixHere(String expressionString,int pos,String suffix) {
int suffixPosition = 0;
for (int i=0;i<suffix.length() && pos<expressionString.length();i++) {
if (expressionString.charAt(pos++)!=suffix.charAt(suffixPosition++)) {
return false;
}
}
if (suffixPosition!=suffix.length()) {
// the expressionString ran out before the suffix could entirely be found
return false;
}
return true;
}
/**
* Copes with nesting, for example '${...${...}}' where the correct end for the first ${ is the final }.
*
* @param prefix the prefix
* @param suffix the suffix
* @param expressionString the expression string
* @param afterPrefixIndex the most recently found prefix location for which the matching end suffix is being sought
* @return the position of the correct matching nextSuffix or -1 if none can be found
*/
private int skipToCorrectEndSuffix(String prefix, String suffix, String expressionString, int afterPrefixIndex) throws ParseException {
// Chew on the expression text - relying on the rules:
// brackets must be in pairs: () [] {}
// string literals are "..." or '...' and these may contain unmatched brackets
int pos = afterPrefixIndex;
int maxlen = expressionString.length();
int nextSuffix = expressionString.indexOf(suffix,afterPrefixIndex);
if (nextSuffix ==-1 ) {
return -1; // the suffix is missing
}
Stack<Bracket> stack = new Stack<Bracket>();
while (pos<maxlen) {
if (isSuffixHere(expressionString,pos,suffix) && stack.isEmpty()) {
break;
}
char ch = expressionString.charAt(pos);
switch (ch) {
case '{': case '[': case '(':
stack.push(new Bracket(ch,pos));
break;
case '}':case ']':case ')':
if (stack.isEmpty()) {
throw new ParseException(expressionString, pos, "Found closing '"+ch+"' at position "+pos+" without an opening '"+Bracket.theOpenBracketFor(ch)+"'");
}
Bracket p = stack.pop();
if (!p.compatibleWithCloseBracket(ch)) {
throw new ParseException(expressionString, pos, "Found closing '"+ch+"' at position "+pos+" but most recent opening is '"+p.bracket+"' at position "+p.pos);
}
break;
case '\'':
case '"':
// jump to the end of the literal
int endLiteral = expressionString.indexOf(ch,pos+1);
if (endLiteral==-1) {
throw new ParseException(expressionString, pos, "Found non terminating string literal starting at position "+pos);
}
pos=endLiteral;
break;
}
pos++;
}
if (!stack.isEmpty()) {
Bracket p = stack.pop();
throw new ParseException(expressionString, p.pos, "Missing closing '"+Bracket.theCloseBracketFor(p.bracket)+"' for '"+p.bracket+"' at position "+p.pos);
}
if (!isSuffixHere(expressionString, pos, suffix)) {
return -1;
}
return pos;
}
/**
* This captures a type of bracket and the position in which it occurs in the expression. The positional
* information is used if an error has to be reported because the related end bracket cannot be found.
* Bracket is used to describe: square brackets [] round brackets () and curly brackets {}
*/
private static class Bracket {
char bracket;
int pos;
Bracket(char bracket,int pos) {
this.bracket = bracket;
this.pos = pos;
}
boolean compatibleWithCloseBracket(char closeBracket) {
if (bracket=='{') {
return closeBracket=='}';
} else if (bracket=='[') {
return closeBracket==']';
}
return closeBracket==')';
}
static char theOpenBracketFor(char closeBracket) {
if (closeBracket=='}') {
return '{';
} else if (closeBracket==']') {
return '[';
}
return '(';
}
static char theCloseBracketFor(char openBracket) {
if (openBracket=='{') {
return '}';
} else if (openBracket=='[') {
return ']';
}
return ')';
}
}
/**
* Actually parse the expression string and return an Expression object.
* @param expressionString the raw expression string to parse
* @param context a context for influencing this expression parsing routine (optional)
* @return an evaluator for the parsed expression
* @throws ParseException an exception occurred during parsing
*/
protected abstract Expression doParseExpression(String expressionString, ParserContext context)
throws ParseException;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.common;
import org.springframework.expression.ParserContext;
/**
* Configurable {@link ParserContext} implementation for template parsing.
* Expects the expression prefix and suffix as constructor arguments.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class TemplateParserContext implements ParserContext {
private final String expressionPrefix;
private final String expressionSuffix;
/**
* Create a new TemplateParserContext with the default "#{" prefix and "}" suffix.
*/
public TemplateParserContext() {
this("#{", "}");
}
/**
* Create a new TemplateParserContext for the given prefix and suffix.
* @param expressionPrefix the expression prefix to use
* @param expressionSuffix the expression suffix to use
*/
public TemplateParserContext(String expressionPrefix, String expressionSuffix) {
this.expressionPrefix = expressionPrefix;
this.expressionSuffix = expressionSuffix;
}
public final boolean isTemplate() {
return true;
}
public final String getExpressionPrefix() {
return this.expressionPrefix;
}
public final String getExpressionSuffix() {
return this.expressionSuffix;
}
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression.spel;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.TypedValue;
/**
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to it are not seen by other
* expressions but it gives a place to hold local variables and for component expressions in a compound expression to
* communicate state. This is in contrast to the EvaluationContext, which is shared amongst expression evaluations, and
* any changes to it will be seen by other expressions or any code that chooses to ask questions of the context.
*
* <p>It also acts as a place for to define common utility routines that the various Ast nodes might need.
*
* @author Andy Clement
* @since 3.0
*/
public class ExpressionState {
private final EvaluationContext relatedContext;
private Stack<VariableScope> variableScopes;
private Stack<TypedValue> contextObjects;
private final TypedValue rootObject;
private SpelParserConfiguration configuration;
public ExpressionState(EvaluationContext context) {
this.relatedContext = context;
this.rootObject = context.getRootObject();
}
public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) {
this.relatedContext = context;
this.configuration = configuration;
this.rootObject = context.getRootObject();
}
public ExpressionState(EvaluationContext context, TypedValue rootObject) {
this.relatedContext = context;
this.rootObject = rootObject;
}
public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) {
this.relatedContext = context;
this.configuration = configuration;
this.rootObject = rootObject;
}
private void ensureVariableScopesInitialized() {
if (this.variableScopes == null) {
this.variableScopes = new Stack<VariableScope>();
// top level empty variable scope
this.variableScopes.add(new VariableScope());
}
}
/**
* The active context object is what unqualified references to properties/etc are resolved against.
*/
public TypedValue getActiveContextObject() {
if (this.contextObjects==null || this.contextObjects.isEmpty()) {
return this.rootObject;
}
return this.contextObjects.peek();
}
public void pushActiveContextObject(TypedValue obj) {
if (this.contextObjects==null) {
this.contextObjects = new Stack<TypedValue>();
}
this.contextObjects.push(obj);
}
public void popActiveContextObject() {
if (this.contextObjects==null) {
this.contextObjects = new Stack<TypedValue>();
}
this.contextObjects.pop();
}
public TypedValue getRootContextObject() {
return this.rootObject;
}
public void setVariable(String name, Object value) {
this.relatedContext.setVariable(name, value);
}
public TypedValue lookupVariable(String name) {
Object value = this.relatedContext.lookupVariable(name);
if (value == null) {
return TypedValue.NULL;
}
else {
return new TypedValue(value);
}
}
public TypeComparator getTypeComparator() {
return this.relatedContext.getTypeComparator();
}
public Class<?> findType(String type) throws EvaluationException {
return this.relatedContext.getTypeLocator().findType(type);
}
public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
return this.relatedContext.getTypeConverter().convertValue(value, TypeDescriptor.forObject(value), targetTypeDescriptor);
}
public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
Object val = value.getValue();
return this.relatedContext.getTypeConverter().convertValue(val, TypeDescriptor.forObject(val), targetTypeDescriptor);
}
/*
* A new scope is entered when a function is invoked
*/
public void enterScope(Map<String, Object> argMap) {
ensureVariableScopesInitialized();
this.variableScopes.push(new VariableScope(argMap));
}
public void enterScope(String name, Object value) {
ensureVariableScopesInitialized();
this.variableScopes.push(new VariableScope(name, value));
}
public void exitScope() {
ensureVariableScopesInitialized();
this.variableScopes.pop();
}
public void setLocalVariable(String name, Object value) {
ensureVariableScopesInitialized();
this.variableScopes.peek().setVariable(name, value);
}
public Object lookupLocalVariable(String name) {
ensureVariableScopesInitialized();
int scopeNumber = this.variableScopes.size() - 1;
for (int i = scopeNumber; i >= 0; i--) {
if (this.variableScopes.get(i).definesVariable(name)) {
return this.variableScopes.get(i).lookupVariable(name);
}
}
return null;
}
public TypedValue operate(Operation op, Object left, Object right) throws EvaluationException {
OperatorOverloader overloader = this.relatedContext.getOperatorOverloader();
if (overloader.overridesOperation(op, left, right)) {
Object returnValue = overloader.operate(op, left, right);
return new TypedValue(returnValue);
}
else {
String leftType = (left==null?"null":left.getClass().getName());
String rightType = (right==null?"null":right.getClass().getName());
throw new SpelEvaluationException(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, leftType, rightType);
}
}
public List<PropertyAccessor> getPropertyAccessors() {
return this.relatedContext.getPropertyAccessors();
}
public EvaluationContext getEvaluationContext() {
return this.relatedContext;
}
public SpelParserConfiguration getConfiguration() {
return this.configuration;
}
/**
* A new scope is entered when a function is called and it is used to hold the parameters to the function call. If the names
* of the parameters clash with those in a higher level scope, those in the higher level scope will not be accessible whilst
* the function is executing. When the function returns the scope is exited.
*/
private static class VariableScope {
private final Map<String, Object> vars = new HashMap<String, Object>();
public VariableScope() { }
public VariableScope(Map<String, Object> arguments) {
if (arguments != null) {
this.vars.putAll(arguments);
}
}
public VariableScope(String name, Object value) {
this.vars.put(name,value);
}
public Object lookupVariable(String name) {
return this.vars.get(name);
}
public void setVariable(String name, Object value) {
this.vars.put(name,value);
}
public boolean definesVariable(String name) {
return this.vars.containsKey(name);
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel;
import org.springframework.expression.spel.SpelParseException;
/**
* Wraps a real parse exception. This exception flows to the top parse method and then
* the wrapped exception is thrown as the real problem.
*
* @author Andy Clement
* @since 3.0
*/
public class InternalParseException extends RuntimeException {
public InternalParseException(SpelParseException cause) {
super(cause);
}
public SpelParseException getCause() {
return (SpelParseException) super.getCause();
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* 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.springframework.expression.spel;
import org.springframework.expression.EvaluationException;
/**
* Root exception for Spring EL related exceptions. Rather than holding a hard coded string indicating the problem, it
* records a message key and the inserts for the message. See {@link SpelMessage} for the list of all possible messages
* that can occur.
*
* @author Andy Clement
* @since 3.0
*/
public class SpelEvaluationException extends EvaluationException {
private SpelMessage message;
private Object[] inserts;
public SpelEvaluationException(SpelMessage message, Object... inserts) {
super(message.formatMessage(0, inserts)); // TODO poor position information, can the callers not really supply something?
this.message = message;
this.inserts = inserts;
}
public SpelEvaluationException(int position, SpelMessage message, Object... inserts) {
super(position, message.formatMessage(position, inserts));
this.message = message;
this.inserts = inserts;
}
public SpelEvaluationException(int position, Throwable cause,
SpelMessage message, Object... inserts) {
super(position,message.formatMessage(position,inserts),cause);
this.message = message;
this.inserts = inserts;
}
public SpelEvaluationException(Throwable cause, SpelMessage message, Object... inserts) {
super(message.formatMessage(0,inserts),cause);
this.message = message;
this.inserts = inserts;
}
/**
* @return a formatted message with inserts applied
*/
@Override
public String getMessage() {
if (message != null)
return message.formatMessage(position, inserts);
else
return super.getMessage();
}
/**
* @return the message code
*/
public SpelMessage getMessageCode() {
return this.message;
}
/**
* Set the position in the related expression which gave rise to this exception.
*
* @param position the position in the expression that gave rise to the exception
*/
public void setPosition(int position) {
this.position = position;
}
/**
* @return the message inserts
*/
public Object[] getInserts() {
return inserts;
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel;
import java.text.MessageFormat;
/**
* Contains all the messages that can be produced by the Spring Expression Language. Each message has a kind (info,
* warn, error) and a code number. Tests can be written to expect particular code numbers rather than particular text,
* enabling the message text to more easily be modified and the tests to run successfully in different locales.
* <p>
* When a message is formatted, it will have this kind of form
*
* <pre>
* EL1004E: (pos 34): Type cannot be found 'String'
* </pre>
*
* </code> The prefix captures the code and the error kind, whilst the position is included if it is known.
*
* @author Andy Clement
* @since 3.0
*/
public enum SpelMessage {
TYPE_CONVERSION_ERROR(Kind.ERROR, 1001, "Type conversion problem, cannot convert from {0} to {1}"), //
CONSTRUCTOR_NOT_FOUND(Kind.ERROR, 1002, "Constructor call: No suitable constructor found on type {0} for arguments {1}"), //
CONSTRUCTOR_INVOCATION_PROBLEM(Kind.ERROR, 1003, "A problem occurred whilst attempting to construct an object of type ''{0}'' using arguments ''{1}''"), //
METHOD_NOT_FOUND(Kind.ERROR, 1004, "Method call: Method {0} cannot be found on {1} type"), //
TYPE_NOT_FOUND(Kind.ERROR, 1005, "Type cannot be found ''{0}''"), //
FUNCTION_NOT_DEFINED(Kind.ERROR, 1006, "The function ''{0}'' could not be found"), //
PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL(Kind.ERROR, 1007, "Field or property ''{0}'' cannot be found on null"), //
PROPERTY_OR_FIELD_NOT_READABLE(Kind.ERROR, 1008, "Field or property ''{0}'' cannot be found on object of type ''{1}''"), //
PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL(Kind.ERROR, 1009, "Field or property ''{0}'' cannot be set on null"), //
PROPERTY_OR_FIELD_NOT_WRITABLE(Kind.ERROR, 1010, "Field or property ''{0}'' cannot be set on object of type ''{1}''"), //
METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED(Kind.ERROR, 1011, "Method call: Attempted to call method {0} on null context object"), //
CANNOT_INDEX_INTO_NULL_VALUE(Kind.ERROR, 1012, "Cannot index into a null value"),
NOT_COMPARABLE(Kind.ERROR, 1013, "Cannot compare instances of {0} and {1}"), //
INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION(Kind.ERROR, 1014, "Incorrect number of arguments for function, {0} supplied but function takes {1}"), //
INVALID_TYPE_FOR_SELECTION(Kind.ERROR, 1015, "Cannot perform selection on input data of type ''{0}''"), //
RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN(Kind.ERROR, 1016, "Result of selection criteria is not boolean"), //
BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST(Kind.ERROR, 1017, "Right operand for the 'between' operator has to be a two-element list"), //
INVALID_PATTERN(Kind.ERROR, 1018, "Pattern is not valid ''{0}''"), //
PROJECTION_NOT_SUPPORTED_ON_TYPE(Kind.ERROR, 1019, "Projection is not supported on the type ''{0}''"), //
ARGLIST_SHOULD_NOT_BE_EVALUATED(Kind.ERROR, 1020, "The argument list of a lambda expression should never have getValue() called upon it"), //
EXCEPTION_DURING_PROPERTY_READ(Kind.ERROR, 1021, "A problem occurred whilst attempting to access the property ''{0}'': ''{1}''"), //
FUNCTION_REFERENCE_CANNOT_BE_INVOKED(Kind.ERROR, 1022, "The function ''{0}'' mapped to an object of type ''{1}'' which cannot be invoked"), //
EXCEPTION_DURING_FUNCTION_CALL(Kind.ERROR, 1023, "A problem occurred whilst attempting to invoke the function ''{0}'': ''{1}''"), //
ARRAY_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1024, "The array has ''{0}'' elements, index ''{1}'' is invalid"), //
COLLECTION_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1025, "The collection has ''{0}'' elements, index ''{1}'' is invalid"), //
STRING_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1026, "The string has ''{0}'' characters, index ''{1}'' is invalid"), //
INDEXING_NOT_SUPPORTED_FOR_TYPE(Kind.ERROR, 1027, "Indexing into type ''{0}'' is not supported"), //
INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND(Kind.ERROR, 1028, "The operator 'instanceof' needs the right operand to be a class, not a ''{0}''"), //
EXCEPTION_DURING_METHOD_INVOCATION(Kind.ERROR, 1029, "A problem occurred when trying to execute method ''{0}'' on object of type ''{1}'': ''{2}''"), //
OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES(Kind.ERROR, 1030, "The operator ''{0}'' is not supported between objects of type ''{1}'' and ''{2}''"), //
PROBLEM_LOCATING_METHOD(Kind.ERROR, 1031, "Problem locating method {0} cannot on type {1}"),
SETVALUE_NOT_SUPPORTED( Kind.ERROR, 1032, "setValue(ExpressionState, Object) not supported for ''{0}''"), //
MULTIPLE_POSSIBLE_METHODS(Kind.ERROR, 1033, "Method call of ''{0}'' is ambiguous, supported type conversions allow multiple variants to match"), //
EXCEPTION_DURING_PROPERTY_WRITE(Kind.ERROR, 1034, "A problem occurred whilst attempting to set the property ''{0}'': {1}"), //
NOT_AN_INTEGER(Kind.ERROR, 1035, "The value ''{0}'' cannot be parsed as an int"), //
NOT_A_LONG(Kind.ERROR, 1036, "The value ''{0}'' cannot be parsed as a long"), //
INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1037, "First operand to matches operator must be a string. ''{0}'' is not"), //
INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1038, "Second operand to matches operator must be a string. ''{0}'' is not"), //
FUNCTION_MUST_BE_STATIC(Kind.ERROR, 1039, "Only static methods can be called via function references. The method ''{0}'' referred to by name ''{1}'' is not static."),//
NOT_A_REAL(Kind.ERROR, 1040, "The value ''{0}'' cannot be parsed as a double"), //
MORE_INPUT(Kind.ERROR,1041, "After parsing a valid expression, there is still more data in the expression: ''{0}''"),
RIGHT_OPERAND_PROBLEM(Kind.ERROR,1042, "Problem parsing right operand"),
NOT_EXPECTED_TOKEN(Kind.ERROR,1043,"Unexpected token. Expected ''{0}'' but was ''{1}''"),
OOD(Kind.ERROR,1044,"Unexpectedly ran out of input"), //
NON_TERMINATING_DOUBLE_QUOTED_STRING(Kind.ERROR,1045,"Cannot find terminating \" for string"),//
NON_TERMINATING_QUOTED_STRING(Kind.ERROR,1046,"Cannot find terminating ' for string"), //
MISSING_LEADING_ZERO_FOR_NUMBER(Kind.ERROR,1047,"A real number must be prefixed by zero, it cannot start with just ''.''"), //
REAL_CANNOT_BE_LONG(Kind.ERROR,1048,"Real number cannot be suffixed with a long (L or l) suffix"),//
UNEXPECTED_DATA_AFTER_DOT(Kind.ERROR,1049,"Unexpected data after ''.'': ''{0}''"),//
MISSING_CONSTRUCTOR_ARGS(Kind.ERROR,1050,"The arguments '(...)' for the constructor call are missing"),//
RUN_OUT_OF_ARGUMENTS(Kind.ERROR,1051,"Unexpected ran out of arguments"),//
UNABLE_TO_GROW_COLLECTION(Kind.ERROR,1052,"Unable to grow collection"),//
UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE(Kind.ERROR,1053,"Unable to grow collection: unable to determine list element type"),//
UNABLE_TO_CREATE_LIST_FOR_INDEXING(Kind.ERROR,1054,"Unable to dynamically create a List to replace a null value"),//
UNABLE_TO_CREATE_MAP_FOR_INDEXING(Kind.ERROR,1055,"Unable to dynamically create a Map to replace a null value"),//
UNABLE_TO_DYNAMICALLY_CREATE_OBJECT(Kind.ERROR,1056,"Unable to dynamically create instance of ''{0}'' to replace a null value"),//
NO_BEAN_RESOLVER_REGISTERED(Kind.ERROR,1057,"No bean resolver registered in the context to resolve access to bean ''{0}''"),//
EXCEPTION_DURING_BEAN_RESOLUTION(Kind.ERROR, 1058, "A problem occurred when trying to resolve bean ''{0}'':''{1}''"), //
INVALID_BEAN_REFERENCE(Kind.ERROR,1059,"@ can only be followed by an identifier or a quoted name"),//
TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION(Kind.ERROR, 1060,
"Expected the type of the new array to be specified as a String but found ''{0}''"), //
INCORRECT_ELEMENT_TYPE_FOR_ARRAY(Kind.ERROR, 1061,
"The array of type ''{0}'' cannot have an element of type ''{1}'' inserted"), //
MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED(Kind.ERROR, 1062,
"Using an initializer to build a multi-dimensional array is not currently supported"), //
MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, "A required array dimension has not been specified"), //
INITIALIZER_LENGTH_INCORRECT(
Kind.ERROR, 1064, "array initializer size does not match array dimensions"), //
;
private Kind kind;
private int code;
private String message;
private SpelMessage(Kind kind, int code, String message) {
this.kind = kind;
this.code = code;
this.message = message;
}
/**
* Produce a complete message including the prefix, the position (if known) and with the inserts applied to the
* message.
*
* @param pos the position, if less than zero it is ignored and not included in the message
* @param inserts the inserts to put into the formatted message
* @return a formatted message
*/
public String formatMessage(int pos, Object... inserts) {
StringBuilder formattedMessage = new StringBuilder();
formattedMessage.append("EL").append(code);
switch (kind) {
// case WARNING:
// formattedMessage.append("W");
// break;
// case INFO:
// formattedMessage.append("I");
// break;
case ERROR:
formattedMessage.append("E");
break;
}
formattedMessage.append(":");
if (pos != -1) {
formattedMessage.append("(pos ").append(pos).append("): ");
}
formattedMessage.append(MessageFormat.format(message, inserts));
return formattedMessage.toString();
}
public static enum Kind {
INFO, WARNING, ERROR
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
/**
* Represents a node in the Ast for a parsed expression.
*
* @author Andy Clement
* @since 3.0
*/
public interface SpelNode {
/**
* Evaluate the expression node in the context of the supplied expression state and return the value.
* @param expressionState the current expression state (includes the context)
* @return the value of this node evaluated against the specified state
*/
Object getValue(ExpressionState expressionState) throws EvaluationException;
/**
* Evaluate the expression node in the context of the supplied expression state and return the typed value.
* @param expressionState the current expression state (includes the context)
* @return the type value of this node evaluated against the specified state
*/
TypedValue getTypedValue(ExpressionState expressionState) throws EvaluationException;
/**
* Determine if this expression node will support a setValue() call.
*
* @param expressionState the current expression state (includes the context)
* @return true if the expression node will allow setValue()
* @throws EvaluationException if something went wrong trying to determine if the node supports writing
*/
boolean isWritable(ExpressionState expressionState) throws EvaluationException;
/**
* Evaluate the expression to a node and then set the new value on that node. For example, if the expression
* evaluates to a property reference then the property will be set to the new value.
* @param expressionState the current expression state (includes the context)
* @param newValue the new value
* @throws EvaluationException if any problem occurs evaluating the expression or setting the new value
*/
void setValue(ExpressionState expressionState, Object newValue) throws EvaluationException;
/**
* @return the string form of this AST node
*/
String toStringAST();
/**
* @return the number of children under this node
*/
int getChildCount();
/**
* Helper method that returns a SpelNode rather than an Antlr Tree node.
* @return the child node cast to a SpelNode
*/
SpelNode getChild(int index);
/**
* Determine the class of the object passed in, unless it is already a class object.
* @param o the object that the caller wants the class of
* @return the class of the object if it is not already a class object, or null if the object is null
*/
Class<?> getObjectClass(Object obj);
/**
* @return the start position of this Ast node in the expression string
*/
int getStartPosition();
/**
* @return the end position of this Ast node in the expression string
*/
int getEndPosition();
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* 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.springframework.expression.spel;
import org.springframework.expression.ParseException;
/**
* Root exception for Spring EL related exceptions. Rather than holding a hard coded string indicating the problem, it
* records a message key and the inserts for the message. See {@link SpelMessage} for the list of all possible messages
* that can occur.
*
* @author Andy Clement
* @since 3.0
*/
public class SpelParseException extends ParseException {
private SpelMessage message;
private Object[] inserts;
// public SpelParseException(String expressionString, int position, Throwable cause, SpelMessages message, Object... inserts) {
// super(expressionString, position, message.formatMessage(position,inserts), cause);
// this.message = message;
// this.inserts = inserts;
// }
public SpelParseException(String expressionString, int position, SpelMessage message, Object... inserts) {
super(expressionString, position, message.formatMessage(position,inserts));
this.position = position;
this.message = message;
this.inserts = inserts;
}
public SpelParseException(int position, SpelMessage message, Object... inserts) {
super(position, message.formatMessage(position,inserts));
this.position = position;
this.message = message;
this.inserts = inserts;
}
public SpelParseException(int position, Throwable cause, SpelMessage message, Object... inserts) {
super(position, message.formatMessage(position,inserts), cause);
this.position = position;
this.message = message;
this.inserts = inserts;
}
//
// public SpelException(Throwable cause, SpelMessages message, Object... inserts) {
// super(cause);
// this.message = message;
// this.inserts = inserts;
// }
//
// public SpelException(int position, SpelMessages message, Object... inserts) {
// super((Throwable)null);
// this.position = position;
// this.message = message;
// this.inserts = inserts;
// }
//
// public SpelException(SpelMessages message, Object... inserts) {
// super((Throwable)null);
// this.message = message;
// this.inserts = inserts;
// }
/**
* @return a formatted message with inserts applied
*/
@Override
public String getMessage() {
if (message != null)
return message.formatMessage(position, inserts);
else
return super.getMessage();
}
/**
* @return the message code
*/
public SpelMessage getMessageCode() {
return this.message;
}
/**
* @return the message inserts
*/
public Object[] getInserts() {
return inserts;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel;
/**
* Configuration object for the SpEL expression parser.
*
* @author Juergen Hoeller
* @since 3.0
* @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration)
*/
public class SpelParserConfiguration {
private final boolean autoGrowNullReferences;
private final boolean autoGrowCollections;
public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections) {
this.autoGrowNullReferences = autoGrowNullReferences;
this.autoGrowCollections = autoGrowCollections;
}
public boolean isAutoGrowNullReferences() {
return this.autoGrowNullReferences;
}
public boolean isAutoGrowCollections() {
return this.autoGrowCollections;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* Represents assignment. An alternative to calling setValue() for an expression is to use an assign.
*
* <p>Example: 'someNumberProperty=42'
*
* @author Andy Clement
* @since 3.0
*/
public class Assign extends SpelNodeImpl {
public Assign(int pos,SpelNodeImpl... operands) {
super(pos,operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue newValue = children[1].getValueInternal(state);
getChild(0).setValue(state, newValue.getValue());
return newValue;
}
@Override
public String toStringAST() {
return new StringBuilder().append(getChild(0).toStringAST()).append("=").append(getChild(1).toStringAST())
.toString();
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.util.ArrayList;
import java.util.List;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.spel.ExpressionState;
/**
* Utilities methods for use in the Ast classes.
*
* @author Andy Clement
* @since 3.0.2
*/
public class AstUtils {
/**
* Determines the set of property resolvers that should be used to try and access a property on the specified target
* type. The resolvers are considered to be in an ordered list, however in the returned list any that are exact
* matches for the input target type (as opposed to 'general' resolvers that could work for any type) are placed at
* the start of the list. In addition, there are specific resolvers that exactly name the class in question and
* resolvers that name a specific class but it is a supertype of the class we have. These are put at the end of the
* specific resolvers set and will be tried after exactly matching accessors but before generic accessors.
*
* @param targetType the type upon which property access is being attempted
* @return a list of resolvers that should be tried in order to access the property
*/
public static List<PropertyAccessor> getPropertyAccessorsToTry(Class<?> targetType, ExpressionState state) {
List<PropertyAccessor> specificAccessors = new ArrayList<PropertyAccessor>();
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
for (PropertyAccessor resolver : state.getPropertyAccessors()) {
Class<?>[] targets = resolver.getSpecificTargetClasses();
if (targets == null) { // generic resolver that says it can be used for any type
generalAccessors.add(resolver);
}
else {
if (targetType != null) {
int pos = 0;
for (Class<?> clazz : targets) {
if (clazz == targetType) { // put exact matches on the front to be tried first?
specificAccessors.add(pos++, resolver);
}
else if (clazz.isAssignableFrom(targetType)) { // put supertype matches at the end of the
// specificAccessor list
generalAccessors.add(resolver);
}
}
}
}
}
List<PropertyAccessor> resolvers = new ArrayList<PropertyAccessor>();
resolvers.addAll(specificAccessors);
resolvers.addAll(generalAccessors);
return resolvers;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.AccessException;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
* Represents a bean reference to a type, for example "@foo" or "@'foo.bar'"
*
* @author Andy Clement
*/
public class BeanReference extends SpelNodeImpl {
private String beanname;
public BeanReference(int pos,String beanname) {
super(pos);
this.beanname = beanname;
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
BeanResolver beanResolver = state.getEvaluationContext().getBeanResolver();
if (beanResolver==null) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.NO_BEAN_RESOLVER_REGISTERED, beanname);
}
try {
TypedValue bean = new TypedValue(beanResolver.resolve(state.getEvaluationContext(),beanname));
return bean;
} catch (AccessException ae) {
throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_BEAN_RESOLUTION,
beanname, ae.getMessage());
}
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append("@");
if (beanname.indexOf('.')==-1) {
sb.append(beanname);
} else {
sb.append("'").append(beanname).append("'");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Represents the literal values TRUE and FALSE.
*
* @author Andy Clement
* @since 3.0
*/
public class BooleanLiteral extends Literal {
private final BooleanTypedValue value;
public BooleanLiteral(String payload, int pos, boolean value) {
super(payload, pos);
this.value = BooleanTypedValue.forValue(value);
}
@Override
public BooleanTypedValue getLiteralValue() {
return this.value;
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
/**
* Represents a DOT separated expression sequence, such as 'property1.property2.methodOne()'
*
* @author Andy Clement
* @since 3.0
*/
public class CompoundExpression extends SpelNodeImpl {
public CompoundExpression(int pos,SpelNodeImpl... expressionComponents) {
super(pos,expressionComponents);
if (expressionComponents.length<2) {
throw new IllegalStateException("Dont build compound expression less than one entry: "+expressionComponents.length);
}
}
/**
* Evalutes a compound expression. This involves evaluating each piece in turn and the return value from each piece
* is the active context object for the subsequent piece.
* @param state the state in which the expression is being evaluated
* @return the final value from the last piece of the compound expression
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue result = null;
SpelNodeImpl nextNode = null;
try {
nextNode = children[0];
result = nextNode.getValueInternal(state);
for (int i = 1; i < getChildCount(); i++) {
try {
state.pushActiveContextObject(result);
nextNode = children[i];
result = nextNode.getValueInternal(state);
} finally {
state.popActiveContextObject();
}
}
} catch (SpelEvaluationException ee) {
// Correct the position for the error before rethrowing
ee.setPosition(nextNode.getStartPosition());
throw ee;
}
return result;
}
@Override
public void setValue(ExpressionState state, Object value) throws EvaluationException {
if (getChildCount() == 1) {
getChild(0).setValue(state, value);
return;
}
TypedValue ctx = children[0].getValueInternal(state);
for (int i = 1; i < getChildCount() - 1; i++) {
try {
state.pushActiveContextObject(ctx);
ctx = children[i].getValueInternal(state);
} finally {
state.popActiveContextObject();
}
}
try {
state.pushActiveContextObject(ctx);
getChild(getChildCount() - 1).setValue(state, value);
} finally {
state.popActiveContextObject();
}
}
@Override
public boolean isWritable(ExpressionState state) throws EvaluationException {
if (getChildCount() == 1) {
return getChild(0).isWritable(state);
}
TypedValue ctx = children[0].getValueInternal(state);
for (int i = 1; i < getChildCount() - 1; i++) {
try {
state.pushActiveContextObject(ctx);
ctx = children[i].getValueInternal(state);
} finally {
state.popActiveContextObject();
}
}
try {
state.pushActiveContextObject(ctx);
return getChild(getChildCount() - 1).isWritable(state);
} finally {
state.popActiveContextObject();
}
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < getChildCount(); i++) {
if (i>0) { sb.append("."); }
sb.append(getChild(i).toStringAST());
}
return sb.toString();
}
}

View File

@@ -0,0 +1,404 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
/**
* Represents the invocation of a constructor. Either a constructor on a regular type or
* construction of an array. When an array is constructed, an initializer can be specified.
*
* <p>
* Examples:<br>
* new String('hello world')<br>
* new int[]{1,2,3,4}<br>
* new int[3] new int[3]{1,2,3}
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class ConstructorReference extends SpelNodeImpl {
private boolean isArrayConstructor = false;
private SpelNodeImpl[] dimensions;
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
/**
* The cached executor that may be reused on subsequent evaluations.
*/
private volatile ConstructorExecutor cachedExecutor;
/**
* Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
* call
*/
public ConstructorReference(int pos, SpelNodeImpl... arguments) {
super(pos, arguments);
this.isArrayConstructor = false;
}
/**
* Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
* call
*/
public ConstructorReference(int pos, SpelNodeImpl[] dimensions, SpelNodeImpl... arguments) {
super(pos, arguments);
this.isArrayConstructor = true;
this.dimensions = dimensions;
}
/**
* Implements getValue() - delegating to the code for building an array or a simple type.
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
if (this.isArrayConstructor) {
return createArray(state);
}
else {
return createNewInstance(state);
}
}
/**
* Create a new ordinary object and return it.
* @param state the expression state within which this expression is being evaluated
* @return the new object
* @throws EvaluationException if there is a problem creating the object
*/
private TypedValue createNewInstance(ExpressionState state) throws EvaluationException {
Object[] arguments = new Object[getChildCount() - 1];
List<TypeDescriptor> argumentTypes = new ArrayList<TypeDescriptor>(getChildCount() - 1);
for (int i = 0; i < arguments.length; i++) {
TypedValue childValue = this.children[i + 1].getValueInternal(state);
Object value = childValue.getValue();
arguments[i] = value;
argumentTypes.add(TypeDescriptor.forObject(value));
}
ConstructorExecutor executorToUse = this.cachedExecutor;
if (executorToUse != null) {
try {
return executorToUse.execute(state.getEvaluationContext(), arguments);
}
catch (AccessException ae) {
// Two reasons this can occur:
// 1. the method invoked actually threw a real exception
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
// In the first case we should not retry, in the second case we should see if there is a
// better suited method.
// To determine which situation it is, the AccessException will contain a cause.
// If the cause is an InvocationTargetException, a user exception was thrown inside the constructor.
// Otherwise the constructor could not be invoked.
if (ae.getCause() instanceof InvocationTargetException) {
// User exception was the root cause - exit now
Throwable rootCause = ae.getCause().getCause();
if (rootCause instanceof RuntimeException) {
throw (RuntimeException) rootCause;
} else {
String typename = (String) this.children[0].getValueInternal(state).getValue();
throw new SpelEvaluationException(getStartPosition(), rootCause,
SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename, FormatHelper
.formatMethodForMessage("", argumentTypes));
}
}
// at this point we know it wasn't a user problem so worth a retry if a better candidate can be found
this.cachedExecutor = null;
}
}
// either there was no accessor or it no longer exists
String typename = (String) this.children[0].getValueInternal(state).getValue();
executorToUse = findExecutorForConstructor(typename, argumentTypes, state);
try {
this.cachedExecutor = executorToUse;
return executorToUse.execute(state.getEvaluationContext(), arguments);
}
catch (AccessException ae) {
throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM,
typename, FormatHelper.formatMethodForMessage("", argumentTypes));
}
}
/**
* Go through the list of registered constructor resolvers and see if any can find a constructor that takes the
* specified set of arguments.
* @param typename the type trying to be constructed
* @param argumentTypes the types of the arguments supplied that the constructor must take
* @param state the current state of the expression
* @return a reusable ConstructorExecutor that can be invoked to run the constructor or null
* @throws SpelEvaluationException if there is a problem locating the constructor
*/
private ConstructorExecutor findExecutorForConstructor(String typename, List<TypeDescriptor> argumentTypes,
ExpressionState state) throws SpelEvaluationException {
EvaluationContext eContext = state.getEvaluationContext();
List<ConstructorResolver> cResolvers = eContext.getConstructorResolvers();
if (cResolvers != null) {
for (ConstructorResolver ctorResolver : cResolvers) {
try {
ConstructorExecutor cEx = ctorResolver.resolve(state.getEvaluationContext(), typename,
argumentTypes);
if (cEx != null) {
return cEx;
}
}
catch (AccessException ex) {
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename,
FormatHelper.formatMethodForMessage("", argumentTypes));
}
}
}
throw new SpelEvaluationException(getStartPosition(), SpelMessage.CONSTRUCTOR_NOT_FOUND, typename, FormatHelper
.formatMethodForMessage("", argumentTypes));
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append("new ");
int index = 0;
sb.append(getChild(index++).toStringAST());
sb.append("(");
for (int i = index; i < getChildCount(); i++) {
if (i > index)
sb.append(",");
sb.append(getChild(i).toStringAST());
}
sb.append(")");
return sb.toString();
}
/**
* Create an array and return it.
* @param state the expression state within which this expression is being evaluated
* @return the new array
* @throws EvaluationException if there is a problem creating the array
*/
private TypedValue createArray(ExpressionState state) throws EvaluationException {
// First child gives us the array type which will either be a primitive or reference type
Object intendedArrayType = getChild(0).getValue(state);
if (!(intendedArrayType instanceof String)) {
throw new SpelEvaluationException(getChild(0).getStartPosition(),
SpelMessage.TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION, FormatHelper
.formatClassNameForMessage(intendedArrayType.getClass()));
}
String type = (String) intendedArrayType;
Class<?> componentType;
TypeCode arrayTypeCode = TypeCode.forName(type);
if (arrayTypeCode == TypeCode.OBJECT) {
componentType = state.findType(type);
}
else {
componentType = arrayTypeCode.getType();
}
Object newArray;
if (!hasInitializer()) {
// Confirm all dimensions were specified (for example [3][][5] is missing the 2nd dimension)
for (SpelNodeImpl dimension : this.dimensions) {
if (dimension == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.MISSING_ARRAY_DIMENSION);
}
}
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
// Shortcut for 1 dimensional
if (this.dimensions.length == 1) {
TypedValue o = this.dimensions[0].getTypedValue(state);
int arraySize = ExpressionUtils.toInt(typeConverter, o);
newArray = Array.newInstance(componentType, arraySize);
}
else {
// Multi-dimensional - hold onto your hat!
int[] dims = new int[this.dimensions.length];
for (int d = 0; d < this.dimensions.length; d++) {
TypedValue o = this.dimensions[d].getTypedValue(state);
dims[d] = ExpressionUtils.toInt(typeConverter, o);
}
newArray = Array.newInstance(componentType, dims);
}
}
else {
// There is an initializer
if (this.dimensions.length > 1) {
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
// is not currently supported
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
}
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
InlineList initializer = (InlineList) getChild(1);
// If a dimension was specified, check it matches the initializer length
if (this.dimensions[0] != null) {
TypedValue dValue = this.dimensions[0].getTypedValue(state);
int i = ExpressionUtils.toInt(typeConverter, dValue);
if (i != initializer.getChildCount()) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INITIALIZER_LENGTH_INCORRECT);
}
}
// Build the array and populate it
int arraySize = initializer.getChildCount();
newArray = Array.newInstance(componentType, arraySize);
if (arrayTypeCode == TypeCode.OBJECT) {
populateReferenceTypeArray(state, newArray, typeConverter, initializer, componentType);
}
else if (arrayTypeCode == TypeCode.INT) {
populateIntArray(state, newArray, typeConverter, initializer);
}
else if (arrayTypeCode == TypeCode.BOOLEAN) {
populateBooleanArray(state, newArray, typeConverter, initializer);
}
else if (arrayTypeCode == TypeCode.CHAR) {
populateCharArray(state, newArray, typeConverter, initializer);
}
else if (arrayTypeCode == TypeCode.LONG) {
populateLongArray(state, newArray, typeConverter, initializer);
}
else if (arrayTypeCode == TypeCode.SHORT) {
populateShortArray(state, newArray, typeConverter, initializer);
}
else if (arrayTypeCode == TypeCode.DOUBLE) {
populateDoubleArray(state, newArray, typeConverter, initializer);
}
else if (arrayTypeCode == TypeCode.FLOAT) {
populateFloatArray(state, newArray, typeConverter, initializer);
}
else if (arrayTypeCode == TypeCode.BYTE) {
populateByteArray(state, newArray, typeConverter, initializer);
}
else {
throw new IllegalStateException(arrayTypeCode.name());
}
}
return new TypedValue(newArray);
}
private void populateReferenceTypeArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer, Class<?> componentType) {
TypeDescriptor toTypeDescriptor = TypeDescriptor.valueOf(componentType);
Object[] newObjectArray = (Object[]) newArray;
for (int i = 0; i < newObjectArray.length; i++) {
SpelNode elementNode = initializer.getChild(i);
Object arrayEntry = elementNode.getValue(state);
newObjectArray[i] = typeConverter.convertValue(arrayEntry, TypeDescriptor.forObject(arrayEntry), toTypeDescriptor);
}
}
private void populateByteArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
byte[] newByteArray = (byte[]) newArray;
for (int i = 0; i < newByteArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newByteArray[i] = ExpressionUtils.toByte(typeConverter, typedValue);
}
}
private void populateFloatArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
float[] newFloatArray = (float[]) newArray;
for (int i = 0; i < newFloatArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newFloatArray[i] = ExpressionUtils.toFloat(typeConverter, typedValue);
}
}
private void populateDoubleArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
double[] newDoubleArray = (double[]) newArray;
for (int i = 0; i < newDoubleArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newDoubleArray[i] = ExpressionUtils.toDouble(typeConverter, typedValue);
}
}
private void populateShortArray(ExpressionState state, Object newArray,
TypeConverter typeConverter, InlineList initializer) {
short[] newShortArray = (short[]) newArray;
for (int i = 0; i < newShortArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newShortArray[i] = ExpressionUtils.toShort(typeConverter, typedValue);
}
}
private void populateLongArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
long[] newLongArray = (long[]) newArray;
for (int i = 0; i < newLongArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newLongArray[i] = ExpressionUtils.toLong(typeConverter, typedValue);
}
}
private void populateCharArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
char[] newCharArray = (char[]) newArray;
for (int i = 0; i < newCharArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newCharArray[i] = ExpressionUtils.toChar(typeConverter, typedValue);
}
}
private void populateBooleanArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
boolean[] newBooleanArray = (boolean[]) newArray;
for (int i = 0; i < newBooleanArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newBooleanArray[i] = ExpressionUtils.toBoolean(typeConverter, typedValue);
}
}
private void populateIntArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
InlineList initializer) {
int[] newIntArray = (int[]) newArray;
for (int i = 0; i < newIntArray.length; i++) {
TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
newIntArray[i] = ExpressionUtils.toInt(typeConverter, typedValue);
}
}
private boolean hasInitializer() {
return getChildCount() > 1;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* Represents the elvis operator ?:. For an expression "a?:b" if a is not null, the value of the expression
* is "a", if a is null then the value of the expression is "b".
*
* @author Andy Clement
* @since 3.0
*/
public class Elvis extends SpelNodeImpl {
public Elvis(int pos, SpelNodeImpl... args) {
super(pos,args);
}
/**
* Evaluate the condition and if not null, return it. If it is null return the other value.
* @param state the expression state
* @throws EvaluationException if the condition does not evaluate correctly to a boolean or there is a problem
* executing the chosen alternative
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue value = children[0].getValueInternal(state);
if (value.getValue()!=null && !((value.getValue() instanceof String) && ((String)value.getValue()).length()==0)) {
return value;
} else {
return children[1].getValueInternal(state);
}
}
@Override
public String toStringAST() {
return new StringBuilder().append(getChild(0).toStringAST()).append(" ?: ").append(getChild(1).toStringAST()).toString();
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
/**
* Utility methods (formatters, etc) used during parsing and evaluation.
*
* @author Andy Clement
*/
public class FormatHelper {
/**
* Produce a nice string for a given method name with specified arguments.
* @param name the name of the method
* @param argumentTypes the types of the arguments to the method
* @return nicely formatted string, eg. foo(String,int)
*/
public static String formatMethodForMessage(String name, List<TypeDescriptor> argumentTypes) {
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append("(");
for (int i = 0; i < argumentTypes.size(); i++) {
if (i > 0) {
sb.append(",");
}
TypeDescriptor typeDescriptor = argumentTypes.get(i);
if (typeDescriptor != null) {
sb.append(formatClassNameForMessage(typeDescriptor.getType()));
}
else {
sb.append(formatClassNameForMessage(null));
}
}
sb.append(")");
return sb.toString();
}
/**
* Produce a nice string for a given class object.
* For example, a string array will have the formatted name "java.lang.String[]".
* @param clazz The class whose name is to be formatted
* @return a formatted string suitable for message inclusion
*/
public static String formatClassNameForMessage(Class<?> clazz) {
if (clazz == null) {
return "null";
}
StringBuilder fmtd = new StringBuilder();
if (clazz.isArray()) {
int dims = 1;
Class baseClass = clazz.getComponentType();
while (baseClass.isArray()) {
baseClass = baseClass.getComponentType();
dims++;
}
fmtd.append(baseClass.getName());
for (int i = 0; i < dims; i++) {
fmtd.append("[]");
}
} else {
fmtd.append(clazz.getName());
}
return fmtd.toString();
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.ReflectionHelper;
import org.springframework.util.ReflectionUtils;
/**
* A function reference is of the form "#someFunction(a,b,c)". Functions may be defined in the context prior to the
* expression being evaluated or within the expression itself using a lambda function definition. For example: Lambda
* function definition in an expression: "(#max = {|x,y|$x>$y?$x:$y};max(2,3))" Calling context defined function:
* "#isEven(37)". Functions may also be static java methods, registered in the context prior to invocation of the
* expression.
*
* <p>Functions are very simplistic, the arguments are not part of the definition (right now),
* so the names must be unique.
*
* @author Andy Clement
* @since 3.0
*/
public class FunctionReference extends SpelNodeImpl {
private final String name;
public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) {
super(pos,arguments);
name = functionName;
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue o = state.lookupVariable(name);
if (o == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, name);
}
// Two possibilities: a lambda function or a Java static method registered as a function
if (!(o.getValue() instanceof Method)) {
throw new SpelEvaluationException(SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, name, o.getClass());
}
try {
return executeFunctionJLRMethod(state, (Method) o.getValue());
}
catch (SpelEvaluationException se) {
se.setPosition(getStartPosition());
throw se;
}
}
/**
* Execute a function represented as a java.lang.reflect.Method.
*
* @param state the expression evaluation state
* @param the java method to invoke
* @return the return value of the invoked Java method
* @throws EvaluationException if there is any problem invoking the method
*/
private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method) throws EvaluationException {
Object[] functionArgs = getArguments(state);
if (!method.isVarArgs() && method.getParameterTypes().length != functionArgs.length) {
throw new SpelEvaluationException(SpelMessage.INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION,
functionArgs.length, method.getParameterTypes().length);
}
// Only static methods can be called in this way
if (!Modifier.isStatic(method.getModifiers())) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_MUST_BE_STATIC, method
.getDeclaringClass().getName()
+ "." + method.getName(), name);
}
// Convert arguments if necessary and remap them for varargs if required
if (functionArgs != null) {
TypeConverter converter = state.getEvaluationContext().getTypeConverter();
ReflectionHelper.convertAllArguments(converter, functionArgs, method);
}
if (method.isVarArgs()) {
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(method.getParameterTypes(), functionArgs);
}
try {
ReflectionUtils.makeAccessible(method);
Object result = method.invoke(method.getClass(), functionArgs);
return new TypedValue(result, new TypeDescriptor(new MethodParameter(method,-1)).narrow(result));
}
catch (Exception ex) {
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_FUNCTION_CALL,
this.name, ex.getMessage());
}
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder("#").append(name);
sb.append("(");
for (int i = 0; i < getChildCount(); i++) {
if (i > 0)
sb.append(",");
sb.append(getChild(i).toStringAST());
}
sb.append(")");
return sb.toString();
}
// to 'assign' to a function don't use the () suffix and so it is just a variable reference
/**
* Compute the arguments to the function, they are the children of this expression node.
* @return an array of argument values for the function call
*/
private Object[] getArguments(ExpressionState state) throws EvaluationException {
// Compute arguments to the function
Object[] arguments = new Object[getChildCount()];
for (int i = 0; i < arguments.length; i++) {
arguments[i] = children[i].getValueInternal(state).getValue();
}
return arguments;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* @author Andy Clement
* @since 3.0
*/
public class Identifier extends SpelNodeImpl {
private final TypedValue id;
public Identifier(String payload,int pos) {
super(pos);
this.id = new TypedValue(payload);
}
@Override
public String toStringAST() {
return (String)this.id.getValue();
}
@Override
public TypedValue getValueInternal(ExpressionState state) {
return this.id;
}
}

View File

@@ -0,0 +1,397 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
/**
* An Indexer can index into some proceeding structure to access a particular piece of it.
* Supported structures are: strings/collections (lists/sets)/arrays
*
* @author Andy Clement
* @since 3.0
*/
// TODO support multidimensional arrays
// TODO support correct syntax for multidimensional [][][] and not [,,,]
public class Indexer extends SpelNodeImpl {
// These fields are used when the indexer is being used as a property read accessor. If the name and
// target type match these cached values then the cachedReadAccessor is used to read the property.
// If they do not match, the correct accessor is discovered and then cached for later use.
private String cachedReadName;
private Class<?> cachedReadTargetType;
private PropertyAccessor cachedReadAccessor;
// These fields are used when the indexer is being used as a property write accessor. If the name and
// target type match these cached values then the cachedWriteAccessor is used to write the property.
// If they do not match, the correct accessor is discovered and then cached for later use.
private String cachedWriteName;
private Class<?> cachedWriteTargetType;
private PropertyAccessor cachedWriteAccessor;
public Indexer(int pos, SpelNodeImpl expr) {
super(pos, expr);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue context = state.getActiveContextObject();
Object targetObject = context.getValue();
TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor();
TypedValue indexValue = null;
Object index = null;
// This first part of the if clause prevents a 'double dereference' of the property (SPR-5847)
if (targetObject instanceof Map && (children[0] instanceof PropertyOrFieldReference)) {
PropertyOrFieldReference reference = (PropertyOrFieldReference)children[0];
index = reference.getName();
indexValue = new TypedValue(index);
}
else {
// In case the map key is unqualified, we want it evaluated against the root object so
// temporarily push that on whilst evaluating the key
try {
state.pushActiveContextObject(state.getRootContextObject());
indexValue = children[0].getValueInternal(state);
index = indexValue.getValue();
}
finally {
state.popActiveContextObject();
}
}
// Indexing into a Map
if (targetObject instanceof Map) {
Object key = index;
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
}
Object value = ((Map<?, ?>) targetObject).get(key);
return new TypedValue(value, targetObjectTypeDescriptor.getMapValueTypeDescriptor(value));
}
if (targetObject == null) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
}
// if the object is something that looks indexable by an integer, attempt to treat the index value as a number
if (targetObject instanceof Collection || targetObject.getClass().isArray() || targetObject instanceof String) {
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
if (targetObject.getClass().isArray()) {
Object arrayElement = accessArrayElement(targetObject, idx);
return new TypedValue(arrayElement, targetObjectTypeDescriptor.elementTypeDescriptor(arrayElement));
} else if (targetObject instanceof Collection) {
Collection c = (Collection) targetObject;
if (idx >= c.size()) {
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
}
}
int pos = 0;
for (Object o : c) {
if (pos == idx) {
return new TypedValue(o, targetObjectTypeDescriptor.elementTypeDescriptor(o));
}
pos++;
}
} else if (targetObject instanceof String) {
String ctxString = (String) targetObject;
if (idx >= ctxString.length()) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, ctxString.length(), idx);
}
return new TypedValue(String.valueOf(ctxString.charAt(idx)));
}
}
// Try and treat the index value as a property of the context object
// TODO could call the conversion service to convert the value to a String
if (indexValue.getTypeDescriptor().getType()==String.class) {
Class<?> targetObjectRuntimeClass = getObjectClass(targetObject);
String name = (String)indexValue.getValue();
EvaluationContext eContext = state.getEvaluationContext();
try {
if (cachedReadName!=null && cachedReadName.equals(name) && cachedReadTargetType!=null && cachedReadTargetType.equals(targetObjectRuntimeClass)) {
// it is OK to use the cached accessor
return cachedReadAccessor.read(eContext, targetObject, name);
}
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(targetObjectRuntimeClass, state);
if (accessorsToTry != null) {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canRead(eContext, targetObject, name)) {
if (accessor instanceof ReflectivePropertyAccessor) {
accessor = ((ReflectivePropertyAccessor)accessor).createOptimalAccessor(eContext, targetObject, name);
}
this.cachedReadAccessor = accessor;
this.cachedReadName = name;
this.cachedReadTargetType = targetObjectRuntimeClass;
return accessor.read(eContext, targetObject, name);
}
}
}
} catch (AccessException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
}
}
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
return true;
}
@SuppressWarnings("unchecked")
@Override
public void setValue(ExpressionState state, Object newValue) throws EvaluationException {
TypedValue contextObject = state.getActiveContextObject();
Object targetObject = contextObject.getValue();
TypeDescriptor targetObjectTypeDescriptor = contextObject.getTypeDescriptor();
TypedValue index = children[0].getValueInternal(state);
if (targetObject == null) {
throw new SpelEvaluationException(SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
}
// Indexing into a Map
if (targetObject instanceof Map) {
Map map = (Map) targetObject;
Object key = index.getValue();
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
key = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
}
if (targetObjectTypeDescriptor.getMapValueTypeDescriptor() != null) {
newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
}
map.put(key, newValue);
return;
}
if (targetObjectTypeDescriptor.isArray()) {
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
setArrayElement(state, contextObject.getValue(), idx, newValue, targetObjectTypeDescriptor.getElementTypeDescriptor().getType());
return;
}
else if (targetObject instanceof Collection) {
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
Collection c = (Collection) targetObject;
if (idx >= c.size()) {
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
}
}
if (targetObject instanceof List) {
List list = (List) targetObject;
if (targetObjectTypeDescriptor.getElementTypeDescriptor() != null) {
newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getElementTypeDescriptor());
}
list.set(idx, newValue);
return;
}
else {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
}
}
// Try and treat the index value as a property of the context object
// TODO could call the conversion service to convert the value to a String
if (index.getTypeDescriptor().getType() == String.class) {
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
String name = (String)index.getValue();
EvaluationContext eContext = state.getEvaluationContext();
try {
if (cachedWriteName!=null && cachedWriteName.equals(name) && cachedWriteTargetType!=null && cachedWriteTargetType.equals(contextObjectClass)) {
// it is OK to use the cached accessor
cachedWriteAccessor.write(eContext, targetObject, name,newValue);
return;
}
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(contextObjectClass, state);
if (accessorsToTry != null) {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canWrite(eContext, contextObject.getValue(), name)) {
this.cachedWriteName = name;
this.cachedWriteTargetType = contextObjectClass;
this.cachedWriteAccessor = accessor;
accessor.write(eContext, contextObject.getValue(), name, newValue);
return;
}
}
}
} catch (AccessException ae) {
throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
name, ae.getMessage());
}
}
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
}
/**
* Attempt to grow the specified collection so that the specified index is valid.
*
* @param state the expression state
* @param elementType the type of the elements in the collection
* @param index the index into the collection that needs to be valid
* @param collection the collection to grow with elements
* @return true if collection growing succeeded, otherwise false
*/
@SuppressWarnings("unchecked")
private boolean growCollection(ExpressionState state, TypeDescriptor targetType, int index,
Collection collection) {
if (state.getConfiguration().isAutoGrowCollections()) {
if (targetType.getElementTypeDescriptor() == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
}
TypeDescriptor elementType = targetType.getElementTypeDescriptor();
Object newCollectionElement = null;
try {
int newElements = index - collection.size();
while (newElements>0) {
collection.add(elementType.getType().newInstance());
newElements--;
}
newCollectionElement = elementType.getType().newInstance();
}
catch (Exception ex) {
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
}
collection.add(newCollectionElement);
return true;
}
return false;
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < getChildCount(); i++) {
if (i > 0)
sb.append(",");
sb.append(getChild(i).toStringAST());
}
sb.append("]");
return sb.toString();
}
private void setArrayElement(ExpressionState state, Object ctx, int idx, Object newValue, Class clazz) throws EvaluationException {
Class<?> arrayComponentType = clazz;
if (arrayComponentType == Integer.TYPE) {
int[] array = (int[]) ctx;
checkAccess(array.length, idx);
array[idx] = (Integer)state.convertValue(newValue, TypeDescriptor.valueOf(Integer.class));
} else if (arrayComponentType == Boolean.TYPE) {
boolean[] array = (boolean[]) ctx;
checkAccess(array.length, idx);
array[idx] = (Boolean)state.convertValue(newValue, TypeDescriptor.valueOf(Boolean.class));
} else if (arrayComponentType == Character.TYPE) {
char[] array = (char[]) ctx;
checkAccess(array.length, idx);
array[idx] = (Character)state.convertValue(newValue, TypeDescriptor.valueOf(Character.class));
} else if (arrayComponentType == Long.TYPE) {
long[] array = (long[]) ctx;
checkAccess(array.length, idx);
array[idx] = (Long)state.convertValue(newValue, TypeDescriptor.valueOf(Long.class));
} else if (arrayComponentType == Short.TYPE) {
short[] array = (short[]) ctx;
checkAccess(array.length, idx);
array[idx] = (Short)state.convertValue(newValue, TypeDescriptor.valueOf(Short.class));
} else if (arrayComponentType == Double.TYPE) {
double[] array = (double[]) ctx;
checkAccess(array.length, idx);
array[idx] = (Double)state.convertValue(newValue, TypeDescriptor.valueOf(Double.class));
} else if (arrayComponentType == Float.TYPE) {
float[] array = (float[]) ctx;
checkAccess(array.length, idx);
array[idx] = (Float)state.convertValue(newValue, TypeDescriptor.valueOf(Float.class));
} else if (arrayComponentType == Byte.TYPE) {
byte[] array = (byte[]) ctx;
checkAccess(array.length, idx);
array[idx] = (Byte)state.convertValue(newValue, TypeDescriptor.valueOf(Byte.class));
} else {
Object[] array = (Object[]) ctx;
checkAccess(array.length, idx);
array[idx] = state.convertValue(newValue, TypeDescriptor.valueOf(clazz));
}
}
private Object accessArrayElement(Object ctx, int idx) throws SpelEvaluationException {
Class<?> arrayComponentType = ctx.getClass().getComponentType();
if (arrayComponentType == Integer.TYPE) {
int[] array = (int[]) ctx;
checkAccess(array.length, idx);
return array[idx];
} else if (arrayComponentType == Boolean.TYPE) {
boolean[] array = (boolean[]) ctx;
checkAccess(array.length, idx);
return array[idx];
} else if (arrayComponentType == Character.TYPE) {
char[] array = (char[]) ctx;
checkAccess(array.length, idx);
return array[idx];
} else if (arrayComponentType == Long.TYPE) {
long[] array = (long[]) ctx;
checkAccess(array.length, idx);
return array[idx];
} else if (arrayComponentType == Short.TYPE) {
short[] array = (short[]) ctx;
checkAccess(array.length, idx);
return array[idx];
} else if (arrayComponentType == Double.TYPE) {
double[] array = (double[]) ctx;
checkAccess(array.length, idx);
return array[idx];
} else if (arrayComponentType == Float.TYPE) {
float[] array = (float[]) ctx;
checkAccess(array.length, idx);
return array[idx];
} else if (arrayComponentType == Byte.TYPE) {
byte[] array = (byte[]) ctx;
checkAccess(array.length, idx);
return array[idx];
} else {
Object[] array = (Object[]) ctx;
checkAccess(array.length, idx);
return array[idx];
}
}
private void checkAccess(int arrayLength, int index) throws SpelEvaluationException {
if (index > arrayLength) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, arrayLength, index);
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
/**
* Represent a list in an expression, e.g. '{1,2,3}'
*
* @author Andy Clement
* @since 3.0.4
*/
public class InlineList extends SpelNodeImpl {
// if the list is purely literals, it is a constant value and can be computed and cached
TypedValue constant = null; // TODO must be immutable list
public InlineList(int pos, SpelNodeImpl... args) {
super(pos, args);
checkIfConstant();
}
/**
* If all the components of the list are constants, or lists that themselves contain constants, then a constant list
* can be built to represent this node. This will speed up later getValue calls and reduce the amount of garbage
* created.
*/
private void checkIfConstant() {
boolean isConstant = true;
for (int c = 0, max = getChildCount(); c < max; c++) {
SpelNode child = getChild(c);
if (!(child instanceof Literal)) {
if (child instanceof InlineList) {
InlineList inlineList = (InlineList) child;
if (!inlineList.isConstant()) {
isConstant = false;
}
} else {
isConstant = false;
}
}
}
if (isConstant) {
List<Object> constantList = new ArrayList<Object>();
int childcount = getChildCount();
for (int c = 0; c < childcount; c++) {
SpelNode child = getChild(c);
if ((child instanceof Literal)) {
constantList.add(((Literal) child).getLiteralValue().getValue());
} else if (child instanceof InlineList) {
constantList.add(((InlineList) child).getConstantValue());
}
}
this.constant = new TypedValue(Collections.unmodifiableList(constantList));
}
}
@Override
public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
if (constant != null) {
return constant;
} else {
List<Object> returnValue = new ArrayList<Object>();
int childcount = getChildCount();
for (int c = 0; c < childcount; c++) {
returnValue.add(getChild(c).getValue(expressionState));
}
return new TypedValue(returnValue);
}
}
@Override
public String toStringAST() {
StringBuilder s = new StringBuilder();
// string ast matches input string, not the 'toString()' of the resultant collection, which would use []
s.append('{');
int count = getChildCount();
for (int c = 0; c < count; c++) {
if (c > 0) {
s.append(',');
}
s.append(getChild(c).toStringAST());
}
s.append('}');
return s.toString();
}
/**
* @return whether this list is a constant value
*/
public boolean isConstant() {
return constant != null;
}
@SuppressWarnings("unchecked")
private List<Object> getConstantValue() {
return (List<Object>) constant.getValue();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
/**
* Expression language AST node that represents an integer literal.
*
* @author Andy Clement
* @since 3.0
*/
public class IntLiteral extends Literal {
private final TypedValue value;
IntLiteral(String payload, int pos, int value) {
super(payload, pos);
this.value = new TypedValue(value);
}
@Override
public TypedValue getLiteralValue() {
return this.value;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.expression.spel.InternalParseException;
/**
* Common superclass for nodes representing literals (boolean, string, number, etc).
*
* @author Andy Clement
*/
public abstract class Literal extends SpelNodeImpl {
protected String literalValue;
public Literal(String payload, int pos) {
super(pos);
this.literalValue = payload;
}
public abstract TypedValue getLiteralValue();
@Override
public final TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
return getLiteralValue();
}
@Override
public String toString() {
return getLiteralValue().getValue().toString();
}
@Override
public String toStringAST() {
return toString();
}
/**
* Process the string form of a number, using the specified base if supplied and return an appropriate literal to
* hold it. Any suffix to indicate a long will be taken into account (either 'l' or 'L' is supported).
*
* @param numberToken the token holding the number as its payload (eg. 1234 or 0xCAFE)
* @param radix the base of number
* @return a subtype of Literal that can represent it
*/
public static Literal getIntLiteral(String numberToken, int pos, int radix) {
try {
int value = Integer.parseInt(numberToken, radix);
return new IntLiteral(numberToken, pos, value);
} catch (NumberFormatException nfe) {
throw new InternalParseException(new SpelParseException(pos>>16, nfe, SpelMessage.NOT_AN_INTEGER, numberToken));
}
}
public static Literal getLongLiteral(String numberToken, int pos, int radix) {
try {
long value = Long.parseLong(numberToken, radix);
return new LongLiteral(numberToken, pos, value);
} catch (NumberFormatException nfe) {
throw new InternalParseException(new SpelParseException(pos>>16, nfe, SpelMessage.NOT_A_LONG, numberToken));
}
}
// TODO should allow for 'f' for float, not just double
public static Literal getRealLiteral(String numberToken, int pos, boolean isFloat) {
try {
if (isFloat) {
float value = Float.parseFloat(numberToken);
return new RealLiteral(numberToken, pos, value);
} else {
double value = Double.parseDouble(numberToken);
return new RealLiteral(numberToken, pos, value);
}
} catch (NumberFormatException nfe) {
throw new InternalParseException(new SpelParseException(pos>>16, nfe, SpelMessage.NOT_A_REAL, numberToken));
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
/**
* Expression language AST node that represents a long integer literal.
*
* @author Andy Clement
* @since 3.0
*/
public class LongLiteral extends Literal {
private final TypedValue value;
LongLiteral(String payload, int pos, long value) {
super(payload, pos);
this.value = new TypedValue(value);
}
@Override
public TypedValue getLiteralValue() {
return this.value;
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.ExpressionInvocationTargetException;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class MethodReference extends SpelNodeImpl {
private final String name;
private final boolean nullSafe;
private volatile MethodExecutor cachedExecutor;
public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) {
super(pos,arguments);
this.name = methodName;
this.nullSafe = nullSafe;
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue currentContext = state.getActiveContextObject();
Object[] arguments = new Object[getChildCount()];
for (int i = 0; i < arguments.length; i++) {
// Make the root object the active context again for evaluating the parameter
// expressions
try {
state.pushActiveContextObject(state.getRootContextObject());
arguments[i] = children[i].getValueInternal(state).getValue();
}
finally {
state.popActiveContextObject();
}
}
if (currentContext.getValue() == null) {
if (this.nullSafe) {
return TypedValue.NULL;
}
else {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED,
FormatHelper.formatMethodForMessage(name, getTypes(arguments)));
}
}
MethodExecutor executorToUse = this.cachedExecutor;
if (executorToUse != null) {
try {
return executorToUse.execute(
state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments);
}
catch (AccessException ae) {
// Two reasons this can occur:
// 1. the method invoked actually threw a real exception
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
// In the first case we should not retry, in the second case we should see if there is a
// better suited method.
// To determine which situation it is, the AccessException will contain a cause.
// If the cause is an InvocationTargetException, a user exception was thrown inside the method.
// Otherwise the method could not be invoked.
throwSimpleExceptionIfPossible(state, ae);
// at this point we know it wasn't a user problem so worth a retry if a better candidate can be found
this.cachedExecutor = null;
}
}
// either there was no accessor or it no longer existed
executorToUse = findAccessorForMethod(this.name, getTypes(arguments), state);
this.cachedExecutor = executorToUse;
try {
return executorToUse.execute(
state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments);
} catch (AccessException ae) {
// Same unwrapping exception handling as above in above catch block
throwSimpleExceptionIfPossible(state, ae);
throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION,
this.name, state.getActiveContextObject().getValue().getClass().getName(), ae.getMessage());
}
}
/**
* Decode the AccessException, throwing a lightweight evaluation exception or, if the cause was a RuntimeException,
* throw the RuntimeException directly.
*/
private void throwSimpleExceptionIfPossible(ExpressionState state, AccessException ae) {
if (ae.getCause() instanceof InvocationTargetException) {
Throwable rootCause = ae.getCause().getCause();
if (rootCause instanceof RuntimeException) {
throw (RuntimeException) rootCause;
}
else {
throw new ExpressionInvocationTargetException(getStartPosition(),
"A problem occurred when trying to execute method '" + this.name +
"' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'",
rootCause);
}
}
}
private List<TypeDescriptor> getTypes(Object... arguments) {
List<TypeDescriptor> descriptors = new ArrayList<TypeDescriptor>(arguments.length);
for (Object argument : arguments) {
descriptors.add(TypeDescriptor.forObject(argument));
}
return descriptors;
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append(name).append("(");
for (int i = 0; i < getChildCount(); i++) {
if (i > 0)
sb.append(",");
sb.append(getChild(i).toStringAST());
}
sb.append(")");
return sb.toString();
}
private MethodExecutor findAccessorForMethod(String name, List<TypeDescriptor> argumentTypes, ExpressionState state)
throws SpelEvaluationException {
TypedValue context = state.getActiveContextObject();
Object contextObject = context.getValue();
EvaluationContext eContext = state.getEvaluationContext();
List<MethodResolver> mResolvers = eContext.getMethodResolvers();
if (mResolvers != null) {
for (MethodResolver methodResolver : mResolvers) {
try {
MethodExecutor cEx = methodResolver.resolve(
state.getEvaluationContext(), contextObject, name, argumentTypes);
if (cEx != null) {
return cEx;
}
}
catch (AccessException ex) {
throw new SpelEvaluationException(getStartPosition(),ex, SpelMessage.PROBLEM_LOCATING_METHOD, name, contextObject.getClass());
}
}
}
throw new SpelEvaluationException(getStartPosition(),SpelMessage.METHOD_NOT_FOUND, FormatHelper.formatMethodForMessage(name, argumentTypes),
FormatHelper.formatClassNameForMessage(contextObject instanceof Class ? ((Class<?>) contextObject) : contextObject.getClass()));
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
/**
* @author Andy Clement
* @since 3.0
*/
public class NullLiteral extends Literal {
public NullLiteral(int pos) {
super(null,pos);
}
@Override
public TypedValue getLiteralValue() {
return TypedValue.NULL;
}
@Override
public String toString() {
return "null";
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Represents the boolean AND operation.
*
* @author Andy Clement
* @author Mark Fisher
* @since 3.0
*/
public class OpAnd extends Operator {
public OpAnd(int pos, SpelNodeImpl... operands) {
super("and", pos, operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
boolean leftValue;
boolean rightValue;
try {
TypedValue typedValue = getLeftOperand().getValueInternal(state);
this.assertTypedValueNotNull(typedValue);
leftValue = (Boolean)state.convertValue(typedValue, TypeDescriptor.valueOf(Boolean.class));
}
catch (SpelEvaluationException ee) {
ee.setPosition(getLeftOperand().getStartPosition());
throw ee;
}
if (leftValue == false) {
return BooleanTypedValue.forValue(false); // no need to evaluate right operand
}
try {
TypedValue typedValue = getRightOperand().getValueInternal(state);
this.assertTypedValueNotNull(typedValue);
rightValue = (Boolean)state.convertValue(typedValue, TypeDescriptor.valueOf(Boolean.class));
}
catch (SpelEvaluationException ee) {
ee.setPosition(getRightOperand().getStartPosition());
throw ee;
}
return /* leftValue && */BooleanTypedValue.forValue(rightValue);
}
private void assertTypedValueNotNull(TypedValue typedValue) {
if (TypedValue.NULL.equals(typedValue)) {
throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean");
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* Implements division operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OpDivide extends Operator {
public OpDivide(int pos, SpelNodeImpl... operands) {
super("/", pos, operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object operandOne = getLeftOperand().getValueInternal(state).getValue();
Object operandTwo = getRightOperand().getValueInternal(state).getValue();
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number op1 = (Number) operandOne;
Number op2 = (Number) operandTwo;
if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(op1.doubleValue() / op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return new TypedValue(op1.longValue() / op2.longValue());
} else { // TODO what about non-int result of the division?
return new TypedValue(op1.intValue() / op2.intValue());
}
}
Object result = state.operate(Operation.DIVIDE, operandOne, operandTwo);
return new TypedValue(result);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Implements equality operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OpEQ extends Operator {
public OpEQ(int pos, SpelNodeImpl... operands) {
super("==", pos, operands);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
if (left instanceof Number && right instanceof Number) {
Number op1 = (Number) left;
Number op2 = (Number) right;
if (op1 instanceof Double || op2 instanceof Double) {
return BooleanTypedValue.forValue(op1.doubleValue() == op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return BooleanTypedValue.forValue(op1.longValue() == op2.longValue());
} else {
return BooleanTypedValue.forValue(op1.intValue() == op2.intValue());
}
}
if (left!=null && (left instanceof Comparable)) {
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) == 0);
} else {
return BooleanTypedValue.forValue(left==right);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Implements greater-than-or-equal operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OpGE extends Operator {
public OpGE(int pos, SpelNodeImpl... operands) {
super(">=", pos, operands);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
if (left instanceof Number && right instanceof Number) {
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || rightNumber instanceof Double) {
return BooleanTypedValue.forValue(leftNumber.doubleValue() >= rightNumber.doubleValue());
} else if (leftNumber instanceof Long || rightNumber instanceof Long) {
return BooleanTypedValue.forValue( leftNumber.longValue() >= rightNumber.longValue());
} else {
return BooleanTypedValue.forValue(leftNumber.intValue() >= rightNumber.intValue());
}
}
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) >= 0);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Implements greater-than operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OpGT extends Operator {
public OpGT(int pos, SpelNodeImpl... operands) {
super(">", pos, operands);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
if (left instanceof Number && right instanceof Number) {
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || rightNumber instanceof Double) {
return BooleanTypedValue.forValue(leftNumber.doubleValue() > rightNumber.doubleValue());
} else if (leftNumber instanceof Long || rightNumber instanceof Long) {
return BooleanTypedValue.forValue(leftNumber.longValue() > rightNumber.longValue());
} else {
return BooleanTypedValue.forValue(leftNumber.intValue() > rightNumber.intValue());
}
}
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) > 0);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Implements the less-than-or-equal operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OpLE extends Operator {
public OpLE(int pos, SpelNodeImpl... operands) {
super("<=", pos, operands);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
if (left instanceof Number && right instanceof Number) {
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || rightNumber instanceof Double) {
return BooleanTypedValue.forValue(leftNumber.doubleValue() <= rightNumber.doubleValue());
} else if (leftNumber instanceof Long || rightNumber instanceof Long) {
return BooleanTypedValue.forValue(leftNumber.longValue() <= rightNumber.longValue());
} else {
return BooleanTypedValue.forValue(leftNumber.intValue() <= rightNumber.intValue());
}
}
return BooleanTypedValue.forValue( state.getTypeComparator().compare(left, right) <= 0);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Implements the less-than operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OpLT extends Operator {
public OpLT(int pos, SpelNodeImpl... operands) {
super("<", pos, operands);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
// TODO could leave all of these to the comparator - just seems quicker to do some here
if (left instanceof Number && right instanceof Number) {
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || rightNumber instanceof Double) {
return BooleanTypedValue.forValue(leftNumber.doubleValue() < rightNumber.doubleValue());
} else if (leftNumber instanceof Long || rightNumber instanceof Long) {
return BooleanTypedValue.forValue(leftNumber.longValue() < rightNumber.longValue());
} else {
return BooleanTypedValue.forValue(leftNumber.intValue() < rightNumber.intValue());
}
}
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) < 0);
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* The minus operator supports:
* <ul>
* <li>subtraction of doubles (floats are represented as doubles)
* <li>subtraction of longs
* <li>subtraction of integers
* <li>subtraction of an int from a string of one character (effectively decreasing that character), so 'd'-3='a'
* </ul>
* It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed
* when the operand types vary (double-int=double).
* For other options it defers to the registered overloader.
*
* @author Andy Clement
* @since 3.0
*/
public class OpMinus extends Operator {
public OpMinus(int pos, SpelNodeImpl... operands) {
super("-", pos, operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
SpelNodeImpl leftOp = getLeftOperand();
SpelNodeImpl rightOp = getRightOperand();
if (rightOp == null) {// If only one operand, then this is unary minus
Object operand = leftOp.getValueInternal(state).getValue();
if (operand instanceof Number) {
Number n = (Number) operand;
if (operand instanceof Double) {
return new TypedValue(0 - n.doubleValue());
} else if (operand instanceof Long) {
return new TypedValue(0 - n.longValue());
} else {
return new TypedValue(0 - n.intValue());
}
}
return state.operate(Operation.SUBTRACT, operand, null);
} else {
Object left = leftOp.getValueInternal(state).getValue();
Object right = rightOp.getValueInternal(state).getValue();
if (left instanceof Number && right instanceof Number) {
Number op1 = (Number) left;
Number op2 = (Number) right;
if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(op1.doubleValue() - op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return new TypedValue(op1.longValue() - op2.longValue());
} else {
return new TypedValue(op1.intValue() - op2.intValue());
}
} else if (left instanceof String && right instanceof Integer && ((String)left).length()==1) {
String theString = (String) left;
Integer theInteger = (Integer) right;
// implements character - int (ie. b - 1 = a)
return new TypedValue(Character.toString((char) (theString.charAt(0) - theInteger)));
}
return state.operate(Operation.SUBTRACT, left, right);
}
}
@Override
public String toStringAST() {
if (getRightOperand() == null) { // unary minus
return new StringBuilder().append("-").append(getLeftOperand().toStringAST()).toString();
}
return super.toStringAST();
}
public SpelNodeImpl getRightOperand() {
if (children.length<2) {return null;}
return children[1];
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* Implements the modulus operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OpModulus extends Operator {
public OpModulus(int pos, SpelNodeImpl... operands) {
super("%", pos, operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object operandOne = getLeftOperand().getValueInternal(state).getValue();
Object operandTwo = getRightOperand().getValueInternal(state).getValue();
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number op1 = (Number) operandOne;
Number op2 = (Number) operandTwo;
if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(op1.doubleValue() % op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return new TypedValue(op1.longValue() % op2.longValue());
} else {
return new TypedValue(op1.intValue() % op2.intValue());
}
}
return state.operate(Operation.MODULUS, operandOne, operandTwo);
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* Implements the multiply operator. Conversions and promotions:
* http://java.sun.com/docs/books/jls/third_edition/html/conversions.html Section 5.6.2:
*
* <p>If any of the operands is of a reference type, unboxing conversion (<28>5.1.8) is performed. Then:<br>
* If either operand is of type double, the other is converted to double.<br>
* Otherwise, if either operand is of type float, the other is converted to float.<br>
* Otherwise, if either operand is of type long, the other is converted to long.<br>
* Otherwise, both operands are converted to type int.
*
* <p>
*
* @author Andy Clement
* @since 3.0
*/
public class OpMultiply extends Operator {
public OpMultiply(int pos, SpelNodeImpl... operands) {
super("*", pos, operands);
}
/**
* Implements multiply directly here for some types of operand, otherwise delegates to any registered overloader for
* types it does not recognize. Supported types here are:
* <ul>
* <li>integers
* <li>doubles
* <li>string and int ('abc' * 2 == 'abcabc')
* </ul>
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object operandOne = getLeftOperand().getValueInternal(state).getValue();
Object operandTwo = getRightOperand().getValueInternal(state).getValue();
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number leftNumber = (Number) operandOne;
Number rightNumber = (Number) operandTwo;
if (leftNumber instanceof Double || rightNumber instanceof Double) {
return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue());
} else if (leftNumber instanceof Long || rightNumber instanceof Long) {
return new TypedValue(leftNumber.longValue() * rightNumber.longValue());
} else {
return new TypedValue(leftNumber.intValue() * rightNumber.intValue());
}
} else if (operandOne instanceof String && operandTwo instanceof Integer) {
int repeats = (Integer) operandTwo;
StringBuilder result = new StringBuilder();
for (int i = 0; i < repeats; i++) {
result.append(operandOne);
}
return new TypedValue(result.toString());
}
return state.operate(Operation.MULTIPLY, operandOne, operandTwo);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Implements the not-equal operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OpNE extends Operator {
public OpNE(int pos, SpelNodeImpl... operands) {
super("!=", pos, operands);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
if (left instanceof Number && right instanceof Number) {
Number op1 = (Number) left;
Number op2 = (Number) right;
if (op1 instanceof Double || op2 instanceof Double) {
return BooleanTypedValue.forValue(op1.doubleValue() != op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return BooleanTypedValue.forValue(op1.longValue() != op2.longValue());
} else {
return BooleanTypedValue.forValue(op1.intValue() != op2.intValue());
}
}
if (left!=null && (left instanceof Comparable)) {
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) != 0);
} else {
return BooleanTypedValue.forValue(left!=right);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Represents the boolean OR operation.
*
* @author Andy Clement
* @author Mark Fisher
* @since 3.0
*/
public class OpOr extends Operator {
public OpOr(int pos, SpelNodeImpl... operands) {
super("or", pos, operands);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
boolean leftValue;
boolean rightValue;
try {
TypedValue typedValue = getLeftOperand().getValueInternal(state);
this.assertTypedValueNotNull(typedValue);
leftValue = (Boolean)state.convertValue(typedValue, TypeDescriptor.valueOf(Boolean.class));
}
catch (SpelEvaluationException see) {
see.setPosition(getLeftOperand().getStartPosition());
throw see;
}
if (leftValue == true) {
return BooleanTypedValue.TRUE; // no need to evaluate right operand
}
try {
TypedValue typedValue = getRightOperand().getValueInternal(state);
this.assertTypedValueNotNull(typedValue);
rightValue = (Boolean)state.convertValue(typedValue, TypeDescriptor.valueOf(Boolean.class));
}
catch (SpelEvaluationException see) {
see.setPosition(getRightOperand().getStartPosition()); // TODO end positions here and in similar situations
throw see;
}
return BooleanTypedValue.forValue(leftValue || rightValue);
}
private void assertTypedValueNotNull(TypedValue typedValue) {
if (TypedValue.NULL.equals(typedValue)) {
throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean");
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* The plus operator will:
* <ul>
* <li>add doubles (floats are represented as doubles)
* <li>add longs
* <li>add integers
* <li>concatenate strings
* </ul>
* It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed
* when the operand types vary (double+int=double). For other options it defers to the registered overloader.
*
* @author Andy Clement
* @since 3.0
*/
public class OpPlus extends Operator {
public OpPlus(int pos, SpelNodeImpl... operands) {
super("+", pos, operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
SpelNodeImpl leftOp = getLeftOperand();
SpelNodeImpl rightOp = getRightOperand();
if (rightOp == null) { // If only one operand, then this is unary plus
Object operandOne = leftOp.getValueInternal(state).getValue();
if (operandOne instanceof Number) {
if (operandOne instanceof Double) {
return new TypedValue(((Double) operandOne).doubleValue());
} else if (operandOne instanceof Long) {
return new TypedValue(((Long) operandOne).longValue());
} else {
return new TypedValue(((Integer) operandOne).intValue());
}
}
return state.operate(Operation.ADD, operandOne, null);
}
else {
Object operandOne = leftOp.getValueInternal(state).getValue();
Object operandTwo = rightOp.getValueInternal(state).getValue();
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number op1 = (Number) operandOne;
Number op2 = (Number) operandTwo;
if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(op1.doubleValue() + op2.doubleValue());
} else if (op1 instanceof Long || op2 instanceof Long) {
return new TypedValue(op1.longValue() + op2.longValue());
} else { // TODO what about overflow?
return new TypedValue(op1.intValue() + op2.intValue());
}
} else if (operandOne instanceof String && operandTwo instanceof String) {
return new TypedValue(new StringBuilder((String) operandOne).append((String) operandTwo).toString());
} else if (operandOne instanceof String) {
StringBuilder result = new StringBuilder((String)operandOne);
result.append((operandTwo==null?"null":operandTwo.toString()));
return new TypedValue(result.toString());
} else if (operandTwo instanceof String) {
StringBuilder result = new StringBuilder((operandOne==null?"null":operandOne.toString()));
result.append((String)operandTwo);
return new TypedValue(result.toString());
}
return state.operate(Operation.ADD, operandOne, operandTwo);
}
}
@Override
public String toStringAST() {
if (children.length<2) { // unary plus
return new StringBuilder().append("+").append(getLeftOperand().toStringAST()).toString();
}
return super.toStringAST();
}
public SpelNodeImpl getRightOperand() {
if (children.length<2) {return null;}
return children[1];
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
/**
* Common supertype for operators that operate on either one or two operands. In the case of multiply or divide there
* would be two operands, but for unary plus or minus, there is only one.
*
* @author Andy Clement
* @since 3.0
*/
public abstract class Operator extends SpelNodeImpl {
String operatorName;
public Operator(String payload,int pos,SpelNodeImpl... operands) {
super(pos, operands);
this.operatorName = payload;
}
public SpelNodeImpl getLeftOperand() {
return children[0];
}
public SpelNodeImpl getRightOperand() {
return children[1];
}
public final String getOperatorName() {
return operatorName;
}
/**
* String format for all operators is the same '(' [operand] [operator] [operand] ')'
*/
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append("(");
sb.append(getChild(0).toStringAST());
for (int i = 1; i < getChildCount(); i++) {
sb.append(" ").append(getOperatorName()).append(" ");
sb.append(getChild(i).toStringAST());
}
sb.append(")");
return sb.toString();
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.util.List;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Represents the between operator. The left operand to between must be a single value and the right operand must be a
* list - this operator returns true if the left operand is between (using the registered comparator) the two elements
* in the list. The definition of between being inclusive follows the SQL BETWEEN definition.
*
* @author Andy Clement
* @since 3.0
*/
public class OperatorBetween extends Operator {
public OperatorBetween(int pos, SpelNodeImpl... operands) {
super("between", pos, operands);
}
/**
* Returns a boolean based on whether a value is in the range expressed. The first operand is any value whilst the
* second is a list of two values - those two values being the bounds allowed for the first operand (inclusive).
* @param state the expression state
* @return true if the left operand is in the range specified, false otherwise
* @throws EvaluationException if there is a problem evaluating the expression
*/
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object left = getLeftOperand().getValueInternal(state).getValue();
Object right = getRightOperand().getValueInternal(state).getValue();
if (!(right instanceof List) || ((List<?>) right).size() != 2) {
throw new SpelEvaluationException(getRightOperand().getStartPosition(),
SpelMessage.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST);
}
List<?> l = (List<?>) right;
Object low = l.get(0);
Object high = l.get(1);
TypeComparator comparator = state.getTypeComparator();
try {
return BooleanTypedValue.forValue((comparator.compare(left, low) >= 0 && comparator.compare(left, high) <= 0));
} catch (SpelEvaluationException ex) {
ex.setPosition(getStartPosition());
throw ex;
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* The operator 'instanceof' checks if an object is of the class specified in the right hand operand,
* in the same way that <code>instanceof</code> does in Java.
*
* @author Andy Clement
* @since 3.0
*/
public class OperatorInstanceof extends Operator {
public OperatorInstanceof(int pos, SpelNodeImpl... operands) {
super("instanceof", pos, operands);
}
/**
* Compare the left operand to see it is an instance of the type specified as the right operand.
* The right operand must be a class.
* @param state the expression state
* @return true if the left operand is an instanceof of the right operand, otherwise false
* @throws EvaluationException if there is a problem evaluating the expression
*/
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue left = getLeftOperand().getValueInternal(state);
TypedValue right = getRightOperand().getValueInternal(state);
Object leftValue = left.getValue();
Object rightValue = right.getValue();
if (leftValue == null) {
return BooleanTypedValue.FALSE; // null is not an instanceof anything
}
if (rightValue == null || !(rightValue instanceof Class<?>)) {
throw new SpelEvaluationException(getRightOperand().getStartPosition(),
SpelMessage.INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND,
(rightValue == null ? "null" : rightValue.getClass().getName()));
}
Class<?> rightClass = (Class<?>) rightValue;
return BooleanTypedValue.forValue(rightClass.isAssignableFrom(leftValue.getClass()));
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Implements the matches operator. Matches takes two operands. The first is a string and the second is a java regex. It
* will return true when getValue() is called if the first operand matches the regex.
*
* @author Andy Clement
* @since 3.0
*/
public class OperatorMatches extends Operator {
public OperatorMatches(int pos, SpelNodeImpl... operands) {
super("matches", pos, operands);
}
/**
* Check the first operand matches the regex specified as the second operand.
* @param state the expression state
* @return true if the first operand matches the regex specified as the second operand, otherwise false
* @throws EvaluationException if there is a problem evaluating the expression (e.g. the regex is invalid)
*/
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
SpelNodeImpl leftOp = getLeftOperand();
SpelNodeImpl rightOp = getRightOperand();
Object left = leftOp.getValue(state, String.class);
Object right = getRightOperand().getValueInternal(state).getValue();
try {
if (!(left instanceof String)) {
throw new SpelEvaluationException(leftOp.getStartPosition(),
SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, left);
}
if (!(right instanceof String)) {
throw new SpelEvaluationException(rightOp.getStartPosition(),
SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right);
}
Pattern pattern = Pattern.compile((String) right);
Matcher matcher = pattern.matcher((String) left);
return BooleanTypedValue.forValue(matcher.matches());
}
catch (PatternSyntaxException pse) {
throw new SpelEvaluationException(rightOp.getStartPosition(), pse, SpelMessage.INVALID_PATTERN, right);
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Represents a NOT operation.
*
* @author Andy Clement
* @author Mark Fisher
* @since 3.0
*/
public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do not extend BinaryOperator
public OperatorNot(int pos, SpelNodeImpl operand) {
super(pos, operand);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
try {
TypedValue typedValue = children[0].getValueInternal(state);
if (TypedValue.NULL.equals(typedValue)) {
throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean");
}
boolean value = (Boolean) state.convertValue(typedValue, TypeDescriptor.valueOf(Boolean.class));
return BooleanTypedValue.forValue(!value);
}
catch (SpelEvaluationException see) {
see.setPosition(getChild(0).getStartPosition());
throw see;
}
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append("!").append(getChild(0).toStringAST());
return sb.toString();
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* The power operator.
*
* @author Andy Clement
* @since 3.0
*/
public class OperatorPower extends Operator {
public OperatorPower(int pos, SpelNodeImpl... operands) {
super("^", pos, operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
SpelNodeImpl leftOp = getLeftOperand();
SpelNodeImpl rightOp = getRightOperand();
Object operandOne = leftOp.getValueInternal(state).getValue();
Object operandTwo = rightOp.getValueInternal(state).getValue();
if (operandOne instanceof Number && operandTwo instanceof Number) {
Number op1 = (Number) operandOne;
Number op2 = (Number) operandTwo;
if (op1 instanceof Double || op2 instanceof Double) {
return new TypedValue(Math.pow(op1.doubleValue(),op2.doubleValue()));
} else if (op1 instanceof Long || op2 instanceof Long) {
double d= Math.pow(op1.longValue(), op2.longValue());
return new TypedValue((long)d);
} else {
double d= Math.pow(op1.longValue(), op2.longValue());
if (d > Integer.MAX_VALUE) {
return new TypedValue((long)d);
} else {
return new TypedValue((int)d);
}
}
}
return state.operate(Operation.POWER, operandOne, operandTwo);
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Represents projection, where a given operation is performed on all elements in some input sequence, returning
* a new sequence of the same size. For example:
* "{1,2,3,4,5,6,7,8,9,10}.!{#isEven(#this)}" returns "[n, y, n, y, n, y, n, y, n, y]"
*
* @author Andy Clement
* @author Mark Fisher
* @since 3.0
*/
public class Projection extends SpelNodeImpl {
private final boolean nullSafe;
public Projection(boolean nullSafe, int pos, SpelNodeImpl expression) {
super(pos, expression);
this.nullSafe = nullSafe;
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue op = state.getActiveContextObject();
Object operand = op.getValue();
boolean operandIsArray = ObjectUtils.isArray(operand);
// TypeDescriptor operandTypeDescriptor = op.getTypeDescriptor();
// When the input is a map, we push a special context object on the stack
// before calling the specified operation. This special context object
// has two fields 'key' and 'value' that refer to the map entries key
// and value, and they can be referenced in the operation
// eg. {'a':'y','b':'n'}.!{value=='y'?key:null}" == ['a', null]
if (operand instanceof Map) {
Map<?, ?> mapData = (Map<?, ?>) operand;
List<Object> result = new ArrayList<Object>();
for (Map.Entry entry : mapData.entrySet()) {
try {
state.pushActiveContextObject(new TypedValue(entry));
result.add(this.children[0].getValueInternal(state).getValue());
}
finally {
state.popActiveContextObject();
}
}
return new TypedValue(result); // TODO unable to build correct type descriptor
}
else if (operand instanceof Collection || operandIsArray) {
Collection<?> data = (operand instanceof Collection ? (Collection<?>) operand :
Arrays.asList(ObjectUtils.toObjectArray(operand)));
List<Object> result = new ArrayList<Object>();
int idx = 0;
Class<?> arrayElementType = null;
for (Object element : data) {
try {
state.pushActiveContextObject(new TypedValue(element));
state.enterScope("index", idx);
Object value = children[0].getValueInternal(state).getValue();
if (value != null && operandIsArray) {
arrayElementType = determineCommonType(arrayElementType, value.getClass());
}
result.add(value);
}
finally {
state.exitScope();
state.popActiveContextObject();
}
idx++;
}
if (operandIsArray) {
if (arrayElementType == null) {
arrayElementType = Object.class;
}
Object resultArray = Array.newInstance(arrayElementType, result.size());
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
return new TypedValue(resultArray);
}
return new TypedValue(result);
}
else {
if (operand==null) {
if (this.nullSafe) {
return TypedValue.NULL;
}
else {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, "null");
}
}
else {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, operand.getClass().getName());
}
}
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
return sb.append("![").append(getChild(0).toStringAST()).append("]").toString();
}
private Class<?> determineCommonType(Class<?> oldType, Class<?> newType) {
if (oldType == null) {
return newType;
}
if (oldType.isAssignableFrom(newType)) {
return oldType;
}
Class<?> nextType = newType;
while (nextType != Object.class) {
if (nextType.isAssignableFrom(oldType)) {
return nextType;
}
nextType = nextType.getSuperclass();
}
Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(newType);
for (Class<?> nextInterface : interfaces) {
if (nextInterface.isAssignableFrom(oldType)) {
return nextInterface;
}
}
return Object.class;
}
}

View File

@@ -0,0 +1,321 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
/**
* Represents a simple property or field reference.
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Clark Duplichien
* @since 3.0
*/
public class PropertyOrFieldReference extends SpelNodeImpl {
private final boolean nullSafe;
private final String name;
private volatile PropertyAccessor cachedReadAccessor;
private volatile PropertyAccessor cachedWriteAccessor;
public PropertyOrFieldReference(boolean nullSafe, String propertyOrFieldName, int pos) {
super(pos);
this.nullSafe = nullSafe;
this.name = propertyOrFieldName;
}
public boolean isNullSafe() {
return this.nullSafe;
}
public String getName() {
return this.name;
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue result = readProperty(state, this.name);
// Dynamically create the objects if the user has requested that optional behaviour
if (result.getValue() == null && state.getConfiguration().isAutoGrowNullReferences() &&
nextChildIs(Indexer.class, PropertyOrFieldReference.class)) {
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
// Creating lists and maps
if ((resultDescriptor.getType().equals(List.class) || resultDescriptor.getType().equals(Map.class))) {
// Create a new collection or map ready for the indexer
if (resultDescriptor.getType().equals(List.class)) {
try {
if (isWritable(state)) {
List newList = ArrayList.class.newInstance();
writeProperty(state, this.name, newList);
result = readProperty(state, this.name);
}
}
catch (InstantiationException ex) {
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
}
catch (IllegalAccessException ex) {
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
}
}
else {
try {
if (isWritable(state)) {
Map newMap = HashMap.class.newInstance();
writeProperty(state, name, newMap);
result = readProperty(state, this.name);
}
}
catch (InstantiationException ex) {
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.UNABLE_TO_CREATE_MAP_FOR_INDEXING);
}
catch (IllegalAccessException ex) {
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.UNABLE_TO_CREATE_MAP_FOR_INDEXING);
}
}
}
else {
// 'simple' object
try {
if (isWritable(state)) {
Object newObject = result.getTypeDescriptor().getType().newInstance();
writeProperty(state, name, newObject);
result = readProperty(state, this.name);
}
}
catch (InstantiationException ex) {
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.UNABLE_TO_DYNAMICALLY_CREATE_OBJECT, result.getTypeDescriptor().getType());
}
catch (IllegalAccessException ex) {
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.UNABLE_TO_DYNAMICALLY_CREATE_OBJECT, result.getTypeDescriptor().getType());
}
}
}
return result;
}
@Override
public void setValue(ExpressionState state, Object newValue) throws SpelEvaluationException {
writeProperty(state, this.name, newValue);
}
@Override
public boolean isWritable(ExpressionState state) throws SpelEvaluationException {
return isWritableProperty(this.name, state);
}
@Override
public String toStringAST() {
return this.name;
}
/**
* Attempt to read the named property from the current context object.
* @param state the evaluation state
* @param name the name of the property
* @return the value of the property
* @throws SpelEvaluationException if any problem accessing the property or it cannot be found
*/
private TypedValue readProperty(ExpressionState state, String name) throws EvaluationException {
TypedValue contextObject = state.getActiveContextObject();
Object targetObject = contextObject.getValue();
if (targetObject == null && this.nullSafe) {
return TypedValue.NULL;
}
PropertyAccessor accessorToUse = this.cachedReadAccessor;
if (accessorToUse != null) {
try {
return accessorToUse.read(state.getEvaluationContext(), contextObject.getValue(), name);
}
catch (AccessException ae) {
// this is OK - it may have gone stale due to a class change,
// let's try to get a new one and call it before giving up
this.cachedReadAccessor = null;
}
}
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state);
EvaluationContext eContext = state.getEvaluationContext();
// Go through the accessors that may be able to resolve it. If they are a cacheable accessor then
// get the accessor and use it. If they are not cacheable but report they can read the property
// then ask them to read it
if (accessorsToTry != null) {
try {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canRead(eContext, contextObject.getValue(), name)) {
if (accessor instanceof ReflectivePropertyAccessor) {
accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor(
eContext, contextObject.getValue(), name);
}
this.cachedReadAccessor = accessor;
return accessor.read(eContext, contextObject.getValue(), name);
}
}
}
catch (AccessException ae) {
throw new SpelEvaluationException(ae, SpelMessage.EXCEPTION_DURING_PROPERTY_READ, name, ae.getMessage());
}
}
if (contextObject.getValue() == null) {
throw new SpelEvaluationException(SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL, name);
}
else {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, name,
FormatHelper.formatClassNameForMessage(contextObjectClass));
}
}
private void writeProperty(ExpressionState state, String name, Object newValue) throws SpelEvaluationException {
TypedValue contextObject = state.getActiveContextObject();
EvaluationContext eContext = state.getEvaluationContext();
if (contextObject.getValue() == null && nullSafe) {
return;
}
PropertyAccessor accessorToUse = this.cachedWriteAccessor;
if (accessorToUse != null) {
try {
accessorToUse.write(state.getEvaluationContext(), contextObject.getValue(), name, newValue);
return;
}
catch (AccessException ae) {
// this is OK - it may have gone stale due to a class change,
// let's try to get a new one and call it before giving up
this.cachedWriteAccessor = null;
}
}
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state);
if (accessorsToTry != null) {
try {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canWrite(eContext, contextObject.getValue(), name)) {
this.cachedWriteAccessor = accessor;
accessor.write(eContext, contextObject.getValue(), name, newValue);
return;
}
}
}
catch (AccessException ae) {
throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
name, ae.getMessage());
}
}
if (contextObject.getValue()==null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL, name);
}
else {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE, name,
FormatHelper.formatClassNameForMessage(contextObjectClass));
}
}
public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException {
Object contextObject = state.getActiveContextObject().getValue();
// TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
EvaluationContext eContext = state.getEvaluationContext();
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject), state);
if (resolversToTry != null) {
for (PropertyAccessor pfResolver : resolversToTry) {
try {
if (pfResolver.canWrite(eContext, contextObject, name)) {
return true;
}
}
catch (AccessException ae) {
// let others try
}
}
}
return false;
}
// TODO when there is more time, remove this and use the version in AstUtils
/**
* Determines the set of property resolvers that should be used to try and access a property on the specified target
* type. The resolvers are considered to be in an ordered list, however in the returned list any that are exact
* matches for the input target type (as opposed to 'general' resolvers that could work for any type) are placed at
* the start of the list. In addition, there are specific resolvers that exactly name the class in question and
* resolvers that name a specific class but it is a supertype of the class we have. These are put at the end of the
* specific resolvers set and will be tried after exactly matching accessors but before generic accessors.
* @param targetType the type upon which property access is being attempted
* @return a list of resolvers that should be tried in order to access the property
*/
private List<PropertyAccessor> getPropertyAccessorsToTry(Class<?> targetType, ExpressionState state) {
List<PropertyAccessor> specificAccessors = new ArrayList<PropertyAccessor>();
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
for (PropertyAccessor resolver : state.getPropertyAccessors()) {
Class<?>[] targets = resolver.getSpecificTargetClasses();
if (targets == null) { // generic resolver that says it can be used for any type
generalAccessors.add(resolver);
}
else {
if (targetType != null) {
for (Class<?> clazz : targets) {
if (clazz == targetType) {
specificAccessors.add( resolver);
break;
}
else if (clazz.isAssignableFrom(targetType)) {
generalAccessors.add(resolver);
}
}
}
}
}
List<PropertyAccessor> resolvers = new ArrayList<PropertyAccessor>();
resolvers.addAll(specificAccessors);
generalAccessors.removeAll(specificAccessors);
resolvers.addAll(generalAccessors);
return resolvers;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* Represents a dot separated sequence of strings that indicate a package qualified type reference.
*
* <p>Example: "java.lang.String" as in the expression "new java.lang.String('hello')"
*
* @author Andy Clement
* @since 3.0
*/
public class QualifiedIdentifier extends SpelNodeImpl {
// TODO safe to cache? dont think so
private TypedValue value;
public QualifiedIdentifier(int pos,SpelNodeImpl... operands) {
super(pos,operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
// Cache the concatenation of child identifiers
if (this.value == null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < getChildCount(); i++) {
Object value = children[i].getValueInternal(state).getValue();
if (i > 0 && !value.toString().startsWith("$")) {
sb.append(".");
}
sb.append(value);
}
this.value = new TypedValue(sb.toString());
}
return this.value;
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
if (this.value != null) {
sb.append(this.value.getValue());
} else {
for (int i = 0; i < getChildCount(); i++) {
if (i > 0) {
sb.append(".");
}
sb.append(getChild(i).toStringAST());
}
}
return sb.toString();
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
/**
* @author Andy Clement
* @since 3.0
*/
public class RealLiteral extends Literal {
private final TypedValue value;
public RealLiteral(String payload, int pos, double value) {
super(payload, pos);
this.value = new TypedValue(value);
}
@Override
public TypedValue getLiteralValue() {
return this.value;
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Represents selection over a map or collection.
* For example: {1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'} returns [2, 4, 6, 8, 10]
*
* <p>Basically a subset of the input data is returned based on the
* evaluation of the expression supplied as selection criteria.
*
* @author Andy Clement
* @author Mark Fisher
* @author Sam Brannen
* @since 3.0
*/
public class Selection extends SpelNodeImpl {
public final static int ALL = 0; // ?[]
public final static int FIRST = 1; // ^[]
public final static int LAST = 2; // $[]
private final int variant;
private final boolean nullSafe;
public Selection(boolean nullSafe, int variant,int pos,SpelNodeImpl expression) {
super(pos,expression);
this.nullSafe = nullSafe;
this.variant = variant;
}
@SuppressWarnings("unchecked")
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue op = state.getActiveContextObject();
Object operand = op.getValue();
SpelNodeImpl selectionCriteria = children[0];
if (operand instanceof Map) {
Map<?, ?> mapdata = (Map<?, ?>) operand;
// TODO don't lose generic info for the new map
Map<Object,Object> result = new HashMap<Object,Object>();
Object lastKey = null;
for (Map.Entry entry : mapdata.entrySet()) {
try {
TypedValue kvpair = new TypedValue(entry);
state.pushActiveContextObject(kvpair);
Object o = selectionCriteria.getValueInternal(state).getValue();
if (o instanceof Boolean) {
if (((Boolean) o).booleanValue() == true) {
if (variant == FIRST) {
result.put(entry.getKey(),entry.getValue());
return new TypedValue(result);
}
result.put(entry.getKey(),entry.getValue());
lastKey = entry.getKey();
}
} else {
throw new SpelEvaluationException(selectionCriteria.getStartPosition(),
SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);// ,selectionCriteria.stringifyAST());
}
} finally {
state.popActiveContextObject();
}
}
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
return new TypedValue(null);
}
if (variant == LAST) {
Map resultMap = new HashMap();
Object lastValue = result.get(lastKey);
resultMap.put(lastKey,lastValue);
return new TypedValue(resultMap);
}
return new TypedValue(result);
} else if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) {
List<Object> data = new ArrayList<Object>();
Collection<?> c = (operand instanceof Collection) ?
(Collection<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand));
data.addAll(c);
List<Object> result = new ArrayList<Object>();
int idx = 0;
for (Object element : data) {
try {
state.pushActiveContextObject(new TypedValue(element));
state.enterScope("index", idx);
Object o = selectionCriteria.getValueInternal(state).getValue();
if (o instanceof Boolean) {
if (((Boolean) o).booleanValue() == true) {
if (variant == FIRST) {
return new TypedValue(element);
}
result.add(element);
}
} else {
throw new SpelEvaluationException(selectionCriteria.getStartPosition(),
SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);// ,selectionCriteria.stringifyAST());
}
idx++;
} finally {
state.exitScope();
state.popActiveContextObject();
}
}
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
return TypedValue.NULL;
}
if (variant == LAST) {
return new TypedValue(result.get(result.size() - 1));
}
if (operand instanceof Collection) {
return new TypedValue(result);
}
else {
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType());
Object resultArray = Array.newInstance(elementType, result.size());
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
return new TypedValue(resultArray);
}
} else {
if (operand==null) {
if (nullSafe) {
return TypedValue.NULL;
} else {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
"null");
}
} else {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
operand.getClass().getName());
}
}
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
switch (variant) {
case ALL:
sb.append("?[");
break;
case FIRST:
sb.append("^[");
break;
case LAST:
sb.append("$[");
break;
}
return sb.append(getChild(0).toStringAST()).append("]").toString();
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
/**
* The common supertype of all AST nodes in a parsed Spring Expression Language format expression.
*
* @author Andy Clement
* @since 3.0
*/
public abstract class SpelNodeImpl implements SpelNode {
private static SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0];
protected int pos; // start = top 16bits, end = bottom 16bits
protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
private SpelNodeImpl parent;
public SpelNodeImpl(int pos, SpelNodeImpl... operands) {
this.pos = pos;
// pos combines start and end so can never be zero because tokens cannot be zero length
Assert.isTrue(pos != 0);
if (operands != null && operands.length > 0) {
this.children = operands;
for (SpelNodeImpl childnode : operands) {
childnode.parent = this;
}
}
}
protected SpelNodeImpl getPreviousChild() {
SpelNodeImpl result = null;
if (parent != null) {
for (SpelNodeImpl child : parent.children) {
if (this==child) break;
result = child;
}
}
return result;
}
/**
* @return true if the next child is one of the specified classes
*/
protected boolean nextChildIs(Class... clazzes) {
if (parent!=null) {
SpelNodeImpl[] peers = parent.children;
for (int i=0,max=peers.length;i<max;i++) {
if (peers[i]==this) {
if ((i+1)>=max) {
return false;
} else {
Class clazz = peers[i+1].getClass();
for (Class desiredClazz: clazzes) {
if (clazz.equals(desiredClazz)) {
return true;
}
}
return false;
}
}
}
}
return false;
}
public final Object getValue(ExpressionState expressionState) throws EvaluationException {
if (expressionState != null) {
return getValueInternal(expressionState).getValue();
} else {
// configuration not set - does that matter?
return getValue(new ExpressionState(new StandardEvaluationContext()));
}
}
public final TypedValue getTypedValue(ExpressionState expressionState) throws EvaluationException {
if (expressionState != null) {
return getValueInternal(expressionState);
} else {
// configuration not set - does that matter?
return getTypedValue(new ExpressionState(new StandardEvaluationContext()));
}
}
// by default Ast nodes are not writable
public boolean isWritable(ExpressionState expressionState) throws EvaluationException {
return false;
}
public void setValue(ExpressionState expressionState, Object newValue) throws EvaluationException {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass());
}
public SpelNode getChild(int index) {
return children[index];
}
public int getChildCount() {
return children.length;
}
public Class<?> getObjectClass(Object obj) {
if (obj == null) {
return null;
}
return (obj instanceof Class ? ((Class<?>) obj) : obj.getClass());
}
@SuppressWarnings("unchecked")
protected final <T> T getValue(ExpressionState state, Class<T> desiredReturnType) throws EvaluationException {
Object result = getValueInternal(state).getValue();
if (result != null && desiredReturnType != null) {
Class<?> resultType = result.getClass();
if (desiredReturnType.isAssignableFrom(resultType)) {
return (T) result;
}
// Attempt conversion to the requested type, may throw an exception
return ExpressionUtils.convert(state.getEvaluationContext(), result, desiredReturnType);
}
return (T) result;
}
public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException;
public abstract String toStringAST();
public int getStartPosition() {
return (pos>>16);
}
public int getEndPosition() {
return (pos&0xffff);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
/**
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class StringLiteral extends Literal {
private final TypedValue value;
public StringLiteral(String payload, int pos, String value) {
super(payload,pos);
// TODO should these have been skipped being created by the parser rules? or not?
value = value.substring(1, value.length() - 1);
this.value = new TypedValue(value.replaceAll("''", "'"));
}
@Override
public TypedValue getLiteralValue() {
return this.value;
}
@Override
public String toString() {
return "'" + getLiteralValue().getValue() + "'";
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
* Represents a ternary expression, for example: "someCheck()?true:false".
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class Ternary extends SpelNodeImpl {
public Ternary(int pos, SpelNodeImpl... args) {
super(pos,args);
}
/**
* Evaluate the condition and if true evaluate the first alternative, otherwise evaluate the second alternative.
* @param state the expression state
* @throws EvaluationException if the condition does not evaluate correctly to a boolean or there is a problem
* executing the chosen alternative
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Boolean value = children[0].getValue(state, Boolean.class);
if (value == null) {
throw new SpelEvaluationException(getChild(0).getStartPosition(),
SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean");
}
if (value.booleanValue()) {
return children[1].getValueInternal(state);
} else {
return children[2].getValueInternal(state);
}
}
@Override
public String toStringAST() {
return new StringBuilder().append(getChild(0).toStringAST()).append(" ? ").append(getChild(1).toStringAST())
.append(" : ").append(getChild(2).toStringAST()).toString();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
/**
* Captures primitive types and their corresponding class objects, plus one special entry that represents all reference
* (non-primitive) types.
*
* @author Andy Clement
*/
public enum TypeCode {
OBJECT(Object.class), BOOLEAN(Boolean.TYPE), BYTE(Byte.TYPE), CHAR(Character.TYPE), //
SHORT(Short.TYPE), INT(Integer.TYPE), LONG(Long.TYPE), FLOAT(Float.TYPE), DOUBLE(Double.TYPE);
private Class<?> type;
TypeCode(Class<?> type) {
this.type = type;
}
public Class<?> getType() {
return type;
}
public static TypeCode forName(String name) {
String searchingFor = name.toUpperCase();
TypeCode[] tcs = values();
for (int i = 1; i < tcs.length; i++) {
if (tcs[i].name().equals(searchingFor)) {
return tcs[i];
}
}
return TypeCode.OBJECT;
}
public static TypeCode forClass(Class<?> c) {
TypeCode[] allValues = TypeCode.values();
for (int i = 0; i < allValues.length; i++) {
TypeCode typeCode = allValues[i];
if (c == typeCode.getType()) {
return typeCode;
}
}
return OBJECT;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
/**
* Represents a reference to a type, for example "T(String)" or "T(com.somewhere.Foo)"
*
* @author Andy Clement
*/
public class TypeReference extends SpelNodeImpl {
public TypeReference(int pos,SpelNodeImpl qualifiedId) {
super(pos,qualifiedId);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
// TODO possible optimization here if we cache the discovered type reference, but can we do that?
String typename = (String) children[0].getValueInternal(state).getValue();
if (typename.indexOf(".") == -1 && Character.isLowerCase(typename.charAt(0))) {
TypeCode tc = TypeCode.valueOf(typename.toUpperCase());
if (tc != TypeCode.OBJECT) {
// it is a primitive type
return new TypedValue(tc.getType());
}
}
return new TypedValue(state.findType(typename));
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
sb.append("T(");
sb.append(getChild(0).toStringAST());
sb.append(")");
return sb.toString();
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
/**
* Represents a variable reference, eg. #someVar. Note this is different to a *local* variable like $someVar
*
* @author Andy Clement
* @since 3.0
*/
public class VariableReference extends SpelNodeImpl {
// Well known variables:
private final static String THIS = "this"; // currently active context object
private final static String ROOT = "root"; // root context object
private final String name;
public VariableReference(String variableName, int pos) {
super(pos);
name = variableName;
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
if (this.name.equals(THIS)) {
return state.getActiveContextObject();
}
if (this.name.equals(ROOT)) {
return state.getRootContextObject();
}
TypedValue result = state.lookupVariable(this.name);
// a null value will mean either the value was null or the variable was not found
return result;
}
@Override
public void setValue(ExpressionState state, Object value) throws SpelEvaluationException {
state.setVariable(this.name, value);
}
@Override
public String toStringAST() {
return "#" + this.name;
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
return !(this.name.equals(THIS) || this.name.equals(ROOT));
}
}

View File

@@ -0,0 +1,268 @@
grammar SpringExpressions;
options {
language = Java;
output=AST;
k=2;
}
tokens {
INTEGER_LITERAL;
EXPRESSION;
QUALIFIED_IDENTIFIER;
PROPERTY_OR_FIELD;
INDEXER;
CONSTRUCTOR;
HOLDER;
NAMED_ARGUMENT;
FUNCTIONREF;
TYPEREF;
VARIABLEREF;
METHOD;
ADD;
SUBTRACT;
NUMBER;
}
// applies only to the parser:
@header {package org.springframework.expression.spel.generated;}
// applies only to the lexer:
@lexer::header {package org.springframework.expression.spel.generated;}
@members {
// For collecting info whilst processing rules that can be used in messages
protected Stack<String> paraphrase = new Stack<String>();
}
@rulecatch {
catch(RecognitionException e) {
reportError(e);
throw e;
}
}
expr: expression EOF!;
expression :
logicalOrExpression
( (ASSIGN^ logicalOrExpression)
| (DEFAULT^ logicalOrExpression)
| (QMARK^ expression COLON! expression))?;
parenExpr : LPAREN! expression RPAREN!;
logicalOrExpression
: logicalAndExpression (OR^ logicalAndExpression)*;
logicalAndExpression
: relationalExpression (AND^ relationalExpression)*;
relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
sumExpression
: productExpression ( (PLUS^ | MINUS^) productExpression)*;
productExpression
: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
powerExpr : unaryExpression (POWER^ unaryExpression)? ;
unaryExpression
: (PLUS^ | MINUS^ | BANG^) unaryExpression
| primaryExpression ;
primaryExpression
: startNode (node)? -> ^(EXPRESSION startNode (node)?);
startNode
:
parenExpr
| methodOrProperty
| functionOrVar
| indexer
| literal
| type
| constructor
| projection
| selection
| firstSelection
| lastSelection
;
node
: ((DOT dottedNode) | nonDottedNode)+;
nonDottedNode
: indexer;
dottedNode
:
((methodOrProperty
| functionOrVar
| projection
| selection
| firstSelection
| lastSelection
))
;
functionOrVar
: (POUND ID LPAREN) => function
| var
;
function : POUND id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs);
var : POUND id=ID -> ^(VARIABLEREF[$id]);
methodOrProperty
: (ID LPAREN) => id=ID methodArgs -> ^(METHOD[$id] methodArgs)
| property
;
// may have to preserve these commas to make it easier to offer suggestions in the right place
// mod at 9th feb 19:13 - added the second 'COMMA?' to allow for code completion "foo(A,"
// TODO need to preserve commas and then check for badly formed call later (optimizing tree walk) to disallow "foo(a,b,c,)"
methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!;
// If we match ID then create a node called PROPERTY_OR_FIELD and copy the id info into it.
// this means the propertyOrField.text is what id.text would have been, rather than having to
// access id as a child of the new node.
property: id=ID -> ^(PROPERTY_OR_FIELD[$id]);
indexer: LBRACKET r1=argument (COMMA r2=argument)* RBRACKET -> ^(INDEXER $r1 ($r2)*);
// argument;
// TODO make expression conditional with ? if want completion for when the RCURLY is missing
projection: PROJECT^ expression RBRACKET!;
selection: SELECT^ expression RBRACKET!;
firstSelection: SELECT_FIRST^ expression RBRACKET!;
lastSelection: SELECT_LAST^ expression RBRACKET!;
// TODO cope with array types
type: TYPE qualifiedId RPAREN -> ^(TYPEREF qualifiedId);
//type: TYPE tn=qualifiedId (LBRACKET RBRACKET)? (COMMA qid=qualifiedId)? RPAREN
constructor
: ('new' qualifiedId LPAREN) => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs)
;
ctorArgs
: LPAREN! (namedArgument (COMMA! namedArgument)*)? RPAREN!;
argument : expression;
namedArgument
: (ID ASSIGN) => id=ID ASSIGN expression
-> ^(NAMED_ARGUMENT[$id] expression)
| argument ;
qualifiedId : ID (DOT ID)* -> ^(QUALIFIED_IDENTIFIER ID*);
contextName : ID (DIV ID)* -> ^(QUALIFIED_IDENTIFIER ID*);
literal
: INTEGER_LITERAL
| STRING_LITERAL
| DQ_STRING_LITERAL
| boolLiteral
| NULL_LITERAL
| HEXADECIMAL_INTEGER_LITERAL
| REAL_LITERAL
;
boolLiteral: TRUE | FALSE;
INTEGER_LITERAL
: (DECIMAL_DIGIT)+ (INTEGER_TYPE_SUFFIX)?;
HEXADECIMAL_INTEGER_LITERAL : ('0x' | '0X') (HEX_DIGIT)+ (INTEGER_TYPE_SUFFIX)?;
relationalOperator
: EQUAL
| NOT_EQUAL
| LESS_THAN
| LESS_THAN_OR_EQUAL
| GREATER_THAN
| GREATER_THAN_OR_EQUAL
| INSTANCEOF
| BETWEEN
| MATCHES
;
ASSIGN: '=';
EQUAL: '==';
NOT_EQUAL: '!=';
LESS_THAN: '<';
LESS_THAN_OR_EQUAL: '<=';
GREATER_THAN: '>';
GREATER_THAN_OR_EQUAL: '>=';
INSTANCEOF: 'instanceof';
BETWEEN:'between';
MATCHES:'matches';
NULL_LITERAL: 'null';
SEMI: ';';
DOT: '.';
COMMA: ',';
LPAREN: '(';
RPAREN: ')';
LCURLY: '{';
RCURLY: '}';
LBRACKET: '[';
RBRACKET: ']';
PIPE: '|';
AND: 'and';
OR: 'or';
FALSE: 'false';
TRUE: 'true';
PLUS: '+';
MINUS: '-';
DIV: '/';
STAR: '*';
MOD: '%';
POWER: '^';
BANG: '!';
POUND: '#';
QMARK: '?';
DEFAULT: '??';
PROJECT: '![';
SELECT: '?[';
SELECT_FIRST: '^[';
SELECT_LAST: '$[';
TYPE: 'T(';
STRING_LITERAL: '\''! (APOS|~'\'')* '\''!;
DQ_STRING_LITERAL: '"'! (~'"')* '"'!;
ID: ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9'|DOT_ESCAPED)*;
DOT_ESCAPED: '\\.';
WS: ( ' ' | '\t' | '\n' |'\r')+ { $channel=HIDDEN; } ;
DOLLAR: '$';
AT: '@';
UPTO: '..';
COLON: ':';
REAL_LITERAL :
('.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) |
((DECIMAL_DIGIT)+ '.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) |
((DECIMAL_DIGIT)+ (EXPONENT_PART) (REAL_TYPE_SUFFIX)?) |
((DECIMAL_DIGIT)+ (REAL_TYPE_SUFFIX));
fragment APOS : '\''! '\'';
fragment DECIMAL_DIGIT : '0'..'9' ;
fragment INTEGER_TYPE_SUFFIX : ( 'L' | 'l' );
fragment HEX_DIGIT : '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f';
fragment EXPONENT_PART : 'e' (SIGN)* (DECIMAL_DIGIT)+ | 'E' (SIGN)* (DECIMAL_DIGIT)+ ;
fragment SIGN : '+' | '-' ;
fragment REAL_TYPE_SUFFIX : 'F' | 'f' | 'D' | 'd';

View File

@@ -0,0 +1,850 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.standard;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateAwareExpressionParser;
import org.springframework.expression.spel.InternalParseException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.Assign;
import org.springframework.expression.spel.ast.BeanReference;
import org.springframework.expression.spel.ast.BooleanLiteral;
import org.springframework.expression.spel.ast.CompoundExpression;
import org.springframework.expression.spel.ast.ConstructorReference;
import org.springframework.expression.spel.ast.Elvis;
import org.springframework.expression.spel.ast.FunctionReference;
import org.springframework.expression.spel.ast.Identifier;
import org.springframework.expression.spel.ast.Indexer;
import org.springframework.expression.spel.ast.InlineList;
import org.springframework.expression.spel.ast.Literal;
import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.expression.spel.ast.NullLiteral;
import org.springframework.expression.spel.ast.OpAnd;
import org.springframework.expression.spel.ast.OpDivide;
import org.springframework.expression.spel.ast.OpEQ;
import org.springframework.expression.spel.ast.OpGE;
import org.springframework.expression.spel.ast.OpGT;
import org.springframework.expression.spel.ast.OpLE;
import org.springframework.expression.spel.ast.OpLT;
import org.springframework.expression.spel.ast.OpMinus;
import org.springframework.expression.spel.ast.OpModulus;
import org.springframework.expression.spel.ast.OpMultiply;
import org.springframework.expression.spel.ast.OpNE;
import org.springframework.expression.spel.ast.OpOr;
import org.springframework.expression.spel.ast.OpPlus;
import org.springframework.expression.spel.ast.OperatorInstanceof;
import org.springframework.expression.spel.ast.OperatorMatches;
import org.springframework.expression.spel.ast.OperatorNot;
import org.springframework.expression.spel.ast.OperatorPower;
import org.springframework.expression.spel.ast.Projection;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.ast.QualifiedIdentifier;
import org.springframework.expression.spel.ast.Selection;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.ast.StringLiteral;
import org.springframework.expression.spel.ast.Ternary;
import org.springframework.expression.spel.ast.TypeReference;
import org.springframework.expression.spel.ast.VariableReference;
import org.springframework.util.Assert;
/**
* Hand written SpEL parser. Instances are reusable but are not thread safe.
*
* @author Andy Clement
* @since 3.0
*/
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// The expression being parsed
private String expressionString;
// The token stream constructed from that expression string
private List<Token> tokenStream;
// length of a populated token stream
private int tokenStreamLength;
// Current location in the token stream when processing tokens
private int tokenStreamPointer;
// For rules that build nodes, they are stacked here for return
private Stack<SpelNodeImpl> constructedNodes = new Stack<SpelNodeImpl>();
private SpelParserConfiguration configuration;
/**
* Create a parser with some configured behavior.
* @param configuration custom configuration options
*/
public InternalSpelExpressionParser(SpelParserConfiguration configuration) {
this.configuration = configuration;
}
@Override
protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
try {
this.expressionString = expressionString;
Tokenizer tokenizer = new Tokenizer(expressionString);
tokenizer.process();
tokenStream = tokenizer.getTokens();
tokenStreamLength = tokenStream.size();
tokenStreamPointer = 0;
constructedNodes.clear();
SpelNodeImpl ast = eatExpression();
if (moreTokens()) {
throw new SpelParseException(peekToken().startpos,SpelMessage.MORE_INPUT,toString(nextToken()));
}
Assert.isTrue(constructedNodes.isEmpty());
return new SpelExpression(expressionString, ast, configuration);
}
catch (InternalParseException ipe) {
throw ipe.getCause();
}
}
// expression
// : logicalOrExpression
// ( (ASSIGN^ logicalOrExpression)
// | (DEFAULT^ logicalOrExpression)
// | (QMARK^ expression COLON! expression)
// | (ELVIS^ expression))?;
private SpelNodeImpl eatExpression() {
SpelNodeImpl expr = eatLogicalOrExpression();
if (moreTokens()) {
Token t = peekToken();
if (t.kind==TokenKind.ASSIGN) { // a=b
if (expr==null) {
expr = new NullLiteral(toPos(t.startpos-1,t.endpos-1));
}
nextToken();
SpelNodeImpl assignedValue = eatLogicalOrExpression();
return new Assign(toPos(t),expr,assignedValue);
} else if (t.kind==TokenKind.ELVIS) { // a?:b (a if it isn't null, otherwise b)
if (expr==null) {
expr = new NullLiteral(toPos(t.startpos-1,t.endpos-2));
}
nextToken(); // elvis has left the building
SpelNodeImpl valueIfNull = eatExpression();
if (valueIfNull==null) {
valueIfNull = new NullLiteral(toPos(t.startpos+1,t.endpos+1));
}
return new Elvis(toPos(t),expr,valueIfNull);
} else if (t.kind==TokenKind.QMARK) { // a?b:c
if (expr==null) {
expr = new NullLiteral(toPos(t.startpos-1,t.endpos-1));
}
nextToken();
SpelNodeImpl ifTrueExprValue = eatExpression();
eatToken(TokenKind.COLON);
SpelNodeImpl ifFalseExprValue = eatExpression();
return new Ternary(toPos(t),expr,ifTrueExprValue,ifFalseExprValue);
}
}
return expr;
}
//logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*;
private SpelNodeImpl eatLogicalOrExpression() {
SpelNodeImpl expr = eatLogicalAndExpression();
while (peekIdentifierToken("or")) {
Token t = nextToken(); //consume OR
SpelNodeImpl rhExpr = eatLogicalAndExpression();
checkRightOperand(t,rhExpr);
expr = new OpOr(toPos(t),expr,rhExpr);
}
return expr;
}
// logicalAndExpression : relationalExpression (AND^ relationalExpression)*;
private SpelNodeImpl eatLogicalAndExpression() {
SpelNodeImpl expr = eatRelationalExpression();
while (peekIdentifierToken("and")) {
Token t = nextToken();// consume 'AND'
SpelNodeImpl rhExpr = eatRelationalExpression();
checkRightOperand(t,rhExpr);
expr = new OpAnd(toPos(t),expr,rhExpr);
}
return expr;
}
// relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
private SpelNodeImpl eatRelationalExpression() {
SpelNodeImpl expr = eatSumExpression();
Token relationalOperatorToken = maybeEatRelationalOperator();
if (relationalOperatorToken != null) {
Token t = nextToken(); //consume relational operator token
SpelNodeImpl rhExpr = eatSumExpression();
checkRightOperand(t,rhExpr);
TokenKind tk = relationalOperatorToken.kind;
if (relationalOperatorToken.isNumericRelationalOperator()) {
int pos = toPos(t);
if (tk==TokenKind.GT) {
return new OpGT(pos,expr,rhExpr);
} else if (tk==TokenKind.LT) {
return new OpLT(pos,expr,rhExpr);
} else if (tk==TokenKind.LE) {
return new OpLE(pos,expr,rhExpr);
} else if (tk==TokenKind.GE) {
return new OpGE(pos,expr,rhExpr);
} else if (tk == TokenKind.EQ) {
return new OpEQ(pos,expr,rhExpr);
} else {
Assert.isTrue(tk == TokenKind.NE);
return new OpNE(pos,expr,rhExpr);
}
}
if (tk==TokenKind.INSTANCEOF) {
return new OperatorInstanceof(toPos(t),expr,rhExpr);
} else if (tk==TokenKind.MATCHES) {
return new OperatorMatches(toPos(t),expr,rhExpr);
} else {
Assert.isTrue(tk==TokenKind.BETWEEN);
return new org.springframework.expression.spel.ast.OperatorBetween(toPos(t),expr,rhExpr);
}
}
return expr;
}
//sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
private SpelNodeImpl eatSumExpression() {
SpelNodeImpl expr = eatProductExpression();
while (peekToken(TokenKind.PLUS,TokenKind.MINUS)) {
Token t = nextToken();//consume PLUS or MINUS
SpelNodeImpl rhExpr = eatProductExpression();
checkRightOperand(t,rhExpr);
if (t.kind==TokenKind.PLUS) {
expr = new OpPlus(toPos(t),expr,rhExpr);
} else {
Assert.isTrue(t.kind==TokenKind.MINUS);
expr = new OpMinus(toPos(t),expr,rhExpr);
}
}
return expr;
}
// productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
private SpelNodeImpl eatProductExpression() {
SpelNodeImpl expr = eatPowerExpression();
while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) {
Token t = nextToken(); // consume STAR/DIV/MOD
SpelNodeImpl rhExpr = eatPowerExpression();
checkRightOperand(t,rhExpr);
if (t.kind==TokenKind.STAR) {
expr = new OpMultiply(toPos(t),expr,rhExpr);
} else if (t.kind==TokenKind.DIV) {
expr = new OpDivide(toPos(t),expr,rhExpr);
} else {
Assert.isTrue(t.kind==TokenKind.MOD);
expr = new OpModulus(toPos(t),expr,rhExpr);
}
}
return expr;
}
// powerExpr : unaryExpression (POWER^ unaryExpression)? ;
private SpelNodeImpl eatPowerExpression() {
SpelNodeImpl expr = eatUnaryExpression();
if (peekToken(TokenKind.POWER)) {
Token t = nextToken();//consume POWER
SpelNodeImpl rhExpr = eatUnaryExpression();
checkRightOperand(t,rhExpr);
return new OperatorPower(toPos(t),expr, rhExpr);
}
return expr;
}
// unaryExpression: (PLUS^ | MINUS^ | BANG^) unaryExpression | primaryExpression ;
private SpelNodeImpl eatUnaryExpression() {
if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) {
Token t = nextToken();
SpelNodeImpl expr = eatUnaryExpression();
if (t.kind==TokenKind.NOT) {
return new OperatorNot(toPos(t),expr);
} else if (t.kind==TokenKind.PLUS) {
return new OpPlus(toPos(t),expr);
} else {
Assert.isTrue(t.kind==TokenKind.MINUS);
return new OpMinus(toPos(t),expr);
}
} else {
return eatPrimaryExpression();
}
}
// primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?);
private SpelNodeImpl eatPrimaryExpression() {
List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
SpelNodeImpl start = eatStartNode(); // always a start node
nodes.add(start);
while (maybeEatNode()) {
nodes.add(pop());
}
if (nodes.size()==1) {
return nodes.get(0);
} else {
return new CompoundExpression(toPos(start.getStartPosition(),nodes.get(nodes.size()-1).getEndPosition()),nodes.toArray(new SpelNodeImpl[nodes.size()]));
}
}
// node : ((DOT dottedNode) | (SAFE_NAVI dottedNode) | nonDottedNode)+;
private boolean maybeEatNode() {
SpelNodeImpl expr = null;
if (peekToken(TokenKind.DOT,TokenKind.SAFE_NAVI)) {
expr = eatDottedNode();
} else {
expr = maybeEatNonDottedNode();
}
if (expr==null) {
return false;
} else {
push(expr);
return true;
}
}
// nonDottedNode: indexer;
private SpelNodeImpl maybeEatNonDottedNode() {
if (peekToken(TokenKind.LSQUARE)) {
if (maybeEatIndexer()) {
return pop();
}
}
return null;
}
//dottedNode
// : ((methodOrProperty
// | functionOrVar
// | projection
// | selection
// | firstSelection
// | lastSelection
// ))
// ;
private SpelNodeImpl eatDottedNode() {
Token t = nextToken();// it was a '.' or a '?.'
boolean nullSafeNavigation = t.kind==TokenKind.SAFE_NAVI;
if (maybeEatMethodOrProperty(nullSafeNavigation) || maybeEatFunctionOrVar() || maybeEatProjection(nullSafeNavigation) || maybeEatSelection(nullSafeNavigation)) {
return pop();
}
if (peekToken()==null) {
// unexpectedly ran out of data
raiseInternalException(t.startpos,SpelMessage.OOD);
} else {
raiseInternalException(t.startpos,SpelMessage.UNEXPECTED_DATA_AFTER_DOT,toString(peekToken()));
}
return null;
}
// functionOrVar
// : (POUND ID LPAREN) => function
// | var
//
// function : POUND id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs);
// var : POUND id=ID -> ^(VARIABLEREF[$id]);
private boolean maybeEatFunctionOrVar() {
if (!peekToken(TokenKind.HASH)) {
return false;
}
Token t = nextToken();
Token functionOrVariableName = eatToken(TokenKind.IDENTIFIER);
SpelNodeImpl[] args = maybeEatMethodArgs();
if (args==null) {
push(new VariableReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos)));
return true;
} else {
push(new FunctionReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos),args));
return true;
}
}
// methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!;
private SpelNodeImpl[] maybeEatMethodArgs() {
if (!peekToken(TokenKind.LPAREN)) {
return null;
}
List<SpelNodeImpl> args = new ArrayList<SpelNodeImpl>();
consumeArguments(args);
eatToken(TokenKind.RPAREN);
return args.toArray(new SpelNodeImpl[args.size()]);
}
private void eatConstructorArgs(List<SpelNodeImpl> accumulatedArguments) {
if (!peekToken(TokenKind.LPAREN)) {
throw new InternalParseException(new SpelParseException(expressionString,positionOf(peekToken()),SpelMessage.MISSING_CONSTRUCTOR_ARGS));
}
consumeArguments(accumulatedArguments);
eatToken(TokenKind.RPAREN);
}
/**
* Used for consuming arguments for either a method or a constructor call
*/
private void consumeArguments(List<SpelNodeImpl> accumulatedArguments) {
int pos = peekToken().startpos;
Token next = null;
do {
nextToken();// consume ( (first time through) or comma (subsequent times)
Token t = peekToken();
if (t==null) {
raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS);
}
if (t.kind!=TokenKind.RPAREN) {
accumulatedArguments.add(eatExpression());
}
next = peekToken();
} while (next!=null && next.kind==TokenKind.COMMA);
if (next==null) {
raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS);
}
}
private int positionOf(Token t) {
if (t==null) {
// if null assume the problem is because the right token was
// not found at the end of the expression
return expressionString.length();
} else {
return t.startpos;
}
}
//startNode
// : parenExpr | literal
// | type
// | methodOrProperty
// | functionOrVar
// | projection
// | selection
// | firstSelection
// | lastSelection
// | indexer
// | constructor
private SpelNodeImpl eatStartNode() {
if (maybeEatLiteral()) {
return pop();
} else if (maybeEatParenExpression()) {
return pop();
} else if (maybeEatTypeReference() || maybeEatNullReference() || maybeEatConstructorReference() || maybeEatMethodOrProperty(false) || maybeEatFunctionOrVar()) {
return pop();
} else if (maybeEatBeanReference()) {
return pop();
} else if (maybeEatProjection(false) || maybeEatSelection(false) || maybeEatIndexer()) {
return pop();
} else if (maybeEatInlineList()) {
return pop();
} else {
return null;
}
}
// parse: @beanname @'bean.name'
// quoted if dotted
private boolean maybeEatBeanReference() {
if (peekToken(TokenKind.BEAN_REF)) {
Token beanRefToken = nextToken();
Token beanNameToken = null;
String beanname = null;
if (peekToken(TokenKind.IDENTIFIER)) {
beanNameToken = eatToken(TokenKind.IDENTIFIER);
beanname = beanNameToken.data;
} else if (peekToken(TokenKind.LITERAL_STRING)) {
beanNameToken = eatToken(TokenKind.LITERAL_STRING);
beanname = beanNameToken.stringValue();
beanname = beanname.substring(1, beanname.length() - 1);
} else {
raiseInternalException(beanRefToken.startpos,SpelMessage.INVALID_BEAN_REFERENCE);
}
BeanReference beanReference = new BeanReference(toPos(beanNameToken),beanname);
constructedNodes.push(beanReference);
return true;
}
return false;
}
private boolean maybeEatTypeReference() {
if (peekToken(TokenKind.IDENTIFIER)) {
Token typeName = peekToken();
if (!typeName.stringValue().equals("T")) {
return false;
}
nextToken();
eatToken(TokenKind.LPAREN);
SpelNodeImpl node = eatPossiblyQualifiedId();
// dotted qualified id
eatToken(TokenKind.RPAREN);
constructedNodes.push(new TypeReference(toPos(typeName),node));
return true;
}
return false;
}
private boolean maybeEatNullReference() {
if (peekToken(TokenKind.IDENTIFIER)) {
Token nullToken = peekToken();
if (!nullToken.stringValue().equals("null")) {
return false;
}
nextToken();
constructedNodes.push(new NullLiteral(toPos(nullToken)));
return true;
}
return false;
}
//projection: PROJECT^ expression RCURLY!;
private boolean maybeEatProjection(boolean nullSafeNavigation) {
Token t = peekToken();
if (!peekToken(TokenKind.PROJECT,true)) {
return false;
}
SpelNodeImpl expr = eatExpression();
eatToken(TokenKind.RSQUARE);
constructedNodes.push(new Projection(nullSafeNavigation, toPos(t), expr));
return true;
}
// list = LCURLY (element (COMMA element)*) RCURLY
private boolean maybeEatInlineList() {
Token t = peekToken();
if (!peekToken(TokenKind.LCURLY,true)) {
return false;
}
SpelNodeImpl expr = null;
Token closingCurly = peekToken();
if (peekToken(TokenKind.RCURLY,true)) {
// empty list '[]'
expr = new InlineList(toPos(t.startpos,closingCurly.endpos));
} else {
List<SpelNodeImpl> listElements = new ArrayList<SpelNodeImpl>();
do {
listElements.add(eatExpression());
} while (peekToken(TokenKind.COMMA,true));
closingCurly = eatToken(TokenKind.RCURLY);
expr = new InlineList(toPos(t.startpos,closingCurly.endpos),listElements.toArray(new SpelNodeImpl[listElements.size()]));
}
constructedNodes.push(expr);
return true;
}
private boolean maybeEatIndexer() {
Token t = peekToken();
if (!peekToken(TokenKind.LSQUARE,true)) {
return false;
}
SpelNodeImpl expr = eatExpression();
eatToken(TokenKind.RSQUARE);
constructedNodes.push(new Indexer(toPos(t),expr));
return true;
}
private boolean maybeEatSelection(boolean nullSafeNavigation) {
Token t = peekToken();
if (!peekSelectToken()) {
return false;
}
nextToken();
SpelNodeImpl expr = eatExpression();
eatToken(TokenKind.RSQUARE);
if (t.kind==TokenKind.SELECT_FIRST) {
constructedNodes.push(new Selection(nullSafeNavigation,Selection.FIRST,toPos(t),expr));
} else if (t.kind==TokenKind.SELECT_LAST) {
constructedNodes.push(new Selection(nullSafeNavigation,Selection.LAST,toPos(t),expr));
} else {
constructedNodes.push(new Selection(nullSafeNavigation,Selection.ALL,toPos(t),expr));
}
return true;
}
/**
* Eat an identifier, possibly qualified (meaning that it is dotted).
* TODO AndyC Could create complete identifiers (a.b.c) here rather than a sequence of them? (a, b, c)
*/
private SpelNodeImpl eatPossiblyQualifiedId() {
List<SpelNodeImpl> qualifiedIdPieces = new ArrayList<SpelNodeImpl>();
Token startnode = eatToken(TokenKind.IDENTIFIER);
qualifiedIdPieces.add(new Identifier(startnode.stringValue(),toPos(startnode)));
while (peekToken(TokenKind.DOT,true)) {
Token node = eatToken(TokenKind.IDENTIFIER);
qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node)));
}
return new QualifiedIdentifier(toPos(startnode.startpos,qualifiedIdPieces.get(qualifiedIdPieces.size()-1).getEndPosition()),qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()]));
}
// This is complicated due to the support for dollars in identifiers. Dollars are normally separate tokens but
// there we want to combine a series of identifiers and dollars into a single identifier
private boolean maybeEatMethodOrProperty(boolean nullSafeNavigation) {
if (peekToken(TokenKind.IDENTIFIER)) {
Token methodOrPropertyName = nextToken();
SpelNodeImpl[] args = maybeEatMethodArgs();
if (args==null) {
// property
push(new PropertyOrFieldReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName)));
return true;
} else {
// methodreference
push(new MethodReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName),args));
// TODO what is the end position for a method reference? the name or the last arg?
return true;
}
}
return false;
}
//constructor
//: ('new' qualifiedId LPAREN) => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs)
private boolean maybeEatConstructorReference() {
if (peekIdentifierToken("new")) {
Token newToken = nextToken();
SpelNodeImpl possiblyQualifiedConstructorName = eatPossiblyQualifiedId();
List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
nodes.add(possiblyQualifiedConstructorName);
if (peekToken(TokenKind.LSQUARE)) {
// array initializer
List<SpelNodeImpl> dimensions = new ArrayList<SpelNodeImpl>();
while (peekToken(TokenKind.LSQUARE,true)) {
if (!peekToken(TokenKind.RSQUARE)) {
dimensions.add(eatExpression());
} else {
dimensions.add(null);
}
eatToken(TokenKind.RSQUARE);
}
if (maybeEatInlineList()) {
nodes.add(pop());
}
push(new ConstructorReference(toPos(newToken), dimensions.toArray(new SpelNodeImpl[dimensions.size()]),
nodes.toArray(new SpelNodeImpl[nodes.size()])));
} else {
// regular constructor invocation
eatConstructorArgs(nodes);
// TODO correct end position?
push(new ConstructorReference(toPos(newToken), nodes.toArray(new SpelNodeImpl[nodes.size()])));
}
return true;
}
return false;
}
private void push(SpelNodeImpl newNode) {
constructedNodes.push(newNode);
}
private SpelNodeImpl pop() {
return constructedNodes.pop();
}
// literal
// : INTEGER_LITERAL
// | boolLiteral
// | STRING_LITERAL
// | HEXADECIMAL_INTEGER_LITERAL
// | REAL_LITERAL
// | DQ_STRING_LITERAL
// | NULL_LITERAL
private boolean maybeEatLiteral() {
Token t = peekToken();
if (t==null) {
return false;
}
if (t.kind==TokenKind.LITERAL_INT) {
push(Literal.getIntLiteral(t.data, toPos(t), 10));
} else if (t.kind==TokenKind.LITERAL_LONG) {
push(Literal.getLongLiteral(t.data, toPos(t), 10));
} else if (t.kind==TokenKind.LITERAL_HEXINT) {
push(Literal.getIntLiteral(t.data, toPos(t), 16));
} else if (t.kind==TokenKind.LITERAL_HEXLONG) {
push(Literal.getLongLiteral(t.data, toPos(t), 16));
} else if (t.kind==TokenKind.LITERAL_REAL) {
push(Literal.getRealLiteral(t.data, toPos(t),false));
} else if (t.kind==TokenKind.LITERAL_REAL_FLOAT) {
push(Literal.getRealLiteral(t.data, toPos(t), true));
} else if (peekIdentifierToken("true")) {
push(new BooleanLiteral(t.data,toPos(t),true));
} else if (peekIdentifierToken("false")) {
push(new BooleanLiteral(t.data,toPos(t),false));
} else if (t.kind==TokenKind.LITERAL_STRING) {
push(new StringLiteral(t.data,toPos(t),t.data));
} else {
return false;
}
nextToken();
return true;
}
//parenExpr : LPAREN! expression RPAREN!;
private boolean maybeEatParenExpression() {
if (peekToken(TokenKind.LPAREN)) {
nextToken();
SpelNodeImpl expr = eatExpression();
eatToken(TokenKind.RPAREN);
push(expr);
return true;
} else {
return false;
}
}
// relationalOperator
// : EQUAL | NOT_EQUAL | LESS_THAN | LESS_THAN_OR_EQUAL | GREATER_THAN
// | GREATER_THAN_OR_EQUAL | INSTANCEOF | BETWEEN | MATCHES
private Token maybeEatRelationalOperator() {
Token t = peekToken();
if (t==null) {
return null;
}
if (t.isNumericRelationalOperator()) {
return t;
}
if (t.isIdentifier()) {
String idString = t.stringValue();
if (idString.equalsIgnoreCase("instanceof")) {
return t.asInstanceOfToken();
} else if (idString.equalsIgnoreCase("matches")) {
return t.asMatchesToken();
} else if (idString.equalsIgnoreCase("between")) {
return t.asBetweenToken();
}
}
return null;
}
private Token eatToken(TokenKind expectedKind) {
Token t = nextToken();
if (t==null) {
raiseInternalException( expressionString.length(), SpelMessage.OOD);
}
if (t.kind!=expectedKind) {
raiseInternalException(t.startpos,SpelMessage.NOT_EXPECTED_TOKEN, expectedKind.toString().toLowerCase(),t.getKind().toString().toLowerCase());
}
return t;
}
private boolean peekToken(TokenKind desiredTokenKind) {
return peekToken(desiredTokenKind,false);
}
private boolean peekToken(TokenKind desiredTokenKind, boolean consumeIfMatched) {
if (!moreTokens()) {
return false;
}
Token t = peekToken();
if (t.kind==desiredTokenKind) {
if (consumeIfMatched) {
tokenStreamPointer++;
}
return true;
} else {
if (desiredTokenKind == TokenKind.IDENTIFIER) {
// might be one of the textual forms of the operators (e.g. NE for != ) - in which case we can treat it as an identifier
// The list is represented here: Tokenizer.alternativeOperatorNames and those ones are in order in the TokenKind enum
if (t.kind.ordinal()>=TokenKind.DIV.ordinal() && t.kind.ordinal()<=TokenKind.NOT.ordinal() && t.data!=null) {
// if t.data were null, we'd know it wasn't the textual form, it was the symbol form
return true;
}
}
return false;
}
}
private boolean peekToken(TokenKind possible1,TokenKind possible2) {
if (!moreTokens()) return false;
Token t = peekToken();
return t.kind==possible1 || t.kind==possible2;
}
private boolean peekToken(TokenKind possible1,TokenKind possible2, TokenKind possible3) {
if (!moreTokens()) return false;
Token t = peekToken();
return t.kind==possible1 || t.kind==possible2 || t.kind==possible3;
}
private boolean peekIdentifierToken(String identifierString) {
if (!moreTokens()) {
return false;
}
Token t = peekToken();
return t.kind==TokenKind.IDENTIFIER && t.stringValue().equalsIgnoreCase(identifierString);
}
private boolean peekSelectToken() {
if (!moreTokens()) return false;
Token t = peekToken();
return t.kind==TokenKind.SELECT || t.kind==TokenKind.SELECT_FIRST || t.kind==TokenKind.SELECT_LAST;
}
private boolean moreTokens() {
return tokenStreamPointer<tokenStream.size();
}
private Token nextToken() {
if (tokenStreamPointer>=tokenStreamLength) {
return null;
}
return tokenStream.get(tokenStreamPointer++);
}
private Token peekToken() {
if (tokenStreamPointer>=tokenStreamLength) {
return null;
}
return tokenStream.get(tokenStreamPointer);
}
private void raiseInternalException(int pos, SpelMessage message,Object... inserts) {
throw new InternalParseException(new SpelParseException(expressionString,pos,message,inserts));
}
public String toString(Token t) {
if (t.getKind().hasPayload()) {
return t.stringValue();
} else {
return t.kind.toString().toLowerCase();
}
}
private void checkRightOperand(Token token, SpelNodeImpl operandExpression) {
if (operandExpression==null) {
raiseInternalException(token.startpos,SpelMessage.RIGHT_OPERAND_PROBLEM);
}
}
/**
* Compress the start and end of a token into a single int
*/
private int toPos(Token t) {
return (t.startpos<<16)+t.endpos;
}
private int toPos(int start,int end) {
return (start<<16)+end;
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.standard;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
/**
* A SpelExpressions represents a parsed (valid) expression that is ready to be evaluated in a specified context. An
* expression can be evaluated standalone or in a specified context. During expression evaluation the context may be
* asked to resolve references to types, beans, properties, methods.
*
* @author Andy Clement
* @since 3.0
*/
public class SpelExpression implements Expression {
private final String expression;
private final SpelNodeImpl ast;
private final SpelParserConfiguration configuration;
// the default context is used if no override is supplied by the user
private EvaluationContext defaultContext;
/**
* Construct an expression, only used by the parser.
*/
public SpelExpression(String expression, SpelNodeImpl ast, SpelParserConfiguration configuration) {
this.expression = expression;
this.ast = ast;
this.configuration = configuration;
}
// implementing Expression
public Object getValue() throws EvaluationException {
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), configuration);
return ast.getValue(expressionState);
}
public Object getValue(Object rootObject) throws EvaluationException {
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration);
return ast.getValue(expressionState);
}
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), configuration);
TypedValue typedResultValue = ast.getTypedValue(expressionState);
return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType);
}
public <T> T getValue(Object rootObject, Class<T> expectedResultType) throws EvaluationException {
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration);
TypedValue typedResultValue = ast.getTypedValue(expressionState);
return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType);
}
public Object getValue(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return ast.getValue(new ExpressionState(context, configuration));
}
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return ast.getValue(new ExpressionState(context, toTypedValue(rootObject), configuration));
}
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
TypedValue typedResultValue = ast.getTypedValue(new ExpressionState(context, configuration));
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
}
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> expectedResultType) throws EvaluationException {
TypedValue typedResultValue = ast.getTypedValue(new ExpressionState(context, toTypedValue(rootObject), configuration));
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
}
public Class getValueType() throws EvaluationException {
return getValueType(getEvaluationContext());
}
public Class getValueType(Object rootObject) throws EvaluationException {
return getValueType(getEvaluationContext(), rootObject);
}
public Class getValueType(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ExpressionState eState = new ExpressionState(context, configuration);
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor != null ? typeDescriptor.getType() : null;
}
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration);
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor != null ? typeDescriptor.getType() : null;
}
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
return getValueTypeDescriptor(getEvaluationContext());
}
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
ExpressionState eState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration);
return ast.getValueInternal(eState).getTypeDescriptor();
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ExpressionState eState = new ExpressionState(context, configuration);
return ast.getValueInternal(eState).getTypeDescriptor();
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration);
return ast.getValueInternal(eState).getTypeDescriptor();
}
public String getExpressionString() {
return expression;
}
public boolean isWritable(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return ast.isWritable(new ExpressionState(context, configuration));
}
public boolean isWritable(Object rootObject) throws EvaluationException {
return ast.isWritable(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration));
}
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), configuration));
}
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ast.setValue(new ExpressionState(context, configuration), value);
}
public void setValue(Object rootObject, Object value) throws EvaluationException {
ast.setValue(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), configuration), value);
}
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ast.setValue(new ExpressionState(context, toTypedValue(rootObject), configuration), value);
}
// impl only
/**
* @return return the Abstract Syntax Tree for the expression
*/
public SpelNode getAST() {
return ast;
}
/**
* Produce a string representation of the Abstract Syntax Tree for the expression, this should ideally look like the
* input expression, but properly formatted since any unnecessary whitespace will have been discarded during the
* parse of the expression.
* @return the string representation of the AST
*/
public String toStringAST() {
return ast.toStringAST();
}
/**
* Return the default evaluation context that will be used if none is supplied on an evaluation call
* @return the default evaluation context
*/
public EvaluationContext getEvaluationContext() {
if (defaultContext == null) {
defaultContext = new StandardEvaluationContext();
}
return defaultContext;
}
/**
* Set the evaluation context that will be used if none is specified on an evaluation call.
* @param context an evaluation context
*/
public void setEvaluationContext(EvaluationContext context) {
this.defaultContext = context;
}
private TypedValue toTypedValue(Object object) {
if (object == null) {
return TypedValue.NULL;
}
else {
return new TypedValue(object);
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.standard;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateAwareExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.util.Assert;
/**
* SpEL parser. Instances are reusable and thread-safe.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class SpelExpressionParser extends TemplateAwareExpressionParser {
private final SpelParserConfiguration configuration;
/**
* Create a parser with standard configuration.
*/
public SpelExpressionParser() {
this.configuration = new SpelParserConfiguration(false, false);
}
/**
* Create a parser with some configured behavior.
* @param configuration custom configuration options
*/
public SpelExpressionParser(SpelParserConfiguration configuration) {
Assert.notNull(configuration, "SpelParserConfiguration must not be null");
this.configuration = configuration;
}
@Override
protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context);
}
public SpelExpression parseRaw(String expressionString) throws ParseException {
return doParseExpression(expressionString, null);
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.standard;
/**
* Holder for a kind of token, the associated data and its position in the input data stream (start/end).
*
* @author Andy Clement
* @since 3.0
*/
class Token {
TokenKind kind;
String data;
int startpos; // index of first character
int endpos; // index of char after the last character
/**
* Constructor for use when there is no particular data for the token (eg. TRUE or '+')
* @param startpos the exact start
* @param endpos the index to the last character
*/
Token(TokenKind tokenKind, int startpos, int endpos) {
this.kind = tokenKind;
this.startpos = startpos;
this.endpos = endpos;
}
Token(TokenKind tokenKind, char[] tokenData, int pos, int endpos) {
this(tokenKind,pos,endpos);
this.data = new String(tokenData);
}
public TokenKind getKind() {
return kind;
}
public String toString() {
StringBuilder s = new StringBuilder();
s.append("[").append(kind.toString());
if (kind.hasPayload()) {
s.append(":").append(data);
}
s.append("]");
s.append("(").append(startpos).append(",").append(endpos).append(")");
return s.toString();
}
public boolean isIdentifier() {
return kind==TokenKind.IDENTIFIER;
}
public boolean isNumericRelationalOperator() {
return kind==TokenKind.GT || kind==TokenKind.GE || kind==TokenKind.LT || kind==TokenKind.LE || kind==TokenKind.EQ || kind==TokenKind.NE;
}
public String stringValue() {
return data;
}
public Token asInstanceOfToken() {
return new Token(TokenKind.INSTANCEOF,startpos,endpos);
}
public Token asMatchesToken() {
return new Token(TokenKind.MATCHES,startpos,endpos);
}
public Token asBetweenToken() {
return new Token(TokenKind.BETWEEN,startpos,endpos);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.standard;
/**
* @author Andy Clement
* @since 3.0
*/
enum TokenKind {
// ordered by priority - operands first
LITERAL_INT, LITERAL_LONG, LITERAL_HEXINT, LITERAL_HEXLONG, LITERAL_STRING, LITERAL_REAL, LITERAL_REAL_FLOAT,
LPAREN("("), RPAREN(")"), COMMA(","), IDENTIFIER,
COLON(":"),HASH("#"),RSQUARE("]"), LSQUARE("["),
LCURLY("{"),RCURLY("}"),
DOT("."), PLUS("+"), STAR("*"), MINUS("-"), SELECT_FIRST("^["), SELECT_LAST("$["), QMARK("?"), PROJECT("!["),
DIV("/"), GE(">="), GT(">"), LE("<="), LT("<"), EQ("=="), NE("!="),
MOD("%"), NOT("!"), ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"),
SELECT("?["), POWER("^"),
ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@")
;
char[] tokenChars;
private boolean hasPayload; // is there more to this token than simply the kind
private TokenKind(String tokenString) {
tokenChars = tokenString.toCharArray();
hasPayload = tokenChars.length==0;
}
private TokenKind() {
this("");
}
public String toString() {
return this.name()+(tokenChars.length!=0?"("+new String(tokenChars)+")":"");
}
public boolean hasPayload() {
return hasPayload;
}
public int getLength() {
return tokenChars.length;
}
}

View File

@@ -0,0 +1,507 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.standard;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.expression.spel.InternalParseException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.util.Assert;
/**
* Lex some input data into a stream of tokens that can then be parsed.
*
* @author Andy Clement
* @since 3.0
*/
class Tokenizer {
String expressionString;
char[] toProcess;
int pos;
int max;
List<Token> tokens = new ArrayList<Token>();
public Tokenizer(String inputdata) {
this.expressionString = inputdata;
this.toProcess = (inputdata+"\0").toCharArray();
this.max = toProcess.length;
this.pos = 0;
process();
}
public void process() {
while (pos<max) {
char ch = toProcess[pos];
if (isAlphabetic(ch)) {
lexIdentifier();
} else {
switch (ch) {
case '+':
pushCharToken(TokenKind.PLUS);
break;
case '_': // the other way to start an identifier
lexIdentifier();
break;
case '-':
pushCharToken(TokenKind.MINUS);
break;
case ':':
pushCharToken(TokenKind.COLON);
break;
case '.':
pushCharToken(TokenKind.DOT);
break;
case ',':
pushCharToken(TokenKind.COMMA);
break;
case '*':
pushCharToken(TokenKind.STAR);
break;
case '/':
pushCharToken(TokenKind.DIV);
break;
case '%':
pushCharToken(TokenKind.MOD);
break;
case '(':
pushCharToken(TokenKind.LPAREN);
break;
case ')':
pushCharToken(TokenKind.RPAREN);
break;
case '[':
pushCharToken(TokenKind.LSQUARE);
break;
case '#':
pushCharToken(TokenKind.HASH);
break;
case ']':
pushCharToken(TokenKind.RSQUARE);
break;
case '{':
pushCharToken(TokenKind.LCURLY);
break;
case '}':
pushCharToken(TokenKind.RCURLY);
break;
case '@':
pushCharToken(TokenKind.BEAN_REF);
break;
case '^':
if (isTwoCharToken(TokenKind.SELECT_FIRST)) {
pushPairToken(TokenKind.SELECT_FIRST);
} else {
pushCharToken(TokenKind.POWER);
}
break;
case '!':
if (isTwoCharToken(TokenKind.NE)) {
pushPairToken(TokenKind.NE);
} else if (isTwoCharToken(TokenKind.PROJECT)) {
pushPairToken(TokenKind.PROJECT);
} else {
pushCharToken(TokenKind.NOT);
}
break;
case '=':
if (isTwoCharToken(TokenKind.EQ)) {
pushPairToken(TokenKind.EQ);
} else {
pushCharToken(TokenKind.ASSIGN);
}
break;
case '?':
if (isTwoCharToken(TokenKind.SELECT)) {
pushPairToken(TokenKind.SELECT);
} else if (isTwoCharToken(TokenKind.ELVIS)) {
pushPairToken(TokenKind.ELVIS);
} else if (isTwoCharToken(TokenKind.SAFE_NAVI)) {
pushPairToken(TokenKind.SAFE_NAVI);
} else {
pushCharToken(TokenKind.QMARK);
}
break;
case '$':
if (isTwoCharToken(TokenKind.SELECT_LAST)) {
pushPairToken(TokenKind.SELECT_LAST);
} else {
lexIdentifier();
}
break;
case '>':
if (isTwoCharToken(TokenKind.GE)) {
pushPairToken(TokenKind.GE);
} else {
pushCharToken(TokenKind.GT);
}
break;
case '<':
if (isTwoCharToken(TokenKind.LE)) {
pushPairToken(TokenKind.LE);
} else {
pushCharToken(TokenKind.LT);
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
lexNumericLiteral(ch=='0');
break;
case ' ':
case '\t':
case '\r':
case '\n':
// drift over white space
pos++;
break;
case '\'':
lexQuotedStringLiteral();
break;
case '"':
lexDoubleQuotedStringLiteral();
break;
case 0:
// hit sentinel at end of value
pos++; // will take us to the end
break;
default:
throw new IllegalStateException("Cannot handle ("+Integer.valueOf(ch)+") '"+ch+"'");
}
}
}
}
public List<Token> getTokens() {
return tokens;
}
// STRING_LITERAL: '\''! (APOS|~'\'')* '\''!;
private void lexQuotedStringLiteral() {
int start = pos;
boolean terminated = false;
while (!terminated) {
pos++;
char ch = toProcess[pos];
if (ch=='\'') {
// may not be the end if the char after is also a '
if (toProcess[pos+1]=='\'') {
pos++; // skip over that too, and continue
} else {
terminated = true;
}
}
if (ch==0) {
throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NON_TERMINATING_QUOTED_STRING));
}
}
pos++;
tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start,pos), start, pos));
}
// DQ_STRING_LITERAL: '"'! (~'"')* '"'!;
private void lexDoubleQuotedStringLiteral() {
int start = pos;
boolean terminated = false;
while (!terminated) {
pos++;
char ch = toProcess[pos];
if (ch=='"') {
terminated = true;
}
if (ch==0) {
throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING));
}
}
pos++;
tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start,pos), start, pos));
}
// REAL_LITERAL :
// ('.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) |
// ((DECIMAL_DIGIT)+ '.' (DECIMAL_DIGIT)+ (EXPONENT_PART)? (REAL_TYPE_SUFFIX)?) |
// ((DECIMAL_DIGIT)+ (EXPONENT_PART) (REAL_TYPE_SUFFIX)?) |
// ((DECIMAL_DIGIT)+ (REAL_TYPE_SUFFIX));
// fragment INTEGER_TYPE_SUFFIX : ( 'L' | 'l' );
// fragment HEX_DIGIT : '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f';
//
// fragment EXPONENT_PART : 'e' (SIGN)* (DECIMAL_DIGIT)+ | 'E' (SIGN)* (DECIMAL_DIGIT)+ ;
// fragment SIGN : '+' | '-' ;
// fragment REAL_TYPE_SUFFIX : 'F' | 'f' | 'D' | 'd';
// INTEGER_LITERAL
// : (DECIMAL_DIGIT)+ (INTEGER_TYPE_SUFFIX)?;
private void lexNumericLiteral(boolean firstCharIsZero) {
boolean isReal = false;
int start = pos;
char ch = toProcess[pos+1];
boolean isHex = ch=='x' || ch=='X';
// deal with hexadecimal
if (firstCharIsZero && isHex) {
pos=pos+1;
do {
pos++;
} while (isHexadecimalDigit(toProcess[pos]));
if (isChar('L','l')) {
pushHexIntToken(subarray(start+2,pos),true, start, pos);
pos++;
} else {
pushHexIntToken(subarray(start+2,pos),false, start, pos);
}
return;
}
// real numbers must have leading digits
// Consume first part of number
do {
pos++;
} while (isDigit(toProcess[pos]));
// a '.' indicates this number is a real
ch = toProcess[pos];
if (ch=='.') {
isReal = true;
// carry on consuming digits
do {
pos++;
} while (isDigit(toProcess[pos]));
}
int endOfNumber = pos;
// Now there may or may not be an exponent
// is it a long ?
if (isChar('L','l')) {
if (isReal) { // 3.4L - not allowed
throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.REAL_CANNOT_BE_LONG));
}
pushIntToken(subarray(start, endOfNumber), true, start, endOfNumber);
pos++;
} else if (isExponentChar(toProcess[pos])) {
isReal = true; // if it wasnt before, it is now
pos++;
char possibleSign = toProcess[pos];
if (isSign(possibleSign)) {
pos++;
}
// exponent digits
do {
pos++;
} while (isDigit(toProcess[pos]));
boolean isFloat = false;
if (isFloatSuffix(toProcess[pos])) {
isFloat = true;
endOfNumber = ++pos;
} else if (isDoubleSuffix(toProcess[pos])) {
endOfNumber = ++pos;
}
pushRealToken(subarray(start,pos), isFloat, start, pos);
} else {
ch = toProcess[pos];
boolean isFloat = false;
if (isFloatSuffix(ch)) {
isReal = true;
isFloat = true;
endOfNumber = ++pos;
} else if (isDoubleSuffix(ch)) {
isReal = true;
endOfNumber = ++pos;
}
if (isReal) {
pushRealToken(subarray(start,endOfNumber), isFloat, start, endOfNumber);
} else {
pushIntToken(subarray(start,endOfNumber), false, start, endOfNumber);
}
}
}
// if this is changed, it must remain sorted
private static final String[] alternativeOperatorNames = { "DIV","EQ","GE","GT","LE","LT","MOD","NE","NOT"};
private void lexIdentifier() {
int start = pos;
do {
pos++;
} while (isIdentifier(toProcess[pos]));
char[] subarray = subarray(start,pos);
// Check if this is the alternative (textual) representation of an operator (see alternativeOperatorNames)
if ((pos-start)==2 || (pos-start)==3) {
String asString = new String(subarray).toUpperCase();
int idx = Arrays.binarySearch(alternativeOperatorNames,asString);
if (idx>=0) {
pushOneCharOrTwoCharToken(TokenKind.valueOf(asString),start,subarray);
return;
}
}
tokens.add(new Token(TokenKind.IDENTIFIER,subarray,start,pos));
}
private void pushIntToken(char[] data,boolean isLong, int start, int end) {
if (isLong) {
tokens.add(new Token(TokenKind.LITERAL_LONG,data, start, end));
} else {
tokens.add(new Token(TokenKind.LITERAL_INT,data, start, end));
}
}
private void pushHexIntToken(char[] data,boolean isLong, int start, int end) {
if (data.length==0) {
if (isLong) {
throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NOT_A_LONG,expressionString.substring(start,end+1)));
} else {
throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NOT_AN_INTEGER,expressionString.substring(start,end)));
}
}
if (isLong) {
tokens.add(new Token(TokenKind.LITERAL_HEXLONG, data, start, end));
} else {
tokens.add(new Token(TokenKind.LITERAL_HEXINT, data, start, end));
}
}
private void pushRealToken(char[] data, boolean isFloat, int start, int end) {
if (isFloat) {
tokens.add(new Token(TokenKind.LITERAL_REAL_FLOAT, data, start, end));
} else {
tokens.add(new Token(TokenKind.LITERAL_REAL, data, start, end));
}
}
private char[] subarray(int start, int end) {
char[] result = new char[end - start];
System.arraycopy(toProcess, start, result, 0, end - start);
return result;
}
/**
* Check if this might be a two character token.
*/
private boolean isTwoCharToken(TokenKind kind) {
Assert.isTrue(kind.tokenChars.length == 2);
Assert.isTrue(toProcess[pos] == kind.tokenChars[0]);
return toProcess[pos+1] == kind.tokenChars[1];
}
/**
* Push a token of just one character in length.
*/
private void pushCharToken(TokenKind kind) {
tokens.add(new Token(kind,pos,pos+1));
pos++;
}
/**
* Push a token of two characters in length.
*/
private void pushPairToken(TokenKind kind) {
tokens.add(new Token(kind,pos,pos+2));
pos+=2;
}
private void pushOneCharOrTwoCharToken(TokenKind kind, int pos, char[] data) {
tokens.add(new Token(kind,data,pos,pos+kind.getLength()));
}
// ID: ('a'..'z'|'A'..'Z'|'_'|'$') ('a'..'z'|'A'..'Z'|'_'|'$'|'0'..'9'|DOT_ESCAPED)*;
private boolean isIdentifier(char ch) {
return isAlphabetic(ch) || isDigit(ch) || ch=='_' || ch=='$';
}
private boolean isChar(char a,char b) {
char ch = toProcess[pos];
return ch==a || ch==b;
}
private boolean isExponentChar(char ch) {
return ch=='e' || ch=='E';
}
private boolean isFloatSuffix(char ch) {
return ch=='f' || ch=='F';
}
private boolean isDoubleSuffix(char ch) {
return ch=='d' || ch=='D';
}
private boolean isSign(char ch) {
return ch=='+' || ch=='-';
}
private boolean isDigit(char ch) {
if (ch>255) {
return false;
}
return (flags[ch] & IS_DIGIT)!=0;
}
private boolean isAlphabetic(char ch) {
if (ch>255) {
return false;
}
return (flags[ch] & IS_ALPHA)!=0;
}
private boolean isHexadecimalDigit(char ch) {
if (ch>255) {
return false;
}
return (flags[ch] & IS_HEXDIGIT)!=0;
}
private static final byte flags[] = new byte[256];
private static final byte IS_DIGIT=0x01;
private static final byte IS_HEXDIGIT=0x02;
private static final byte IS_ALPHA=0x04;
static {
for (int ch='0';ch<='9';ch++) {
flags[ch]|=IS_DIGIT | IS_HEXDIGIT;
}
for (int ch='A';ch<='F';ch++) {
flags[ch]|= IS_HEXDIGIT;
}
for (int ch='a';ch<='f';ch++) {
flags[ch]|= IS_HEXDIGIT;
}
for (int ch='A';ch<='Z';ch++) {
flags[ch]|= IS_ALPHA;
}
for (int ch='a';ch<='z';ch++) {
flags[ch]|= IS_ALPHA;
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.support;
import org.springframework.expression.TypedValue;
/**
* @author Andy Clement
* @since 3.0
*/
public class BooleanTypedValue extends TypedValue {
public static final BooleanTypedValue TRUE = new BooleanTypedValue(true);
public static final BooleanTypedValue FALSE = new BooleanTypedValue(false);
private BooleanTypedValue(boolean b) {
super(b);
}
public static BooleanTypedValue forValue(boolean b) {
if (b) {
return TRUE;
}
else {
return FALSE;
}
}
}

View File

@@ -0,0 +1,507 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression.spel.support;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
/**
* Utility methods used by the reflection resolver code to discover the appropriate
* methods/constructors and fields that should be used in expressions.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class ReflectionHelper {
/**
* Compare argument arrays and return information about whether they match. A supplied type converter
* and conversionAllowed flag allow for matches to take into account that a type may be transformed
* into a different type by the converter.
* @param expectedArgTypes the array of types the method/constructor is expecting
* @param suppliedArgTypes the array of types that are being supplied at the point of invocation
* @param typeConverter a registered type converter
* @return a MatchInfo object indicating what kind of match it was or null if it was not a match
*/
static ArgumentsMatchInfo compareArguments(
List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) {
Assert.isTrue(expectedArgTypes.size() == suppliedArgTypes.size(),
"Expected argument types and supplied argument types should be arrays of same length");
ArgsMatchKind match = ArgsMatchKind.EXACT;
List<Integer> argsRequiringConversion = null;
for (int i = 0; i < expectedArgTypes.size() && match != null; i++) {
TypeDescriptor suppliedArg = suppliedArgTypes.get(i);
TypeDescriptor expectedArg = expectedArgTypes.get(i);
if (!expectedArg.equals(suppliedArg)) {
// The user may supply null - and that will be ok unless a primitive is expected
if (suppliedArg == null) {
if (expectedArg.isPrimitive()) {
match = null;
}
}
else {
if (suppliedArg.isAssignableTo(expectedArg)) {
if (match != ArgsMatchKind.REQUIRES_CONVERSION) {
match = ArgsMatchKind.CLOSE;
}
}
else if (typeConverter.canConvert(suppliedArg, expectedArg)) {
if (argsRequiringConversion == null) {
argsRequiringConversion = new ArrayList<Integer>();
}
argsRequiringConversion.add(i);
match = ArgsMatchKind.REQUIRES_CONVERSION;
}
else {
match = null;
}
}
}
}
if (match == null) {
return null;
}
else {
if (match == ArgsMatchKind.REQUIRES_CONVERSION) {
int[] argsArray = new int[argsRequiringConversion.size()];
for (int i = 0; i < argsRequiringConversion.size(); i++) {
argsArray[i] = argsRequiringConversion.get(i);
}
return new ArgumentsMatchInfo(match, argsArray);
}
else {
return new ArgumentsMatchInfo(match);
}
}
}
/**
* Based on {@link MethodInvoker#getTypeDifferenceWeight(Class[], Object[])} but operates on TypeDescriptors.
*/
public static int getTypeDifferenceWeight(List<TypeDescriptor> paramTypes, List<TypeDescriptor> argTypes) {
int result = 0;
for (int i = 0,max=paramTypes.size(); i < max; i++) {
TypeDescriptor argType = argTypes.get(i);
TypeDescriptor paramType = paramTypes.get(i);
if (argType == null) {
if (paramType.isPrimitive()) {
return Integer.MAX_VALUE;
}
}
if (!ClassUtils.isAssignable(paramType.getClass(), argType.getClass())) {
return Integer.MAX_VALUE;
}
if (argType != null) {
Class paramTypeClazz = paramType.getType();
if (paramTypeClazz.isPrimitive()) {
paramTypeClazz = Object.class;
}
Class superClass = argType.getClass().getSuperclass();
while (superClass != null) {
if (paramType.equals(superClass)) {
result = result + 2;
superClass = null;
}
else if (ClassUtils.isAssignable(paramTypeClazz, superClass)) {
result = result + 2;
superClass = superClass.getSuperclass();
}
else {
superClass = null;
}
}
if (paramTypeClazz.isInterface()) {
result = result + 1;
}
}
}
return result;
}
/**
* Compare argument arrays and return information about whether they match. A supplied type converter and
* conversionAllowed flag allow for matches to take into account that a type may be transformed into a different
* type by the converter. This variant of compareArguments also allows for a varargs match.
* @param expectedArgTypes the array of types the method/constructor is expecting
* @param suppliedArgTypes the array of types that are being supplied at the point of invocation
* @param typeConverter a registered type converter
* @return a MatchInfo object indicating what kind of match it was or null if it was not a match
*/
static ArgumentsMatchInfo compareArgumentsVarargs(
List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) {
Assert.isTrue(expectedArgTypes != null && expectedArgTypes.size() > 0,
"Expected arguments must at least include one array (the vargargs parameter)");
Assert.isTrue(expectedArgTypes.get(expectedArgTypes.size() - 1).isArray(),
"Final expected argument should be array type (the varargs parameter)");
ArgsMatchKind match = ArgsMatchKind.EXACT;
List<Integer> argsRequiringConversion = null;
// Check up until the varargs argument:
// Deal with the arguments up to 'expected number' - 1 (that is everything but the varargs argument)
int argCountUpToVarargs = expectedArgTypes.size() - 1;
for (int i = 0; i < argCountUpToVarargs && match != null; i++) {
TypeDescriptor suppliedArg = suppliedArgTypes.get(i);
TypeDescriptor expectedArg = expectedArgTypes.get(i);
if (suppliedArg == null) {
if (expectedArg.isPrimitive()) {
match = null;
}
}
else {
if (!expectedArg.equals(suppliedArg)) {
if (suppliedArg.isAssignableTo(expectedArg)) {
if (match != ArgsMatchKind.REQUIRES_CONVERSION) {
match = ArgsMatchKind.CLOSE;
}
}
else if (typeConverter.canConvert(suppliedArg, expectedArg)) {
if (argsRequiringConversion == null) {
argsRequiringConversion = new ArrayList<Integer>();
}
argsRequiringConversion.add(i);
match = ArgsMatchKind.REQUIRES_CONVERSION;
}
else {
match = null;
}
}
}
}
// If already confirmed it cannot be a match, then return
if (match == null) {
return null;
}
if (suppliedArgTypes.size() == expectedArgTypes.size() &&
expectedArgTypes.get(expectedArgTypes.size() - 1).equals(
suppliedArgTypes.get(suppliedArgTypes.size() - 1))) {
// Special case: there is one parameter left and it is an array and it matches the varargs
// expected argument - that is a match, the caller has already built the array. Proceed with it.
}
else {
// Now... we have the final argument in the method we are checking as a match and we have 0 or more other
// arguments left to pass to it.
Class varargsParameterType = expectedArgTypes.get(expectedArgTypes.size() - 1).getElementTypeDescriptor().getType();
// All remaining parameters must be of this type or convertable to this type
for (int i = expectedArgTypes.size() - 1; i < suppliedArgTypes.size(); i++) {
TypeDescriptor suppliedArg = suppliedArgTypes.get(i);
if (suppliedArg == null) {
if (varargsParameterType.isPrimitive()) {
match = null;
}
} else {
if (varargsParameterType != suppliedArg.getType()) {
if (ClassUtils.isAssignable(varargsParameterType, suppliedArg.getType())) {
if (match != ArgsMatchKind.REQUIRES_CONVERSION) {
match = ArgsMatchKind.CLOSE;
}
}
else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsParameterType))) {
if (argsRequiringConversion == null) {
argsRequiringConversion = new ArrayList<Integer>();
}
argsRequiringConversion.add(i);
match = ArgsMatchKind.REQUIRES_CONVERSION;
}
else {
match = null;
}
}
}
}
}
if (match == null) {
return null;
}
else {
if (match == ArgsMatchKind.REQUIRES_CONVERSION) {
int[] argsArray = new int[argsRequiringConversion.size()];
for (int i = 0; i < argsRequiringConversion.size(); i++) {
argsArray[i] = argsRequiringConversion.get(i);
}
return new ArgumentsMatchInfo(match, argsArray);
}
else {
return new ArgumentsMatchInfo(match);
}
}
}
/**
* Takes an input set of argument values and, following the positions specified in the int array,
* it converts them to the types specified as the required parameter types. The arguments are
* converted 'in-place' in the input array.
* @param converter the type converter to use for attempting conversions
* @param arguments the actual arguments that need conversion
* @param methodOrCtor the target Method or Constructor
* @param argumentsRequiringConversion details which of the input arguments for sure need conversion
* @param varargsPosition the known position of the varargs argument, if any
* @throws EvaluationException if a problem occurs during conversion
*/
static void convertArguments(TypeConverter converter, Object[] arguments, Object methodOrCtor,
int[] argumentsRequiringConversion, Integer varargsPosition) throws EvaluationException {
if (varargsPosition == null) {
for (int i = 0; i < arguments.length; i++) {
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i));
Object argument = arguments[i];
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
}
} else {
for (int i = 0; i < varargsPosition; i++) {
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i));
Object argument = arguments[i];
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
}
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition);
if (varargsPosition == arguments.length - 1) {
TypeDescriptor targetType = new TypeDescriptor(methodParam);
Object argument = arguments[varargsPosition];
arguments[varargsPosition] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
} else {
TypeDescriptor targetType = TypeDescriptor.nested(methodParam, 1);
for (int i = varargsPosition; i < arguments.length; i++) {
Object argument = arguments[i];
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
}
}
}
}
/**
* Convert a supplied set of arguments into the requested types. If the parameterTypes are related to
* a varargs method then the final entry in the parameterTypes array is going to be an array itself whose
* component type should be used as the conversion target for extraneous arguments. (For example, if the
* parameterTypes are {Integer, String[]} and the input arguments are {Integer, boolean, float} then both
* the boolean and float must be converted to strings). This method does not repackage the arguments
* into a form suitable for the varargs invocation
* @param converter the converter to use for type conversions
* @param arguments the arguments to convert to the requested parameter types
* @param method the target Method
* @throws SpelEvaluationException if there is a problem with conversion
*/
public static void convertAllArguments(TypeConverter converter, Object[] arguments, Method method) throws SpelEvaluationException {
Integer varargsPosition = null;
if (method.isVarArgs()) {
Class[] paramTypes = method.getParameterTypes();
varargsPosition = paramTypes.length - 1;
}
for (int argPosition = 0; argPosition < arguments.length; argPosition++) {
TypeDescriptor targetType;
if (varargsPosition != null && argPosition >= varargsPosition) {
MethodParameter methodParam = new MethodParameter(method, varargsPosition);
targetType = TypeDescriptor.nested(methodParam, 1);
}
else {
targetType = new TypeDescriptor(new MethodParameter(method, argPosition));
}
try {
Object argument = arguments[argPosition];
if (argument != null && !targetType.getObjectType().isInstance(argument)) {
if (converter == null) {
throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, argument.getClass().getName(), targetType);
}
arguments[argPosition] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
}
}
catch (EvaluationException ex) {
// allows for another type converter throwing a different kind of EvaluationException
if (ex instanceof SpelEvaluationException) {
throw (SpelEvaluationException)ex;
}
else {
throw new SpelEvaluationException(ex, SpelMessage.TYPE_CONVERSION_ERROR,arguments[argPosition].getClass().getName(), targetType);
}
}
}
}
/**
* Package up the arguments so that they correctly match what is expected in parameterTypes. For example, if
* parameterTypes is (int, String[]) because the second parameter was declared String... then if arguments is
* [1,"a","b"] then it must be repackaged as [1,new String[]{"a","b"}] in order to match the expected
* parameterTypes.
* @param requiredParameterTypes the types of the parameters for the invocation
* @param args the arguments to be setup ready for the invocation
* @return a repackaged array of arguments where any varargs setup has been done
*/
public static Object[] setupArgumentsForVarargsInvocation(Class[] requiredParameterTypes, Object... args) {
// Check if array already built for final argument
int parameterCount = requiredParameterTypes.length;
int argumentCount = args.length;
// Check if repackaging is needed:
if (parameterCount != args.length ||
requiredParameterTypes[parameterCount - 1] !=
(args[argumentCount - 1] == null ? null : args[argumentCount - 1].getClass())) {
int arraySize = 0; // zero size array if nothing to pass as the varargs parameter
if (argumentCount >= parameterCount) {
arraySize = argumentCount - (parameterCount - 1);
}
// Create an array for the varargs arguments
Object[] newArgs = new Object[parameterCount];
for (int i = 0; i < newArgs.length - 1; i++) {
newArgs[i] = args[i];
}
// Now sort out the final argument, which is the varargs one. Before entering this
// method the arguments should have been converted to the box form of the required type.
Class<?> componentType = requiredParameterTypes[parameterCount-1].getComponentType();
if (componentType.isPrimitive()) {
if (componentType==Integer.TYPE) {
int[] repackagedArguments = (int[]) Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = ((Integer)args[parameterCount + i - 1]).intValue();
}
newArgs[newArgs.length - 1] = repackagedArguments;
} else if(componentType==Float.TYPE) {
float[] repackagedArguments = (float[]) Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = ((Float)args[parameterCount + i - 1]).floatValue();
}
newArgs[newArgs.length - 1] = repackagedArguments;
} else if(componentType==Double.TYPE) {
double[] repackagedArguments = (double[]) Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = ((Double)args[parameterCount + i - 1]).doubleValue();
}
newArgs[newArgs.length - 1] = repackagedArguments;
} else if(componentType==Short.TYPE) {
short[] repackagedArguments = (short[]) Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = ((Short)args[parameterCount + i - 1]).shortValue();
}
newArgs[newArgs.length - 1] = repackagedArguments;
} else if(componentType==Character.TYPE) {
char[] repackagedArguments = (char[]) Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = ((Character)args[parameterCount + i - 1]).charValue();
}
newArgs[newArgs.length - 1] = repackagedArguments;
} else if(componentType==Byte.TYPE) {
byte[] repackagedArguments = (byte[]) Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = ((Byte)args[parameterCount + i - 1]).byteValue();
}
newArgs[newArgs.length - 1] = repackagedArguments;
} else if(componentType==Boolean.TYPE) {
boolean[] repackagedArguments = (boolean[]) Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = ((Boolean)args[parameterCount + i - 1]).booleanValue();
}
newArgs[newArgs.length - 1] = repackagedArguments;
} else if(componentType==Long.TYPE) {
long[] repackagedArguments = (long[]) Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = ((Long)args[parameterCount + i - 1]).longValue();
}
newArgs[newArgs.length - 1] = repackagedArguments;
}
} else {
Object[] repackagedArguments = (Object[]) Array.newInstance(componentType, arraySize);
// Copy all but the varargs arguments
for (int i = 0; i < arraySize; i++) {
repackagedArguments[i] = args[parameterCount + i - 1];
}
newArgs[newArgs.length - 1] = repackagedArguments;
}
return newArgs;
}
return args;
}
public static enum ArgsMatchKind {
// An exact match is where the parameter types exactly match what the method/constructor being invoked is expecting
EXACT,
// A close match is where the parameter types either exactly match or are assignment compatible with the method/constructor being invoked
CLOSE,
// A conversion match is where the type converter must be used to transform some of the parameter types
REQUIRES_CONVERSION
}
/**
* An instance of ArgumentsMatchInfo describes what kind of match was achieved between two sets of arguments - the set that a
* method/constructor is expecting and the set that are being supplied at the point of invocation. If the kind
* indicates that conversion is required for some of the arguments then the arguments that require conversion are
* listed in the argsRequiringConversion array.
*/
public static class ArgumentsMatchInfo {
public final ArgsMatchKind kind;
public int[] argsRequiringConversion;
ArgumentsMatchInfo(ArgsMatchKind kind, int[] integers) {
this.kind = kind;
this.argsRequiringConversion = integers;
}
ArgumentsMatchInfo(ArgsMatchKind kind) {
this.kind = kind;
}
public boolean isExactMatch() {
return (this.kind == ArgsMatchKind.EXACT);
}
public boolean isCloseMatch() {
return (this.kind == ArgsMatchKind.CLOSE);
}
public boolean isMatchRequiringConversion() {
return (this.kind == ArgsMatchKind.REQUIRES_CONVERSION);
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ArgumentMatch: ").append(this.kind);
if (this.argsRequiringConversion != null) {
sb.append(" (argsForConversion:");
for (int i = 0; i < this.argsRequiringConversion.length;i++) {
if (i > 0) {
sb.append(",");
}
sb.append(this.argsRequiringConversion[i]);
}
sb.append(")");
}
return sb.toString();
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.support;
import java.lang.reflect.Constructor;
import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue;
import org.springframework.util.ReflectionUtils;
/**
* A simple ConstructorExecutor implementation that runs a constructor using reflective invocation.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
class ReflectiveConstructorExecutor implements ConstructorExecutor {
private final Constructor<?> ctor;
private final Integer varargsPosition;
// When the constructor was found, we will have determined if arguments need to be converted for it
// to be invoked. Conversion won't be cheap so let's only do it if necessary.
private final int[] argsRequiringConversion;
public ReflectiveConstructorExecutor(Constructor<?> ctor, int[] argsRequiringConversion) {
this.ctor = ctor;
if (ctor.isVarArgs()) {
Class[] paramTypes = ctor.getParameterTypes();
this.varargsPosition = paramTypes.length - 1;
}
else {
this.varargsPosition = null;
}
this.argsRequiringConversion = argsRequiringConversion;
}
public TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException {
try {
if (arguments != null) {
ReflectionHelper.convertArguments(context.getTypeConverter(), arguments,
this.ctor, this.argsRequiringConversion, this.varargsPosition);
}
if (this.ctor.isVarArgs()) {
arguments = ReflectionHelper.setupArgumentsForVarargsInvocation(this.ctor.getParameterTypes(), arguments);
}
ReflectionUtils.makeAccessible(this.ctor);
return new TypedValue(this.ctor.newInstance(arguments));
}
catch (Exception ex) {
throw new AccessException("Problem invoking constructor: " + this.ctor, ex);
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.support;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
/**
* A constructor resolver that uses reflection to locate the constructor that should be invoked
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class ReflectiveConstructorResolver implements ConstructorResolver {
/**
* Locate a constructor on the type. There are three kinds of match that might occur:
* <ol>
* <li>An exact match where the types of the arguments match the types of the constructor
* <li>An in-exact match where the types we are looking for are subtypes of those defined on the constructor
* <li>A match where we are able to convert the arguments into those expected by the constructor, according to the
* registered type converter.
* </ol>
*/
public ConstructorExecutor resolve(EvaluationContext context, String typename, List<TypeDescriptor> argumentTypes)
throws AccessException {
try {
TypeConverter typeConverter = context.getTypeConverter();
Class<?> type = context.getTypeLocator().findType(typename);
Constructor[] ctors = type.getConstructors();
Arrays.sort(ctors, new Comparator<Constructor>() {
public int compare(Constructor c1, Constructor c2) {
int c1pl = c1.getParameterTypes().length;
int c2pl = c2.getParameterTypes().length;
return (new Integer(c1pl)).compareTo(c2pl);
}
});
Constructor closeMatch = null;
int[] argsToConvert = null;
Constructor matchRequiringConversion = null;
for (Constructor ctor : ctors) {
Class[] paramTypes = ctor.getParameterTypes();
List<TypeDescriptor> paramDescriptors = new ArrayList<TypeDescriptor>(paramTypes.length);
for (int i = 0; i < paramTypes.length; i++) {
paramDescriptors.add(new TypeDescriptor(new MethodParameter(ctor, i)));
}
ReflectionHelper.ArgumentsMatchInfo matchInfo = null;
if (ctor.isVarArgs() && argumentTypes.size() >= (paramTypes.length - 1)) {
// *sigh* complicated
// Basically.. we have to have all parameters match up until the varargs one, then the rest of what is
// being provided should be
// the same type whilst the final argument to the method must be an array of that (oh, how easy...not) -
// or the final parameter
// we are supplied does match exactly (it is an array already).
matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter);
}
else if (paramTypes.length == argumentTypes.size()) {
// worth a closer look
matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter);
}
if (matchInfo != null) {
if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.EXACT) {
return new ReflectiveConstructorExecutor(ctor, null);
}
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) {
closeMatch = ctor;
}
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) {
argsToConvert = matchInfo.argsRequiringConversion;
matchRequiringConversion = ctor;
}
}
}
if (closeMatch != null) {
return new ReflectiveConstructorExecutor(closeMatch, null);
}
else if (matchRequiringConversion != null) {
return new ReflectiveConstructorExecutor(matchRequiringConversion, argsToConvert);
}
else {
return null;
}
}
catch (EvaluationException ex) {
throw new AccessException("Failed to resolve constructor", ex);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.support;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.TypedValue;
import org.springframework.util.ReflectionUtils;
/**
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
class ReflectiveMethodExecutor implements MethodExecutor {
private final Method method;
private final Integer varargsPosition;
// When the method was found, we will have determined if arguments need to be converted for it
// to be invoked. Conversion won't be cheap so let's only do it if necessary.
private final int[] argsRequiringConversion;
public ReflectiveMethodExecutor(Method theMethod, int[] argumentsRequiringConversion) {
this.method = theMethod;
if (theMethod.isVarArgs()) {
Class[] paramTypes = theMethod.getParameterTypes();
this.varargsPosition = paramTypes.length - 1;
}
else {
this.varargsPosition = null;
}
this.argsRequiringConversion = argumentsRequiringConversion;
}
public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException {
try {
if (arguments != null) {
ReflectionHelper.convertArguments(
context.getTypeConverter(), arguments, this.method,
this.argsRequiringConversion, this.varargsPosition);
}
if (this.method.isVarArgs()) {
arguments = ReflectionHelper.setupArgumentsForVarargsInvocation(this.method.getParameterTypes(), arguments);
}
ReflectionUtils.makeAccessible(this.method);
Object value = this.method.invoke(target, arguments);
return new TypedValue(value, new TypeDescriptor(new MethodParameter(this.method, -1)).narrow(value));
}
catch (Exception ex) {
throw new AccessException("Problem invoking method: " + this.method, ex);
}
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.expression.spel.support;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodFilter;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.CollectionUtils;
/**
* A method resolver that uses reflection to locate the method that should be invoked.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class ReflectiveMethodResolver implements MethodResolver {
private static Method[] NO_METHODS = new Method[0];
private Map<Class<?>, MethodFilter> filters = null;
// Using distance will ensure a more accurate match is discovered,
// more closely following the Java rules.
private boolean useDistance = false;
public ReflectiveMethodResolver() {
}
/**
* This constructors allows the ReflectiveMethodResolver to be configured such that it will
* use a distance computation to check which is the better of two close matches (when there
* are multiple matches). Using the distance computation is intended to ensure matches
* are more closely representative of what a Java compiler would do when taking into
* account boxing/unboxing and whether the method candidates are declared to handle a
* supertype of the type (of the argument) being passed in.
* @param useDistance true if distance computation should be used when calculating matches
*/
public ReflectiveMethodResolver(boolean useDistance) {
this.useDistance = useDistance;
}
/**
* Locate a method on a type. There are three kinds of match that might occur:
* <ol>
* <li>An exact match where the types of the arguments match the types of the constructor
* <li>An in-exact match where the types we are looking for are subtypes of those defined on the constructor
* <li>A match where we are able to convert the arguments into those expected by the constructor,
* according to the registered type converter.
* </ol>
*/
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
List<TypeDescriptor> argumentTypes) throws AccessException {
try {
TypeConverter typeConverter = context.getTypeConverter();
Class<?> type = (targetObject instanceof Class ? (Class<?>) targetObject : targetObject.getClass());
Method[] methods = type.getMethods();
// If a filter is registered for this type, call it
MethodFilter filter = (this.filters != null ? this.filters.get(type) : null);
if (filter != null) {
List<Method> methodsForFiltering = new ArrayList<Method>();
for (Method method: methods) {
methodsForFiltering.add(method);
}
List<Method> methodsFiltered = filter.filter(methodsForFiltering);
if (CollectionUtils.isEmpty(methodsFiltered)) {
methods = NO_METHODS;
}
else {
methods = methodsFiltered.toArray(new Method[methodsFiltered.size()]);
}
}
Arrays.sort(methods, new Comparator<Method>() {
public int compare(Method m1, Method m2) {
int m1pl = m1.getParameterTypes().length;
int m2pl = m2.getParameterTypes().length;
return (new Integer(m1pl)).compareTo(m2pl);
}
});
Method closeMatch = null;
int closeMatchDistance = Integer.MAX_VALUE;
int[] argsToConvert = null;
Method matchRequiringConversion = null;
boolean multipleOptions = false;
for (Method method : methods) {
if (method.isBridge()) {
continue;
}
if (method.getName().equals(name)) {
Class[] paramTypes = method.getParameterTypes();
List<TypeDescriptor> paramDescriptors = new ArrayList<TypeDescriptor>(paramTypes.length);
for (int i = 0; i < paramTypes.length; i++) {
paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i)));
}
ReflectionHelper.ArgumentsMatchInfo matchInfo = null;
if (method.isVarArgs() && argumentTypes.size() >= (paramTypes.length - 1)) {
// *sigh* complicated
matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter);
}
else if (paramTypes.length == argumentTypes.size()) {
// name and parameter number match, check the arguments
matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter);
}
if (matchInfo != null) {
if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.EXACT) {
return new ReflectiveMethodExecutor(method, null);
}
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) {
if (!useDistance) {
closeMatch = method;
} else {
int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes);
if (matchDistance<closeMatchDistance) {
// this is a better match
closeMatchDistance = matchDistance;
closeMatch = method;
}
}
}
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) {
if (matchRequiringConversion != null) {
multipleOptions = true;
}
argsToConvert = matchInfo.argsRequiringConversion;
matchRequiringConversion = method;
}
}
}
}
if (closeMatch != null) {
return new ReflectiveMethodExecutor(closeMatch, null);
}
else if (matchRequiringConversion != null) {
if (multipleOptions) {
throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name);
}
return new ReflectiveMethodExecutor(matchRequiringConversion, argsToConvert);
}
else {
return null;
}
}
catch (EvaluationException ex) {
throw new AccessException("Failed to resolve method", ex);
}
}
public void registerMethodFilter(Class<?> type, MethodFilter filter) {
if (this.filters == null) {
this.filters = new HashMap<Class<?>, MethodFilter>();
}
if (filter == null) {
this.filters.remove(type);
}
else {
this.filters.put(type,filter);
}
}
}

View File

@@ -0,0 +1,527 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression.spel.support;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Simple PropertyAccessor that uses reflection to access properties for reading and writing. A property can be accessed
* if it is accessible as a field on the object or through a getter (if being read) or a setter (if being written).
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class ReflectivePropertyAccessor implements PropertyAccessor {
protected final Map<CacheKey, InvokerPair> readerCache = new ConcurrentHashMap<CacheKey, InvokerPair>();
protected final Map<CacheKey, Member> writerCache = new ConcurrentHashMap<CacheKey, Member>();
protected final Map<CacheKey, TypeDescriptor> typeDescriptorCache = new ConcurrentHashMap<CacheKey, TypeDescriptor>();
/**
* @return null which means this is a general purpose accessor
*/
public Class<?>[] getSpecificTargetClasses() {
return null;
}
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
if (target == null) {
return false;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (type.isArray() && name.equals("length")) {
return true;
}
CacheKey cacheKey = new CacheKey(type, name);
if (this.readerCache.containsKey(cacheKey)) {
return true;
}
Method method = findGetterForProperty(name, type, target instanceof Class);
if (method != null) {
// Treat it like a property
// The readerCache will only contain gettable properties (let's not worry about setters for now)
Property property = new Property(type, method, null);
TypeDescriptor typeDescriptor = new TypeDescriptor(property);
this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor));
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true;
}
else {
Field field = findField(name, type, target instanceof Class);
if (field != null) {
TypeDescriptor typeDescriptor = new TypeDescriptor(field);
this.readerCache.put(cacheKey, new InvokerPair(field,typeDescriptor));
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true;
}
}
return false;
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
if (target == null) {
throw new AccessException("Cannot read property of null target");
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (type.isArray() && name.equals("length")) {
if (target instanceof Class) {
throw new AccessException("Cannot access length on array class itself");
}
return new TypedValue(Array.getLength(target));
}
CacheKey cacheKey = new CacheKey(type, name);
InvokerPair invoker = this.readerCache.get(cacheKey);
if (invoker == null || invoker.member instanceof Method) {
Method method = (Method) (invoker != null ? invoker.member : null);
if (method == null) {
method = findGetterForProperty(name, type, target instanceof Class);
if (method != null) {
// TODO remove the duplication here between canRead and read
// Treat it like a property
// The readerCache will only contain gettable properties (let's not worry about setters for now)
Property property = new Property(type, method, null);
TypeDescriptor typeDescriptor = new TypeDescriptor(property);
invoker = new InvokerPair(method, typeDescriptor);
this.readerCache.put(cacheKey, invoker);
}
}
if (method != null) {
try {
ReflectionUtils.makeAccessible(method);
Object value = method.invoke(target);
return new TypedValue(value, invoker.typeDescriptor.narrow(value));
}
catch (Exception ex) {
throw new AccessException("Unable to access property '" + name + "' through getter", ex);
}
}
}
if (invoker == null || invoker.member instanceof Field) {
Field field = (Field) (invoker == null ? null : invoker.member);
if (field == null) {
field = findField(name, type, target instanceof Class);
if (field != null) {
invoker = new InvokerPair(field, new TypeDescriptor(field));
this.readerCache.put(cacheKey, invoker);
}
}
if (field != null) {
try {
ReflectionUtils.makeAccessible(field);
Object value = field.get(target);
return new TypedValue(value, invoker.typeDescriptor.narrow(value));
}
catch (Exception ex) {
throw new AccessException("Unable to access field: " + name, ex);
}
}
}
throw new AccessException("Neither getter nor field found for property '" + name + "'");
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
if (target == null) {
return false;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
CacheKey cacheKey = new CacheKey(type, name);
if (this.writerCache.containsKey(cacheKey)) {
return true;
}
Method method = findSetterForProperty(name, type, target instanceof Class);
if (method != null) {
// Treat it like a property
Property property = new Property(type, null, method);
TypeDescriptor typeDescriptor = new TypeDescriptor(property);
this.writerCache.put(cacheKey, method);
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true;
}
else {
Field field = findField(name, type, target instanceof Class);
if (field != null) {
this.writerCache.put(cacheKey, field);
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field));
return true;
}
}
return false;
}
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
if (target == null) {
throw new AccessException("Cannot write property on null target");
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
Object possiblyConvertedNewValue = newValue;
TypeDescriptor typeDescriptor = getTypeDescriptor(context, target, name);
if (typeDescriptor != null) {
try {
possiblyConvertedNewValue = context.getTypeConverter().convertValue(
newValue, TypeDescriptor.forObject(newValue), typeDescriptor);
}
catch (EvaluationException evaluationException) {
throw new AccessException("Type conversion failure",evaluationException);
}
}
CacheKey cacheKey = new CacheKey(type, name);
Member cachedMember = this.writerCache.get(cacheKey);
if (cachedMember == null || cachedMember instanceof Method) {
Method method = (Method) cachedMember;
if (method == null) {
method = findSetterForProperty(name, type, target instanceof Class);
if (method != null) {
cachedMember = method;
this.writerCache.put(cacheKey, cachedMember);
}
}
if (method != null) {
try {
ReflectionUtils.makeAccessible(method);
method.invoke(target, possiblyConvertedNewValue);
return;
}
catch (Exception ex) {
throw new AccessException("Unable to access property '" + name + "' through setter", ex);
}
}
}
if (cachedMember == null || cachedMember instanceof Field) {
Field field = (Field) cachedMember;
if (field == null) {
field = findField(name, type, target instanceof Class);
if (field != null) {
cachedMember = field;
this.writerCache.put(cacheKey, cachedMember);
}
}
if (field != null) {
try {
ReflectionUtils.makeAccessible(field);
field.set(target, possiblyConvertedNewValue);
return;
}
catch (Exception ex) {
throw new AccessException("Unable to access field: " + name, ex);
}
}
}
throw new AccessException("Neither setter nor field found for property '" + name + "'");
}
private TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
if (target == null) {
return null;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (type.isArray() && name.equals("length")) {
return TypeDescriptor.valueOf(Integer.TYPE);
}
CacheKey cacheKey = new CacheKey(type, name);
TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey);
if (typeDescriptor == null) {
// attempt to populate the cache entry
try {
if (canRead(context, target, name)) {
typeDescriptor = this.typeDescriptorCache.get(cacheKey);
}
else if (canWrite(context, target, name)) {
typeDescriptor = this.typeDescriptorCache.get(cacheKey);
}
}
catch (AccessException ex) {
// continue with null type descriptor
}
}
return typeDescriptor;
}
/**
* Find a getter method for the specified property. A getter is defined as a method whose name start with the prefix
* 'get' and the rest of the name is the same as the property name (with the first character uppercased).
*/
protected Method findGetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) {
Method[] ms = clazz.getMethods();
// Try "get*" method...
String getterName = "get" + StringUtils.capitalize(propertyName);
for (Method method : ms) {
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 &&
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
return method;
}
}
// Try "is*" method...
getterName = "is" + StringUtils.capitalize(propertyName);
for (Method method : ms) {
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 &&
boolean.class.equals(method.getReturnType()) &&
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
return method;
}
}
return null;
}
/**
* Find a setter method for the specified property.
*/
protected Method findSetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) {
Method[] methods = clazz.getMethods();
String setterName = "set" + StringUtils.capitalize(propertyName);
for (Method method : methods) {
if (method.getName().equals(setterName) && method.getParameterTypes().length == 1 &&
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
return method;
}
}
return null;
}
/**
* Find a field of a certain name on a specified class
*/
protected Field findField(String name, Class<?> clazz, boolean mustBeStatic) {
Field[] fields = clazz.getFields();
for (Field field : fields) {
if (field.getName().equals(name) && (!mustBeStatic || Modifier.isStatic(field.getModifiers()))) {
return field;
}
}
return null;
}
/**
* Captures the member (method/field) to call reflectively to access a property value and the type descriptor for the
* value returned by the reflective call.
*/
private static class InvokerPair {
final Member member;
final TypeDescriptor typeDescriptor;
public InvokerPair(Member member, TypeDescriptor typeDescriptor) {
this.member = member;
this.typeDescriptor = typeDescriptor;
}
}
private static class CacheKey {
private final Class clazz;
private final String name;
public CacheKey(Class clazz, String name) {
this.clazz = clazz;
this.name = name;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof CacheKey)) {
return false;
}
CacheKey otherKey = (CacheKey) other;
return (this.clazz.equals(otherKey.clazz) && this.name.equals(otherKey.name));
}
@Override
public int hashCode() {
return this.clazz.hashCode() * 29 + this.name.hashCode();
}
}
/**
* Attempt to create an optimized property accessor tailored for a property of a particular name on
* a particular class. The general ReflectivePropertyAccessor will always work but is not optimal
* due to the need to lookup which reflective member (method/field) to use each time read() is called.
* This method will just return the ReflectivePropertyAccessor instance if it is unable to build
* something more optimal.
*/
public PropertyAccessor createOptimalAccessor(EvaluationContext eContext, Object target, String name) {
// Don't be clever for arrays or null target
if (target == null) {
return this;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (type.isArray()) {
return this;
}
CacheKey cacheKey = new CacheKey(type, name);
InvokerPair invocationTarget = this.readerCache.get(cacheKey);
if (invocationTarget == null || invocationTarget.member instanceof Method) {
Method method = (Method) (invocationTarget==null?null:invocationTarget.member);
if (method == null) {
method = findGetterForProperty(name, type, target instanceof Class);
if (method != null) {
invocationTarget = new InvokerPair(method,new TypeDescriptor(new MethodParameter(method,-1)));
ReflectionUtils.makeAccessible(method);
this.readerCache.put(cacheKey, invocationTarget);
}
}
if (method != null) {
return new OptimalPropertyAccessor(invocationTarget);
}
}
if (invocationTarget == null || invocationTarget.member instanceof Field) {
Field field = (Field) (invocationTarget==null?null:invocationTarget.member);
if (field == null) {
field = findField(name, type, target instanceof Class);
if (field != null) {
invocationTarget = new InvokerPair(field, new TypeDescriptor(field));
ReflectionUtils.makeAccessible(field);
this.readerCache.put(cacheKey, invocationTarget);
}
}
if (field != null) {
return new OptimalPropertyAccessor(invocationTarget);
}
}
return this;
}
/**
* An optimized form of a PropertyAccessor that will use reflection but only knows how to access a particular property
* on a particular class. This is unlike the general ReflectivePropertyResolver which manages a cache of methods/fields that
* may be invoked to access different properties on different classes. This optimal accessor exists because looking up
* the appropriate reflective object by class/name on each read is not cheap.
*/
static class OptimalPropertyAccessor implements PropertyAccessor {
private final Member member;
private final TypeDescriptor typeDescriptor;
private final boolean needsToBeMadeAccessible;
OptimalPropertyAccessor(InvokerPair target) {
this.member = target.member;
this.typeDescriptor = target.typeDescriptor;
if (this.member instanceof Field) {
Field field = (Field)member;
needsToBeMadeAccessible = (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()))
&& !field.isAccessible();
}
else {
Method method = (Method)member;
needsToBeMadeAccessible = ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible());
}
}
public Class[] getSpecificTargetClasses() {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
if (target == null) {
return false;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (type.isArray()) {
return false;
}
if (member instanceof Method) {
Method method = (Method)member;
String getterName = "get" + StringUtils.capitalize(name);
if (getterName.equals(method.getName())) {
return true;
}
getterName = "is" + StringUtils.capitalize(name);
return getterName.equals(method.getName());
}
else {
Field field = (Field)member;
return field.getName().equals(name);
}
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
if (member instanceof Method) {
try {
if (needsToBeMadeAccessible) {
ReflectionUtils.makeAccessible((Method) member);
}
Object value = ((Method) member).invoke(target);
return new TypedValue(value, typeDescriptor.narrow(value));
}
catch (Exception ex) {
throw new AccessException("Unable to access property '" + name + "' through getter", ex);
}
}
if (member instanceof Field) {
try {
if (needsToBeMadeAccessible) {
ReflectionUtils.makeAccessible((Field)member);
}
Object value = ((Field)member).get(target);
return new TypedValue(value, typeDescriptor.narrow(value));
}
catch (Exception ex) {
throw new AccessException("Unable to access field: " + name, ex);
}
}
throw new AccessException("Neither getter nor field found for property '" + name + "'");
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
public void write(EvaluationContext context, Object target, String name, Object newValue)
throws AccessException {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
}
}

View File

@@ -0,0 +1,275 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression.spel.support;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.MethodFilter;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.TypedValue;
import org.springframework.util.Assert;
/**
* Provides a default EvaluationContext implementation.
*
* <p>To resolve properties/methods/fields this context uses a reflection mechanism.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class StandardEvaluationContext implements EvaluationContext {
private TypedValue rootObject;
private List<ConstructorResolver> constructorResolvers;
private List<MethodResolver> methodResolvers;
private ReflectiveMethodResolver reflectiveMethodResolver;
private List<PropertyAccessor> propertyAccessors;
private TypeLocator typeLocator;
private TypeConverter typeConverter;
private TypeComparator typeComparator = new StandardTypeComparator();
private OperatorOverloader operatorOverloader = new StandardOperatorOverloader();
private final Map<String, Object> variables = new HashMap<String, Object>();
private BeanResolver beanResolver;
public StandardEvaluationContext() {
setRootObject(null);
}
public StandardEvaluationContext(Object rootObject) {
this();
setRootObject(rootObject);
}
public void setRootObject(Object rootObject, TypeDescriptor typeDescriptor) {
this.rootObject = new TypedValue(rootObject, typeDescriptor);
}
public void setRootObject(Object rootObject) {
this.rootObject = (rootObject != null ? new TypedValue(rootObject) : TypedValue.NULL);
}
public TypedValue getRootObject() {
return this.rootObject;
}
public void addConstructorResolver(ConstructorResolver resolver) {
ensureConstructorResolversInitialized();
this.constructorResolvers.add(this.constructorResolvers.size() - 1, resolver);
}
public boolean removeConstructorResolver(ConstructorResolver resolver) {
ensureConstructorResolversInitialized();
return this.constructorResolvers.remove(resolver);
}
public List<ConstructorResolver> getConstructorResolvers() {
ensureConstructorResolversInitialized();
return this.constructorResolvers;
}
public void setConstructorResolvers(List<ConstructorResolver> constructorResolvers) {
this.constructorResolvers = constructorResolvers;
}
public void addMethodResolver(MethodResolver resolver) {
ensureMethodResolversInitialized();
this.methodResolvers.add(this.methodResolvers.size() - 1, resolver);
}
public boolean removeMethodResolver(MethodResolver methodResolver) {
ensureMethodResolversInitialized();
return this.methodResolvers.remove(methodResolver);
}
public List<MethodResolver> getMethodResolvers() {
ensureMethodResolversInitialized();
return this.methodResolvers;
}
public void setBeanResolver(BeanResolver beanResolver) {
this.beanResolver = beanResolver;
}
public BeanResolver getBeanResolver() {
return this.beanResolver;
}
public void setMethodResolvers(List<MethodResolver> methodResolvers) {
this.methodResolvers = methodResolvers;
}
public void addPropertyAccessor(PropertyAccessor accessor) {
ensurePropertyAccessorsInitialized();
this.propertyAccessors.add(this.propertyAccessors.size() - 1, accessor);
}
public boolean removePropertyAccessor(PropertyAccessor accessor) {
return this.propertyAccessors.remove(accessor);
}
public List<PropertyAccessor> getPropertyAccessors() {
ensurePropertyAccessorsInitialized();
return this.propertyAccessors;
}
public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
this.propertyAccessors = propertyAccessors;
}
public void setTypeLocator(TypeLocator typeLocator) {
Assert.notNull(typeLocator, "TypeLocator must not be null");
this.typeLocator = typeLocator;
}
public TypeLocator getTypeLocator() {
if (this.typeLocator == null) {
this.typeLocator = new StandardTypeLocator();
}
return this.typeLocator;
}
public void setTypeConverter(TypeConverter typeConverter) {
Assert.notNull(typeConverter, "TypeConverter must not be null");
this.typeConverter = typeConverter;
}
public TypeConverter getTypeConverter() {
if (this.typeConverter == null) {
this.typeConverter = new StandardTypeConverter();
}
return this.typeConverter;
}
public void setTypeComparator(TypeComparator typeComparator) {
Assert.notNull(typeComparator, "TypeComparator must not be null");
this.typeComparator = typeComparator;
}
public TypeComparator getTypeComparator() {
return this.typeComparator;
}
public void setOperatorOverloader(OperatorOverloader operatorOverloader) {
Assert.notNull(operatorOverloader, "OperatorOverloader must not be null");
this.operatorOverloader = operatorOverloader;
}
public OperatorOverloader getOperatorOverloader() {
return this.operatorOverloader;
}
public void setVariable(String name, Object value) {
this.variables.put(name, value);
}
public void setVariables(Map<String,Object> variables) {
this.variables.putAll(variables);
}
public void registerFunction(String name, Method method) {
this.variables.put(name, method);
}
public Object lookupVariable(String name) {
return this.variables.get(name);
}
/**
* Register a MethodFilter which will be called during method resolution for the
* specified type. The MethodFilter may remove methods and/or sort the methods
* which will then be used by SpEL as the candidates to look through for a match.
*
* @param type the type for which the filter should be called
* @param filter a MethodFilter, or NULL to deregister a filter for the type
*/
public void registerMethodFilter(Class<?> type, MethodFilter filter) {
ensureMethodResolversInitialized();
reflectiveMethodResolver.registerMethodFilter(type,filter);
}
private void ensurePropertyAccessorsInitialized() {
if (this.propertyAccessors == null) {
initializePropertyAccessors();
}
}
private synchronized void initializePropertyAccessors() {
if (this.propertyAccessors == null) {
List<PropertyAccessor> defaultAccessors = new ArrayList<PropertyAccessor>();
defaultAccessors.add(new ReflectivePropertyAccessor());
this.propertyAccessors = defaultAccessors;
}
}
private void ensureMethodResolversInitialized() {
if (this.methodResolvers == null) {
initializeMethodResolvers();
}
}
private synchronized void initializeMethodResolvers() {
if (this.methodResolvers == null) {
List<MethodResolver> defaultResolvers = new ArrayList<MethodResolver>();
defaultResolvers.add(reflectiveMethodResolver = new ReflectiveMethodResolver());
this.methodResolvers = defaultResolvers;
}
}
private void ensureConstructorResolversInitialized() {
if (this.constructorResolvers == null) {
initializeConstructorResolvers();
}
}
private synchronized void initializeConstructorResolvers() {
if (this.constructorResolvers == null) {
List<ConstructorResolver> defaultResolvers = new ArrayList<ConstructorResolver>();
defaultResolvers.add(new ReflectiveConstructorResolver());
this.constructorResolvers = defaultResolvers;
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.support;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.OperatorOverloader;
/**
* @author Juergen Hoeller
* @since 3.0
*/
public class StandardOperatorOverloader implements OperatorOverloader {
public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand)
throws EvaluationException {
return false;
}
public Object operate(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException {
throw new EvaluationException("No operation overloaded by default");
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.support;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
* A simple basic TypeComparator implementation. It supports comparison of numbers and types implementing Comparable.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class StandardTypeComparator implements TypeComparator {
@SuppressWarnings("unchecked")
public int compare(Object left, Object right) throws SpelEvaluationException {
// If one is null, check if the other is
if (left == null) {
return right == null ? 0 : -1;
} else if (right == null) {
return 1; // left cannot be null
}
// Basic number comparisons
if (left instanceof Number && right instanceof Number) {
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || rightNumber instanceof Double) {
double d1 = leftNumber.doubleValue();
double d2 = rightNumber.doubleValue();
return Double.compare(d1,d2);
} else if (leftNumber instanceof Float || rightNumber instanceof Float) {
float f1 = leftNumber.floatValue();
float f2 = rightNumber.floatValue();
return Float.compare(f1,f2);
} else if (leftNumber instanceof Long || rightNumber instanceof Long) {
Long l1 = leftNumber.longValue();
Long l2 = rightNumber.longValue();
return l1.compareTo(l2);
} else {
Integer i1 = leftNumber.intValue();
Integer i2 = rightNumber.intValue();
return i1.compareTo(i2);
}
}
try {
if (left instanceof Comparable) {
return ((Comparable) left).compareTo(right);
}
} catch (ClassCastException cce) {
throw new SpelEvaluationException(cce, SpelMessage.NOT_COMPARABLE, left.getClass(), right.getClass());
}
throw new SpelEvaluationException(SpelMessage.NOT_COMPARABLE, left.getClass(), right.getClass());
}
public boolean canCompare(Object left, Object right) {
if (left == null || right == null) {
return true;
}
if (left instanceof Number && right instanceof Number) {
return true;
}
if (left instanceof Comparable) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.expression.spel.support;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert;
/**
* Default implementation of the {@link TypeConverter} interface,
* delegating to a core Spring {@link ConversionService}.
*
* @author Juergen Hoeller
* @author Andy Clement
* @since 3.0
* @see org.springframework.core.convert.ConversionService
*/
public class StandardTypeConverter implements TypeConverter {
private static ConversionService defaultConversionService;
private final ConversionService conversionService;
public StandardTypeConverter() {
synchronized (this) {
if (defaultConversionService == null) {
defaultConversionService = new DefaultConversionService();
}
}
this.conversionService = defaultConversionService;
}
public StandardTypeConverter(ConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = conversionService;
}
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType, targetType);
}
public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
return this.conversionService.convert(value, sourceType, targetType);
}
catch (ConverterNotFoundException cenfe) {
throw new SpelEvaluationException(cenfe, SpelMessage.TYPE_CONVERSION_ERROR, sourceType.toString(), targetType.toString());
}
catch (ConversionException ce) {
throw new SpelEvaluationException(ce, SpelMessage.TYPE_CONVERSION_ERROR, sourceType.toString(), targetType.toString());
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* 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.springframework.expression.spel.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.ClassUtils;
/**
* A default implementation of a TypeLocator that uses the context classloader (or any classloader set upon it). It
* supports 'well known' packages so if a type cannot be found it will try the registered imports to locate it.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class StandardTypeLocator implements TypeLocator {
private ClassLoader loader;
private final List<String> knownPackagePrefixes = new ArrayList<String>();
public StandardTypeLocator() {
this(ClassUtils.getDefaultClassLoader());
}
public StandardTypeLocator(ClassLoader loader) {
this.loader = loader;
// Similar to when writing Java, it only knows about java.lang by default
registerImport("java.lang");
}
/**
* Find a (possibly unqualified) type reference - first using the typename as is, then trying any registered
* prefixes if the typename cannot be found.
* @param typename the type to locate
* @return the class object for the type
* @throws EvaluationException if the type cannot be found
*/
public Class<?> findType(String typename) throws EvaluationException {
String nameToLookup = typename;
try {
return this.loader.loadClass(nameToLookup);
}
catch (ClassNotFoundException ey) {
// try any registered prefixes before giving up
}
for (String prefix : this.knownPackagePrefixes) {
try {
nameToLookup = new StringBuilder().append(prefix).append(".").append(typename).toString();
return this.loader.loadClass(nameToLookup);
}
catch (ClassNotFoundException ex) {
// might be a different prefix
}
}
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typename);
}
/**
* Register a new import prefix that will be used when searching for unqualified types.
* Expected format is something like "java.lang".
* @param prefix the prefix to register
*/
public void registerImport(String prefix) {
this.knownPackagePrefixes.add(prefix);
}
/**
* Return a list of all the import prefixes registered with this StandardTypeLocator.
* @return list of registered import prefixes
*/
public List<String> getImportPrefixes() {
return Collections.unmodifiableList(this.knownPackagePrefixes);
}
public void removeImport(String prefix) {
this.knownPackagePrefixes.remove(prefix);
}
}

Some files were not shown because too many files have changed in this diff Show More