Index: 3rdParty_sources/spring/org/springframework/expression/AccessException.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/AccessException.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/AccessException.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2012 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 + */ +@SuppressWarnings("serial") +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); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/BeanResolver.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/BeanResolver.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/BeanResolver.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2012 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} 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; + +} Index: 3rdParty_sources/spring/org/springframework/expression/ConstructorExecutor.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/ConstructorExecutor.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/ConstructorExecutor.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2013 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; + +} Index: 3rdParty_sources/spring/org/springframework/expression/ConstructorResolver.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/ConstructorResolver.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/ConstructorResolver.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2013 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} 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 argumentTypes) + throws AccessException; + +} Index: 3rdParty_sources/spring/org/springframework/expression/EvaluationContext.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/EvaluationContext.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/EvaluationContext.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2013 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. + * + *

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 getConstructorResolvers(); + + /** + * Return a list of resolvers that will be asked in turn to locate a method. + */ + List getMethodResolvers(); + + /** + * Return a list of accessors that will be asked in turn to read/write a property. + */ + List 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); + +} Index: 3rdParty_sources/spring/org/springframework/expression/EvaluationException.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/EvaluationException.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/EvaluationException.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2012 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 + */ +@SuppressWarnings("serial") +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); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/Expression.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/Expression.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/Expression.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,240 @@ +/* + * Copyright 2002-2013 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 getValue(Class 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 getValue(Object rootObject, Class 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 getValue(EvaluationContext context, Class 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 getValue(EvaluationContext context, Object rootObject, Class 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(); + +} Index: 3rdParty_sources/spring/org/springframework/expression/ExpressionException.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/ExpressionException.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/ExpressionException.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2013 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 + */ +@SuppressWarnings("serial") +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); + } + + + /** + * Return the exception message. Since Spring 4.0 this method returns the same + * result as {@link #toDetailedString()}. + * @see java.lang.Throwable#getMessage() + */ + @Override + public String getMessage() { + return toDetailedString(); + } + + /** + * Return the exception simple message without including the expression that caused + * the failure. + */ + public String getSimpleMessage() { + return super.getMessage(); + } + + public String toDetailedString() { + StringBuilder output = new StringBuilder(); + if (this.expressionString!=null) { + output.append("Expression '"); + output.append(this.expressionString); + output.append("'"); + if (this.position!=-1) { + output.append(" @ "); + output.append(this.position); + } + output.append(": "); + } + output.append(getSimpleMessage()); + return output.toString(); + } + + public final String getExpressionString() { + return this.expressionString; + } + + public final int getPosition() { + return this.position; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/ExpressionInvocationTargetException.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/ExpressionInvocationTargetException.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/ExpressionInvocationTargetException.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2013 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 + */ +@SuppressWarnings("serial") +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); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/ExpressionParser.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/ExpressionParser.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/ExpressionParser.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -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. + *

Some examples: + *

+	 *     3 + 4
+	 *     name.firstName
+	 * 
+ * @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. + *

Some examples: + *

+	 *     3 + 4
+	 *     name.firstName
+	 * 
+ * @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; + +} Index: 3rdParty_sources/spring/org/springframework/expression/MethodExecutor.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/MethodExecutor.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/MethodExecutor.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2013 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. + * + *

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; + +} Index: 3rdParty_sources/spring/org/springframework/expression/MethodFilter.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/MethodFilter.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/MethodFilter.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2013 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 filter(List methods); + +} Index: 3rdParty_sources/spring/org/springframework/expression/MethodResolver.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/MethodResolver.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/MethodResolver.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2013 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 {@link MethodExecutor} that can be used + * to invoke that method, or {@code null} 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 argumentTypes) throws AccessException; + +} Index: 3rdParty_sources/spring/org/springframework/expression/Operation.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/Operation.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/Operation.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2013 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 + +} Index: 3rdParty_sources/spring/org/springframework/expression/OperatorOverloader.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/OperatorOverloader.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/OperatorOverloader.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2013 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; + +} Index: 3rdParty_sources/spring/org/springframework/expression/ParseException.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/ParseException.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/ParseException.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2012 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 + */ +@SuppressWarnings("serial") +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); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/ParserContext.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/ParserContext.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/ParserContext.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2013 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: + *

+	 * 	   Some literal text
+	 *     Hello #{name.firstName}!
+	 *     #{3 + 4}
+	 * 
+ * @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() { + + @Override + public String getExpressionPrefix() { + return "#{"; + } + + @Override + public String getExpressionSuffix() { + return "}"; + } + + @Override + public boolean isTemplate() { + return true; + } + + }; + +} Index: 3rdParty_sources/spring/org/springframework/expression/PropertyAccessor.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/PropertyAccessor.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/PropertyAccessor.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2014 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 from (and possibly write to) an object's properties. + * This 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. However, if it returns {@code null} from {@link #getSpecificTargetClasses()}, + * 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 {@link #getSpecificTargetClasses()} 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 {@code 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 {@code 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. + * Should only succeed if {@link #canRead} also returns {@code 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 + * @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 {@link #canWrite} also returns {@code 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; + +} Index: 3rdParty_sources/spring/org/springframework/expression/TypeComparator.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/TypeComparator.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/TypeComparator.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2013 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 java.lang.Comparable}. + * + * @author Andy Clement + * @since 3.0 + * @see java.lang.Comparable + */ +public interface TypeComparator { + + /** + * Return {@code true} if the comparator can compare these two objects. + * @param firstObject the first object + * @param secondObject the second object + * @return {@code true} if the comparator can compare these objects + */ + boolean canCompare(Object firstObject, Object secondObject); + + /** + * Compare two given 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 if they are not comparable in the first place) + */ + int compare(Object firstObject, Object secondObject) throws EvaluationException; + +} Index: 3rdParty_sources/spring/org/springframework/expression/TypeConverter.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/TypeConverter.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/TypeConverter.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2013 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 {@code 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 {@code true} if that conversion can be performed + */ + boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); + + /** + * Convert (or coerce) a value from one type to another, for example from a + * {@code boolean} to a {@code String}. + *

The {@link TypeDescriptor} parameters enable support for typed collections: + * A caller may prefer a {@code List<Integer>}, for example, rather than + * simply any {@code 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 failed or is not possible to begin with + */ + Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType); + +} Index: 3rdParty_sources/spring/org/springframework/expression/TypeLocator.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/TypeLocator.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/TypeLocator.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2013 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; + +/** + * Implementers of this interface are expected to be able to locate types. + * They may use a custom {@link ClassLoader} and/or deal with common + * package prefixes (e.g. {@code java.lang}) 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 + * (e.g. {@code String} or {@code java.lang.String}). + * @param typeName the type to be located + * @return the {@code Class} object representing that type + * @throws EvaluationException if there is a problem finding the type + */ + Class findType(String typeName) throws EvaluationException; + +} Index: 3rdParty_sources/spring/org/springframework/expression/TypedValue.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/TypedValue.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/TypedValue.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2014 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; +import org.springframework.util.ObjectUtils; + +/** + * Encapsulates an object and a {@link TypeDescriptor} that describes it. + * The type descriptor can contain generic declarations that would not + * be accessible through a simple {@code getClass()} 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 {@link TypedValue} for a simple object. The {@link TypeDescriptor} + * is inferred from the object, so no generic declarations are preserved. + * @param value the object value + */ + public TypedValue(Object value) { + this.value = value; + this.typeDescriptor = null; // initialized when/if requested + } + + /** + * Create a {@link TypedValue} for a particular value with a particular + * {@link TypeDescriptor} which may contain additional generic declarations. + * @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.value != null) { + this.typeDescriptor = TypeDescriptor.forObject(this.value); + } + return this.typeDescriptor; + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof TypedValue)) { + return false; + } + TypedValue otherTv = (TypedValue) other; + // Avoid TypeDescriptor initialization if not necessary + return (ObjectUtils.nullSafeEquals(this.value, otherTv.value) && + ((this.typeDescriptor == null && otherTv.typeDescriptor == null) || + ObjectUtils.nullSafeEquals(getTypeDescriptor(), otherTv.getTypeDescriptor()))); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.value); + } + + @Override + public String toString() { + return "TypedValue: '" + this.value + "' of [" + getTypeDescriptor() + "]"; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/common/CompositeStringExpression.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/common/CompositeStringExpression.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/common/CompositeStringExpression.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,209 @@ +/* + * Copyright 2002-2013 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; +import org.springframework.expression.TypedValue; + +/** + * 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: + * + *

+ * "Hello ${getName()}"
+ * 
+ * + * 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()} 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; + } + + + @Override + public final String getExpressionString() { + return this.expressionString; + } + + @Override + 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(); + } + + @Override + 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(); + } + + @Override + 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(); + } + + @Override + 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(); + } + + @Override + public Class getValueType(EvaluationContext context) { + return String.class; + } + + @Override + public Class getValueType() { + return String.class; + } + + @Override + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) { + return TypeDescriptor.valueOf(String.class); + } + + @Override + public TypeDescriptor getValueTypeDescriptor() { + return TypeDescriptor.valueOf(String.class); + } + + @Override + public void setValue(EvaluationContext context, Object value) throws EvaluationException { + throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression"); + } + + @Override + public T getValue(EvaluationContext context, Class expectedResultType) throws EvaluationException { + Object value = getValue(context); + return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType); + } + + @Override + public T getValue(Class expectedResultType) throws EvaluationException { + Object value = getValue(); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), expectedResultType); + } + + @Override + public boolean isWritable(EvaluationContext context) { + return false; + } + + public Expression[] getExpressions() { + return this.expressions; + } + + + @Override + public T getValue(Object rootObject, Class desiredResultType) throws EvaluationException { + Object value = getValue(rootObject); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType); + } + + @Override + public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) + throws EvaluationException { + Object value = getValue(context,rootObject); + return ExpressionUtils.convertTypedValue(context, new TypedValue(value), desiredResultType); + } + + @Override + public Class getValueType(Object rootObject) throws EvaluationException { + return String.class; + } + + @Override + public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { + return String.class; + } + + @Override + public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + return TypeDescriptor.valueOf(String.class); + } + + @Override + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { + return TypeDescriptor.valueOf(String.class); + } + + @Override + public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { + return false; + } + + @Override + public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { + throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression"); + } + + @Override + public boolean isWritable(Object rootObject) throws EvaluationException { + return false; + } + + @Override + public void setValue(Object rootObject, Object value) throws EvaluationException { + throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression"); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/common/ExpressionUtils.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/common/ExpressionUtils.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/common/ExpressionUtils.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2013 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 + * @deprecated use {@link #convertTypedValue(EvaluationContext, TypedValue, Class)} + */ + @Deprecated + public static T convert(EvaluationContext context, Object value, Class targetType) throws EvaluationException { + 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 convertTypedValue(EvaluationContext context, TypedValue typedValue, Class targetType) { + Object value = typedValue.getValue(); + if (targetType == null) { + return (T) value; + } + if (context != null) { + return (T) context.getTypeConverter().convertValue( + value, typedValue.getTypeDescriptor(), TypeDescriptor.valueOf(targetType)); + } + if (ClassUtils.isAssignableValue(targetType, value)) { + return (T) value; + } + 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)); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/common/LiteralExpression.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/common/LiteralExpression.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/common/LiteralExpression.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,164 @@ +/* + * Copyright 2002-2013 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; +import org.springframework.expression.TypedValue; + +/** + * 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; + } + + + @Override + public final String getExpressionString() { + return this.literalValue; + } + + @Override + public String getValue() { + return this.literalValue; + } + + @Override + public String getValue(EvaluationContext context) { + return this.literalValue; + } + + @Override + public String getValue(Object rootObject) { + return this.literalValue; + } + + @Override + public Class getValueType(EvaluationContext context) { + return String.class; + } + + @Override + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) { + return TypeDescriptor.valueOf(String.class); + } + + @Override + public TypeDescriptor getValueTypeDescriptor() { + return TypeDescriptor.valueOf(String.class); + } + + @Override + public void setValue(EvaluationContext context, Object value) throws EvaluationException { + throw new EvaluationException(this.literalValue, "Cannot call setValue() on a LiteralExpression"); + } + + @Override + public T getValue(EvaluationContext context, Class expectedResultType) throws EvaluationException { + Object value = getValue(context); + return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType); + } + + @Override + public T getValue(Class expectedResultType) throws EvaluationException { + Object value = getValue(); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), expectedResultType); + } + + @Override + public boolean isWritable(EvaluationContext context) { + return false; + } + + @Override + public Class getValueType() { + return String.class; + } + + @Override + public T getValue(Object rootObject, Class desiredResultType) throws EvaluationException { + Object value = getValue(rootObject); + return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType); + } + + @Override + public String getValue(EvaluationContext context, Object rootObject) throws EvaluationException { + return this.literalValue; + } + + @Override + public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { + Object value = getValue(context, rootObject); + return ExpressionUtils.convertTypedValue(context, new TypedValue(value), desiredResultType); + } + + @Override + public Class getValueType(Object rootObject) throws EvaluationException { + return String.class; + } + + @Override + public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { + return String.class; + } + + @Override + public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + return TypeDescriptor.valueOf(String.class); + } + + @Override + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { + return TypeDescriptor.valueOf(String.class); + } + + @Override + public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { + return false; + } + + @Override + public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { + throw new EvaluationException(this.literalValue, "Cannot call setValue() on a LiteralExpression"); + } + + @Override + public boolean isWritable(Object rootObject) throws EvaluationException { + return false; + } + + @Override + public void setValue(Object rootObject, Object value) throws EvaluationException { + throw new EvaluationException(this.literalValue, "Cannot call setValue() on a LiteralExpression"); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/common/TemplateAwareExpressionParser.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/common/TemplateAwareExpressionParser.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/common/TemplateAwareExpressionParser.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,326 @@ +/* + * Copyright 2002-2013 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() { + + @Override + public String getExpressionPrefix() { + return null; + } + + @Override + public String getExpressionSuffix() { + return null; + } + + @Override + public boolean isTemplate() { + return false; + } + }; + + @Override + public Expression parseExpression(String expressionString) throws ParseException { + return parseExpression(expressionString, NON_TEMPLATE_PARSER_CONTEXT); + } + + @Override + 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 expressions = new LinkedList(); + 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); + } + + 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 + */ + 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 stack = new Stack(); + 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 (this.bracket == '{') { + return closeBracket == '}'; + } + else if (this.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; + +} Index: 3rdParty_sources/spring/org/springframework/expression/common/TemplateParserContext.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/common/TemplateParserContext.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/common/TemplateParserContext.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2013 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; + } + + + @Override + public final boolean isTemplate() { + return true; + } + + @Override + public final String getExpressionPrefix() { + return this.expressionPrefix; + } + + @Override + public final String getExpressionSuffix() { + return this.expressionSuffix; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ExpressionState.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ExpressionState.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ExpressionState.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,252 @@ +/* + * Copyright 2002-2013 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.TypeConverter; +import org.springframework.expression.TypedValue; +import org.springframework.util.Assert; + +/** + * 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. + * + *

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 final TypedValue rootObject; + + private final SpelParserConfiguration configuration; + + private Stack variableScopes; + + private Stack contextObjects; + + + public ExpressionState(EvaluationContext context) { + this(context, context.getRootObject(), new SpelParserConfiguration(false, false)); + } + + public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) { + this(context, context.getRootObject(), configuration); + } + + public ExpressionState(EvaluationContext context, TypedValue rootObject) { + this(context, rootObject, new SpelParserConfiguration(false, false)); + } + + public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) { + Assert.notNull(context, "EvaluationContext must not be null"); + Assert.notNull(configuration, "SpelParserConfiguration must not be null"); + this.relatedContext = context; + this.rootObject = rootObject; + this.configuration = configuration; + } + + + private void ensureVariableScopesInitialized() { + if (this.variableScopes == null) { + this.variableScopes = new Stack(); + // 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(); + } + this.contextObjects.push(obj); + } + + public void popActiveContextObject() { + if (this.contextObjects == null) { + this.contextObjects = new Stack(); + } + 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 TypeConverter getTypeConverter() { + return this.relatedContext.getTypeConverter(); + } + + 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 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 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 vars = new HashMap(); + + public VariableScope() { + } + + public VariableScope(Map 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); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/InternalParseException.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/InternalParseException.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/InternalParseException.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2012 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 + */ +@SuppressWarnings("serial") +public class InternalParseException extends RuntimeException { + + public InternalParseException(SpelParseException cause) { + super(cause); + } + + @Override + public SpelParseException getCause() { + return (SpelParseException) super.getCause(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/SpelEvaluationException.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/SpelEvaluationException.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/SpelEvaluationException.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2013 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 + */ +@SuppressWarnings("serial") +public class SpelEvaluationException extends EvaluationException { + + private final SpelMessage message; + + private final 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 (this.message != null) { + return this.message.formatMessage(this.position, this.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 this.inserts; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/SpelMessage.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/SpelMessage.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/SpelMessage.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,295 @@ +/* + * Copyright 2002-2014 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. + * + *

When a message is formatted, it will have this kind of form + * + *

+ * EL1004E: (pos 34): Type cannot be found 'String'
+ * 
+ * + * 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, + "Property or field ''{0}'' cannot be found on null"), + + PROPERTY_OR_FIELD_NOT_READABLE(Kind.ERROR, 1008, + "Property or field ''{0}'' cannot be found on object of type ''{1}'' - maybe not public?"), + + PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL(Kind.ERROR, 1009, + "Property or field ''{0}'' cannot be set on null"), + + PROPERTY_OR_FIELD_NOT_WRITABLE(Kind.ERROR, 1010, + "Property or field ''{0}'' cannot be set on object of type ''{1}'' - maybe not public?"), + + 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} 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"), + + UNEXPECTED_ESCAPE_CHAR(Kind.ERROR, 1065, "unexpected escape character."), + + OPERAND_NOT_INCREMENTABLE(Kind.ERROR, 1066, + "the expression component ''{0}'' does not support increment"), + + OPERAND_NOT_DECREMENTABLE(Kind.ERROR, 1067, + "the expression component ''{0}'' does not support decrement"), + + NOT_ASSIGNABLE(Kind.ERROR, 1068, + "the expression component ''{0}'' is not assignable"), + + MISSING_CHARACTER(Kind.ERROR, 1069, + "missing expected character ''{0}''"), + + LEFT_OPERAND_PROBLEM(Kind.ERROR, 1070, + "Problem parsing left operand"), + + MISSING_SELECTION_EXPRESSION(Kind.ERROR, 1071, + "A required selection expression has not been specified"); + + + private final Kind kind; + + private final int code; + + private final 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 (ignored and not included in the message if less than 0) + * @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(this.code); + switch (this.kind) { + case ERROR: + formattedMessage.append("E"); + break; + } + formattedMessage.append(":"); + if (pos != -1) { + formattedMessage.append("(pos ").append(pos).append("): "); + } + formattedMessage.append(MessageFormat.format(this.message, inserts)); + return formattedMessage.toString(); + } + + + public static enum Kind { INFO, WARNING, ERROR } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/SpelNode.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/SpelNode.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/SpelNode.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2013 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 obj 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(); + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/SpelParseException.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/SpelParseException.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/SpelParseException.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2013 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 + */ +@SuppressWarnings("serial") +public class SpelParseException extends ParseException { + + private final SpelMessage message; + + private final Object[] 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; + } + + + /** + * @return a formatted message with inserts applied + */ + @Override + public String getMessage() { + return (this.message != null ? this.message.formatMessage(this.position, this.inserts) + : super.getMessage()); + } + + /** + * @return the message code + */ + public SpelMessage getMessageCode() { + return this.message; + } + + /** + * @return the message inserts + */ + public Object[] getInserts() { + return this.inserts; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/SpelParserConfiguration.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/SpelParserConfiguration.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/SpelParserConfiguration.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2013 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 + * @author Phillip Webb + * @since 3.0 + * @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration) + */ +public class SpelParserConfiguration { + + private final boolean autoGrowNullReferences; + + private final boolean autoGrowCollections; + + private final int maximumAutoGrowSize; + + + /** + * Create a new {@link SpelParserConfiguration} instance. + * @param autoGrowNullReferences if null references should automatically grow + * @param autoGrowCollections if collections should automatically grow + * @see #SpelParserConfiguration(boolean, boolean, int) + */ + public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections) { + this(autoGrowNullReferences, autoGrowCollections, Integer.MAX_VALUE); + } + + /** + * Create a new {@link SpelParserConfiguration} instance. + * @param autoGrowNullReferences if null references should automatically grow + * @param autoGrowCollections if collections should automatically grow + * @param maximumAutoGrowSize the maximum size that the collection can auto grow + */ + public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) { + this.autoGrowNullReferences = autoGrowNullReferences; + this.autoGrowCollections = autoGrowCollections; + this.maximumAutoGrowSize = maximumAutoGrowSize; + } + + + /** + * @return {@code true} if {@code null} references should be automatically grown + */ + public boolean isAutoGrowNullReferences() { + return this.autoGrowNullReferences; + } + + /** + * @return {@code true} if collections should be automatically grown + */ + public boolean isAutoGrowCollections() { + return this.autoGrowCollections; + } + + /** + * @return the maximum size that a collection can auto grow + */ + public int getMaximumAutoGrowSize() { + return this.maximumAutoGrowSize; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Assign.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Assign.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Assign.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2013 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. + * + *

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 = this.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(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/AstUtils.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/AstUtils.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/AstUtils.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2013 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; + +/** + * 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 getPropertyAccessorsToTry(Class targetType, List propertyAccessors) { + List specificAccessors = new ArrayList(); + List generalAccessors = new ArrayList(); + for (PropertyAccessor resolver : propertyAccessors) { + 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 resolvers = new ArrayList(); + resolvers.addAll(specificAccessors); + resolvers.addAll(generalAccessors); + return resolvers; + } +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/BeanReference.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/BeanReference.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/BeanReference.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2013 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 final 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, this.beanname); + } + + try { + TypedValue bean = new TypedValue(beanResolver.resolve( + state.getEvaluationContext(), this.beanname)); + return bean; + } + catch (AccessException ae) { + throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_BEAN_RESOLUTION, + this.beanname, ae.getMessage()); + } + } + + @Override + public String toStringAST() { + StringBuilder sb = new StringBuilder(); + sb.append("@"); + if (this.beanname.indexOf('.') == -1) { + sb.append(this.beanname); + } + else { + sb.append("'").append(this.beanname).append("'"); + } + return sb.toString(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/BooleanLiteral.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/BooleanLiteral.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/BooleanLiteral.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2013 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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/CompoundExpression.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/CompoundExpression.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/CompoundExpression.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2014 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("Do not build compound expression less than one entry: " + + expressionComponents.length); + } + } + + + @Override + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { + if (getChildCount() == 1) { + return this.children[0].getValueRef(state); + } + SpelNodeImpl nextNode = this.children[0]; + try { + TypedValue result = nextNode.getValueInternal(state); + int cc = getChildCount(); + for (int i = 1; i < cc - 1; i++) { + try { + state.pushActiveContextObject(result); + nextNode = this.children[i]; + result = nextNode.getValueInternal(state); + } + finally { + state.popActiveContextObject(); + } + } + try { + state.pushActiveContextObject(result); + nextNode = this.children[cc-1]; + return nextNode.getValueRef(state); + } + finally { + state.popActiveContextObject(); + } + } + catch (SpelEvaluationException ee) { + // Correct the position for the error before re-throwing + ee.setPosition(nextNode.getStartPosition()); + throw ee; + } + } + + /** + * Evaluates 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 { + return getValueRef(state).getValue(); + } + + @Override + public void setValue(ExpressionState state, Object value) throws EvaluationException { + getValueRef(state).setValue(value); + } + + @Override + public boolean isWritable(ExpressionState state) throws EvaluationException { + return getValueRef(state).isWritable(); + } + + @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(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/ConstructorReference.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/ConstructorReference.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/ConstructorReference.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,407 @@ +/* + * Copyright 2002-2013 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. + * + *

+ * Examples:
+ * new String('hello world')
+ * new int[]{1,2,3,4}
+ * 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 argumentTypes = new ArrayList(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 argumentTypes, ExpressionState state) + throws SpelEvaluationException { + + EvaluationContext eContext = state.getEvaluationContext(); + List 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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Elvis.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Elvis.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Elvis.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2013 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 = this.children[0].getValueInternal(state); + if ((value.getValue() != null) && !((value.getValue() instanceof String) && + ((String) value.getValue()).length() == 0)) { + return value; + } + else { + return this.children[1].getValueInternal(state); + } + } + + @Override + public String toStringAST() { + return new StringBuilder().append(getChild(0).toStringAST()).append(" ?: ").append( + getChild(1).toStringAST()).toString(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/FloatLiteral.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/FloatLiteral.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/FloatLiteral.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2013 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 float literal. + * + * @author Satyapal Reddy + * @since 3.2 + */ +public class FloatLiteral extends Literal { + + private final TypedValue value; + + FloatLiteral(String payload, int pos, float value) { + super(payload, pos); + this.value = new TypedValue(value); + } + + + @Override + public TypedValue getLiteralValue() { + return this.value; + } +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/FormatHelper.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/FormatHelper.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/FormatHelper.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2013 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 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(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/FunctionReference.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/FunctionReference.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/FunctionReference.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,150 @@ +/* + * Copyright 2002-2013 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. + * + *

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); + this.name = functionName; + } + + + @Override + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue o = state.lookupVariable(this.name); + if (o == null) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, this.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, this.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(), this.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(this.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] = this.children[i].getValueInternal(state).getValue(); + } + return arguments; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Identifier.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Identifier.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Identifier.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2013 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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Indexer.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Indexer.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Indexer.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,590 @@ +/* + * Copyright 2002-2014 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.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.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 + * @author Phillip Webb + * @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 { + return getValueRef(state).getValue(); + } + + @Override + public void setValue(ExpressionState state, Object newValue) throws EvaluationException { + getValueRef(state).setValue(newValue); + } + + @Override + public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException { + return true; + } + + + @Override + protected ValueRef getValueRef(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 && (this.children[0] instanceof PropertyOrFieldReference)) { + PropertyOrFieldReference reference = (PropertyOrFieldReference) this.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 = this.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()); + } + return new MapIndexingValueRef(state.getTypeConverter(), (Map) targetObject, key, + targetObjectTypeDescriptor); + } + + 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.getClass().isArray() || targetObject instanceof Collection || targetObject instanceof String) { + int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class)); + if (targetObject.getClass().isArray()) { + return new ArrayIndexingValueRef(state.getTypeConverter(), targetObject, idx, targetObjectTypeDescriptor); + } + else if (targetObject instanceof Collection) { + return new CollectionIndexingValueRef((Collection) targetObject, idx, targetObjectTypeDescriptor, + state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(), + state.getConfiguration().getMaximumAutoGrowSize()); + } + else if (targetObject instanceof String) { + return new StringIndexingLValue((String) targetObject, idx, targetObjectTypeDescriptor); + } + } + + // 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) { + return new PropertyIndexingValueRef(targetObject, (String) indexValue.getValue(), + state.getEvaluationContext(), targetObjectTypeDescriptor); + } + + throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, + targetObjectTypeDescriptor.toString()); + } + + @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(TypeConverter converter, Object ctx, int idx, Object newValue, + Class arrayComponentType) throws EvaluationException { + + if (arrayComponentType == Integer.TYPE) { + int[] array = (int[]) ctx; + checkAccess(array.length, idx); + array[idx] = (Integer) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Integer.class)); + } + else if (arrayComponentType == Boolean.TYPE) { + boolean[] array = (boolean[]) ctx; + checkAccess(array.length, idx); + array[idx] = (Boolean) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Boolean.class)); + } + else if (arrayComponentType == Character.TYPE) { + char[] array = (char[]) ctx; + checkAccess(array.length, idx); + array[idx] = (Character) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Character.class)); + } + else if (arrayComponentType == Long.TYPE) { + long[] array = (long[]) ctx; + checkAccess(array.length, idx); + array[idx] = (Long) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Long.class)); + } + else if (arrayComponentType == Short.TYPE) { + short[] array = (short[]) ctx; + checkAccess(array.length, idx); + array[idx] = (Short) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Short.class)); + } + else if (arrayComponentType == Double.TYPE) { + double[] array = (double[]) ctx; + checkAccess(array.length, idx); + array[idx] = (Double) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Double.class)); + } + else if (arrayComponentType == Float.TYPE) { + float[] array = (float[]) ctx; + checkAccess(array.length, idx); + array[idx] = (Float) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Float.class)); + } + else if (arrayComponentType == Byte.TYPE) { + byte[] array = (byte[]) ctx; + checkAccess(array.length, idx); + array[idx] = (Byte) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Byte.class)); + } + else { + Object[] array = (Object[]) ctx; + checkAccess(array.length, idx); + array[idx] = converter.convertValue(newValue, TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(arrayComponentType)); + } + } + + 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); + } + } + + + private class ArrayIndexingValueRef implements ValueRef { + + private final TypeConverter typeConverter; + + private final Object array; + + private final int index; + + private final TypeDescriptor typeDescriptor; + + + ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int index, TypeDescriptor typeDescriptor) { + this.typeConverter = typeConverter; + this.array = array; + this.index = index; + this.typeDescriptor = typeDescriptor; + } + + + @Override + public TypedValue getValue() { + Object arrayElement = accessArrayElement(this.array, this.index); + return new TypedValue(arrayElement, this.typeDescriptor.elementTypeDescriptor(arrayElement)); + } + + @Override + public void setValue(Object newValue) { + setArrayElement(this.typeConverter, this.array, this.index, newValue, + this.typeDescriptor.getElementTypeDescriptor().getType()); + } + + @Override + public boolean isWritable() { + return true; + } + } + + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static class MapIndexingValueRef implements ValueRef { + + private final TypeConverter typeConverter; + + private final Map map; + + private final Object key; + + private final TypeDescriptor mapEntryTypeDescriptor; + + public MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, TypeDescriptor mapEntryTypeDescriptor) { + this.typeConverter = typeConverter; + this.map = map; + this.key = key; + this.mapEntryTypeDescriptor = mapEntryTypeDescriptor; + } + + @Override + public TypedValue getValue() { + Object value = this.map.get(this.key); + return new TypedValue(value, + this.mapEntryTypeDescriptor.getMapValueTypeDescriptor(value)); + } + + @Override + public void setValue(Object newValue) { + if (this.mapEntryTypeDescriptor.getMapValueTypeDescriptor() != null) { + newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue), + this.mapEntryTypeDescriptor.getMapValueTypeDescriptor()); + } + this.map.put(this.key, newValue); + } + + @Override + public boolean isWritable() { + return true; + } + } + + + private class PropertyIndexingValueRef implements ValueRef { + + private final Object targetObject; + + private final String name; + + private final EvaluationContext evaluationContext; + + private final TypeDescriptor targetObjectTypeDescriptor; + + public PropertyIndexingValueRef(Object targetObject, String value, EvaluationContext evaluationContext, + TypeDescriptor targetObjectTypeDescriptor) { + this.targetObject = targetObject; + this.name = value; + this.evaluationContext = evaluationContext; + this.targetObjectTypeDescriptor = targetObjectTypeDescriptor; + } + + + @Override + public TypedValue getValue() { + Class targetObjectRuntimeClass = getObjectClass(this.targetObject); + try { + if (Indexer.this.cachedReadName != null && Indexer.this.cachedReadName.equals(this.name) && + Indexer.this.cachedReadTargetType != null && + Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) { + // It is OK to use the cached accessor + return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name); + } + List accessorsToTry = AstUtils.getPropertyAccessorsToTry( + targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors()); + if (accessorsToTry != null) { + for (PropertyAccessor accessor : accessorsToTry) { + if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) { + if (accessor instanceof ReflectivePropertyAccessor) { + accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor( + this.evaluationContext, this.targetObject, this.name); + } + Indexer.this.cachedReadAccessor = accessor; + Indexer.this.cachedReadName = this.name; + Indexer.this.cachedReadTargetType = targetObjectRuntimeClass; + return accessor.read(this.evaluationContext, this.targetObject, this.name); + } + } + } + } + catch (AccessException ex) { + throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, + this.targetObjectTypeDescriptor.toString()); + } + throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, + this.targetObjectTypeDescriptor.toString()); + } + + @Override + public void setValue(Object newValue) { + Class contextObjectClass = getObjectClass(this.targetObject); + try { + if (Indexer.this.cachedWriteName != null && Indexer.this.cachedWriteName.equals(this.name) && + Indexer.this.cachedWriteTargetType != null && + Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) { + // It is OK to use the cached accessor + Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue); + return; + } + List accessorsToTry = + AstUtils.getPropertyAccessorsToTry(contextObjectClass, this.evaluationContext.getPropertyAccessors()); + if (accessorsToTry != null) { + for (PropertyAccessor accessor : accessorsToTry) { + if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) { + Indexer.this.cachedWriteName = this.name; + Indexer.this.cachedWriteTargetType = contextObjectClass; + Indexer.this.cachedWriteAccessor = accessor; + accessor.write(this.evaluationContext, this.targetObject, this.name, newValue); + return; + } + } + } + } + catch (AccessException ex) { + throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE, + this.name, ex.getMessage()); + } + } + + @Override + public boolean isWritable() { + return true; + } + } + + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private class CollectionIndexingValueRef implements ValueRef { + + private final Collection collection; + + private final int index; + + private final TypeDescriptor collectionEntryDescriptor; + + private final TypeConverter typeConverter; + + private final boolean growCollection; + + private final int maximumSize; + + public CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor, + TypeConverter typeConverter, boolean growCollection, int maximumSize) { + this.collection = collection; + this.index = index; + this.collectionEntryDescriptor = collectionEntryTypeDescriptor; + this.typeConverter = typeConverter; + this.growCollection = growCollection; + this.maximumSize = maximumSize; + } + + + @Override + public TypedValue getValue() { + growCollectionIfNecessary(); + if (this.collection instanceof List) { + Object o = ((List) this.collection).get(this.index); + return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o)); + } + int pos = 0; + for (Object o : this.collection) { + if (pos == this.index) { + return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o)); + } + pos++; + } + throw new IllegalStateException("Failed to find indexed element " + this.index + ": " + this.collection); + } + + @Override + public void setValue(Object newValue) { + growCollectionIfNecessary(); + if (this.collection instanceof List) { + List list = (List) this.collection; + if (this.collectionEntryDescriptor.getElementTypeDescriptor() != null) { + newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue), + this.collectionEntryDescriptor.getElementTypeDescriptor()); + } + list.set(this.index, newValue); + } + else { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, + this.collectionEntryDescriptor.toString()); + } + } + + private void growCollectionIfNecessary() { + if (this.index >= this.collection.size()) { + if (!this.growCollection) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, + this.collection.size(), this.index); + } + if(this.index >= this.maximumSize) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION); + } + if (this.collectionEntryDescriptor.getElementTypeDescriptor() == null) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); + } + TypeDescriptor elementType = this.collectionEntryDescriptor.getElementTypeDescriptor(); + try { + int newElements = this.index - this.collection.size(); + while (newElements >= 0) { + (this.collection).add(elementType.getType().newInstance()); + newElements--; + } + } + catch (Exception ex) { + throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION); + } + } + } + + @Override + public boolean isWritable() { + return true; + } + } + + + private class StringIndexingLValue implements ValueRef { + + private final String target; + + private final int index; + + private final TypeDescriptor typeDescriptor; + + public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) { + this.target = target; + this.index = index; + this.typeDescriptor = typeDescriptor; + } + + @Override + public TypedValue getValue() { + if (this.index >= this.target.length()) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, + this.target.length(), this.index); + } + return new TypedValue(String.valueOf(this.target.charAt(this.index))); + } + + @Override + public void setValue(Object newValue) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, + this.typeDescriptor.toString()); + } + + @Override + public boolean isWritable() { + return true; + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/InlineList.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/InlineList.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/InlineList.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2013 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 constantList = new ArrayList(); + 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 (this.constant != null) { + return this.constant; + } + else { + List returnValue = new ArrayList(); + 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 this.constant != null; + } + + @SuppressWarnings("unchecked") + private List getConstantValue() { + return (List) this.constant.getValue(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/IntLiteral.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/IntLiteral.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/IntLiteral.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2013 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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Literal.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Literal.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Literal.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2013 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.InternalParseException; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.SpelMessage; +import org.springframework.expression.spel.SpelParseException; + +/** + * Common superclass for nodes representing literals (boolean, string, number, etc). + * + * @author Andy Clement + * @author Juergen Hoeller + */ +public abstract class Literal extends SpelNodeImpl { + + private final String originalValue; + + + public Literal(String originalValue, int pos) { + super(pos); + this.originalValue = originalValue; + } + + + public final String getOriginalValue() { + return this.originalValue; + } + + @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(); + } + + + public abstract TypedValue getLiteralValue(); + + + /** + * 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)); + } + } + + public static Literal getRealLiteral(String numberToken, int pos, boolean isFloat) { + try { + if (isFloat) { + float value = Float.parseFloat(numberToken); + return new FloatLiteral(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)); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/LongLiteral.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/LongLiteral.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/LongLiteral.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2013 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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/MethodReference.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/MethodReference.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/MethodReference.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,301 @@ +/* + * Copyright 2002-2013 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.Collections; +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; +import org.springframework.expression.spel.support.ReflectiveMethodResolver; + +/** + * Expression language AST node that represents a method reference. + * + * @author Andy Clement + * @author Juergen Hoeller + * @since 3.0 + */ +public class MethodReference extends SpelNodeImpl { + + private final String name; + + private final boolean nullSafe; + + private volatile CachedMethodExecutor cachedExecutor; + + + public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) { + super(pos, arguments); + this.name = methodName; + this.nullSafe = nullSafe; + } + + + public final String getName() { + return this.name; + } + + @Override + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { + Object[] arguments = getArguments(state); + if (state.getActiveContextObject().getValue() == null) { + throwIfNotNullSafe(getArgumentTypes(arguments)); + return ValueRef.NullValueRef.instance; + } + return new MethodValueRef(state, arguments); + } + + @Override + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + EvaluationContext evaluationContext = state.getEvaluationContext(); + Object value = state.getActiveContextObject().getValue(); + TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor(); + Object[] arguments = getArguments(state); + return getValueInternal(evaluationContext, value, targetType, arguments); + } + + private TypedValue getValueInternal(EvaluationContext evaluationContext, + Object value, TypeDescriptor targetType, Object[] arguments) { + + List argumentTypes = getArgumentTypes(arguments); + if (value == null) { + throwIfNotNullSafe(argumentTypes); + return TypedValue.NULL; + } + + MethodExecutor executorToUse = getCachedExecutor(evaluationContext, value, targetType, argumentTypes); + if (executorToUse != null) { + try { + return executorToUse.execute(evaluationContext, value, 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 the situation, 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(value, 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, argumentTypes, value, evaluationContext); + this.cachedExecutor = new CachedMethodExecutor( + executorToUse, (value instanceof Class ? (Class) value : null), targetType, argumentTypes); + try { + return executorToUse.execute(evaluationContext, value, arguments); + } + catch (AccessException ex) { + // Same unwrapping exception handling as above in above catch block + throwSimpleExceptionIfPossible(value, ex); + throw new SpelEvaluationException(getStartPosition(), ex, + SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, this.name, + value.getClass().getName(), ex.getMessage()); + } + } + + private void throwIfNotNullSafe(List argumentTypes) { + if (!this.nullSafe) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, + FormatHelper.formatMethodForMessage(this.name, argumentTypes)); + } + } + + private Object[] getArguments(ExpressionState state) { + 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] = this.children[i].getValueInternal(state).getValue(); + } + finally { + state.popActiveContextObject(); + } + } + return arguments; + } + + private List getArgumentTypes(Object... arguments) { + List descriptors = new ArrayList(arguments.length); + for (Object argument : arguments) { + descriptors.add(TypeDescriptor.forObject(argument)); + } + return Collections.unmodifiableList(descriptors); + } + + private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value, + TypeDescriptor target, List argumentTypes) { + + List methodResolvers = evaluationContext.getMethodResolvers(); + if (methodResolvers == null || methodResolvers.size() != 1 || + !(methodResolvers.get(0) instanceof ReflectiveMethodResolver)) { + // Not a default ReflectiveMethodResolver - don't know whether caching is valid + return null; + } + + CachedMethodExecutor executorToCheck = this.cachedExecutor; + if (executorToCheck != null && executorToCheck.isSuitable(value, target, argumentTypes)) { + return executorToCheck.get(); + } + this.cachedExecutor = null; + return null; + } + + private MethodExecutor findAccessorForMethod(String name, List argumentTypes, + Object targetObject, EvaluationContext evaluationContext) throws SpelEvaluationException { + + List methodResolvers = evaluationContext.getMethodResolvers(); + if (methodResolvers != null) { + for (MethodResolver methodResolver : methodResolvers) { + try { + MethodExecutor methodExecutor = methodResolver.resolve( + evaluationContext, targetObject, name, argumentTypes); + if (methodExecutor != null) { + return methodExecutor; + } + } + catch (AccessException ex) { + throw new SpelEvaluationException(getStartPosition(), ex, + SpelMessage.PROBLEM_LOCATING_METHOD, name, targetObject.getClass()); + } + } + } + + throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_NOT_FOUND, + FormatHelper.formatMethodForMessage(name, argumentTypes), + FormatHelper.formatClassNameForMessage( + targetObject instanceof Class ? ((Class) targetObject) : targetObject.getClass())); + } + + /** + * Decode the AccessException, throwing a lightweight evaluation exception or, if the + * cause was a RuntimeException, throw the RuntimeException directly. + */ + private void throwSimpleExceptionIfPossible(Object value, AccessException ae) { + if (ae.getCause() instanceof InvocationTargetException) { + Throwable rootCause = ae.getCause().getCause(); + if (rootCause instanceof RuntimeException) { + throw (RuntimeException) rootCause; + } + throw new ExpressionInvocationTargetException(getStartPosition(), + "A problem occurred when trying to execute method '" + this.name + + "' on object of type [" + value.getClass().getName() + "]", rootCause); + } + } + + @Override + public String toStringAST() { + StringBuilder sb = new StringBuilder(); + sb.append(this.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 class MethodValueRef implements ValueRef { + + private final EvaluationContext evaluationContext; + + private final Object value; + + private final TypeDescriptor targetType; + + private final Object[] arguments; + + public MethodValueRef(ExpressionState state, Object[] arguments) { + this.evaluationContext = state.getEvaluationContext(); + this.value = state.getActiveContextObject().getValue(); + this.targetType = state.getActiveContextObject().getTypeDescriptor(); + this.arguments = arguments; + } + + @Override + public TypedValue getValue() { + return getValueInternal(this.evaluationContext, this.value, this.targetType, this.arguments); + } + + @Override + public void setValue(Object newValue) { + throw new IllegalAccessError(); + } + + @Override + public boolean isWritable() { + return false; + } + } + + + private static class CachedMethodExecutor { + + private final MethodExecutor methodExecutor; + + private final Class staticClass; + + private final TypeDescriptor target; + + private final List argumentTypes; + + public CachedMethodExecutor(MethodExecutor methodExecutor, Class staticClass, + TypeDescriptor target, List argumentTypes) { + this.methodExecutor = methodExecutor; + this.staticClass = staticClass; + this.target = target; + this.argumentTypes = argumentTypes; + } + + public boolean isSuitable(Object value, TypeDescriptor target, List argumentTypes) { + return ((this.staticClass == null || this.staticClass.equals(value)) && + this.target.equals(target) && this.argumentTypes.equals(argumentTypes)); + } + + public MethodExecutor get() { + return this.methodExecutor; + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/NullLiteral.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/NullLiteral.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/NullLiteral.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2013 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 null. + * + * @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"; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpAnd.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpAnd.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpAnd.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2013 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; + +/** + * Represents the boolean AND operation. + * + * @author Andy Clement + * @author Mark Fisher + * @author Oliver Becker + * @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 { + if (getBooleanValue(state, getLeftOperand()) == false) { + // no need to evaluate right operand + return BooleanTypedValue.FALSE; + } + return BooleanTypedValue.forValue(getBooleanValue(state, getRightOperand())); + } + + private boolean getBooleanValue(ExpressionState state, SpelNodeImpl operand) { + try { + Boolean value = operand.getValue(state, Boolean.class); + assertValueNotNull(value); + return value; + } + catch (SpelEvaluationException ex) { + ex.setPosition(operand.getStartPosition()); + throw ex; + } + } + + private void assertValueNotNull(Boolean value) { + if (value == null) { + throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean"); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpDec.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpDec.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpDec.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +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.Assert; + +/** + * Decrement operator. Can be used in a prefix or postfix form. This will throw + * appropriate exceptions if the operand in question does not support decrement. + * + * @author Andy Clement + * @author Giovanni Dall'Oglio Risso + * @since 3.2 + */ +public class OpDec extends Operator { + + private final boolean postfix; // false means prefix + + + public OpDec(int pos, boolean postfix, SpelNodeImpl... operands) { + super("--", pos, operands); + Assert.notEmpty(operands); + this.postfix = postfix; + } + + + @Override + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + SpelNodeImpl operand = getLeftOperand(); + + // The operand is going to be read and then assigned to, we don't want to evaluate it twice. + + ValueRef lvalue = operand.getValueRef(state); + + final TypedValue operandTypedValue = lvalue.getValue();//operand.getValueInternal(state); + final Object operandValue = operandTypedValue.getValue(); + TypedValue returnValue = operandTypedValue; + TypedValue newValue = null; + + if (operandValue instanceof Number) { + Number op1 = (Number) operandValue; + if (op1 instanceof BigDecimal) { + newValue = new TypedValue(((BigDecimal) op1).subtract(BigDecimal.ONE), + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Double) { + newValue = new TypedValue(op1.doubleValue() - 1.0d, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Float) { + newValue = new TypedValue(op1.floatValue() - 1.0f, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Long) { + newValue = new TypedValue(op1.longValue() - 1L, + operandTypedValue.getTypeDescriptor()); + } + else if (op1 instanceof Short) { + newValue = new TypedValue(op1.shortValue() - (short) 1, + operandTypedValue.getTypeDescriptor()); + } + else { + newValue = new TypedValue(op1.intValue() - 1, + operandTypedValue.getTypeDescriptor()); + } + } + if (newValue == null) { + try { + newValue = state.operate(Operation.SUBTRACT, returnValue.getValue(), 1); + } + catch (SpelEvaluationException ex) { + if (ex.getMessageCode() == SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES) { + // This means the operand is not decrementable + throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_DECREMENTABLE,operand.toStringAST()); + } + else { + throw ex; + } + } + } + + // set the new value + try { + lvalue.setValue(newValue.getValue()); + } + catch (SpelEvaluationException see) { + // if unable to set the value the operand is not writable (e.g. 1-- ) + if (see.getMessageCode() == SpelMessage.SETVALUE_NOT_SUPPORTED) { + throw new SpelEvaluationException(operand.getStartPosition(), + SpelMessage.OPERAND_NOT_DECREMENTABLE); + } + else { + throw see; + } + } + + if (!this.postfix) { + // the return value is the new value, not the original value + returnValue = newValue; + } + + return returnValue; + } + + @Override + public String toStringAST() { + return new StringBuilder().append(getLeftOperand().toStringAST()).append("--").toString(); + } + + @Override + public SpelNodeImpl getRightOperand() { + return null; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpDivide.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpDivide.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpDivide.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; +import java.math.RoundingMode; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.util.NumberUtils; + +/** + * Implements division operator. + * + * @author Andy Clement + * @author Juergen Hoeller + * @author Giovanni Dall'Oglio Risso + * @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 leftOperand = getLeftOperand().getValueInternal(state).getValue(); + Object rightOperand = getRightOperand().getValueInternal(state).getValue(); + + if (leftOperand instanceof Number && rightOperand instanceof Number) { + Number leftNumber = (Number) leftOperand; + Number rightNumber = (Number) rightOperand; + + if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + int scale = Math.max(leftBigDecimal.scale(), rightBigDecimal.scale()); + return new TypedValue(leftBigDecimal.divide(rightBigDecimal, scale, RoundingMode.HALF_EVEN)); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return new TypedValue(leftNumber.doubleValue() / rightNumber.doubleValue()); + } + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return new TypedValue(leftNumber.floatValue() / rightNumber.floatValue()); + } + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return new TypedValue(leftNumber.longValue() / rightNumber.longValue()); + } + + // TODO what about non-int result of the division? + return new TypedValue(leftNumber.intValue() / rightNumber.intValue()); + } + + return state.operate(Operation.DIVIDE, leftOperand, rightOperand); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpEQ.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpEQ.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpEQ.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2014 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 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(); + return BooleanTypedValue.forValue(equalityCheck(state, left, right)); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpGE.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpGE.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpGE.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; +import org.springframework.util.NumberUtils; + +/** + * Implements greater-than-or-equal operator. + * + * @author Andy Clement + * @author Giovanni Dall'Oglio Risso + * @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 BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return BooleanTypedValue.forValue(leftBigDecimal.compareTo(rightBigDecimal) >= 0); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return BooleanTypedValue.forValue(leftNumber.doubleValue() >= rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return BooleanTypedValue.forValue(leftNumber.floatValue() >= rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return BooleanTypedValue.forValue(leftNumber.longValue() >= rightNumber.longValue()); + } + return BooleanTypedValue.forValue(leftNumber.intValue() >= rightNumber.intValue()); + } + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) >= 0); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpGT.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpGT.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpGT.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2014 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; +import org.springframework.util.NumberUtils; + +/** + * Implements the greater-than operator. + * + * @author Andy Clement + * @author Juergen Hoeller + * @author Giovanni Dall'Oglio Risso + * @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 BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return BooleanTypedValue.forValue(leftBigDecimal.compareTo(rightBigDecimal) > 0); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return BooleanTypedValue.forValue(leftNumber.doubleValue() > rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return BooleanTypedValue.forValue(leftNumber.floatValue() > rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return BooleanTypedValue.forValue(leftNumber.longValue() > rightNumber.longValue()); + } + + return BooleanTypedValue.forValue(leftNumber.intValue() > rightNumber.intValue()); + } + + if (left instanceof CharSequence && right instanceof CharSequence) { + left = left.toString(); + right = right.toString(); + } + + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) > 0); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpInc.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpInc.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpInc.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +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.Assert; + +/** + * Increment operator. Can be used in a prefix or postfix form. This will throw + * appropriate exceptions if the operand in question does not support increment. + * + * @author Andy Clement + * @author Giovanni Dall'Oglio Risso + * @since 3.2 + */ +public class OpInc extends Operator { + + private final boolean postfix; // false means prefix + + + public OpInc(int pos, boolean postfix, SpelNodeImpl... operands) { + super("++", pos, operands); + Assert.notEmpty(operands); + this.postfix = postfix; + } + + + @Override + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + SpelNodeImpl operand = getLeftOperand(); + + ValueRef valueRef = operand.getValueRef(state); + + final TypedValue typedValue = valueRef.getValue(); + final Object value = typedValue.getValue(); + TypedValue returnValue = typedValue; + TypedValue newValue = null; + + if (value instanceof Number) { + Number op1 = (Number) value; + if (op1 instanceof BigDecimal) { + newValue = new TypedValue(((BigDecimal) op1).add(BigDecimal.ONE), + typedValue.getTypeDescriptor()); + } + else if (op1 instanceof Double) { + newValue = new TypedValue(op1.doubleValue() + 1.0d, + typedValue.getTypeDescriptor()); + } + else if (op1 instanceof Float) { + newValue = new TypedValue(op1.floatValue() + 1.0f, + typedValue.getTypeDescriptor()); + } + else if (op1 instanceof Long) { + newValue = new TypedValue(op1.longValue() + 1L, + typedValue.getTypeDescriptor()); + } + else if (op1 instanceof Short) { + newValue = new TypedValue(op1.shortValue() + (short) 1, + typedValue.getTypeDescriptor()); + } + else { + newValue = new TypedValue(op1.intValue() + 1, + typedValue.getTypeDescriptor()); + } + } + if (newValue == null) { + try { + newValue = state.operate(Operation.ADD, returnValue.getValue(), 1); + } + catch (SpelEvaluationException ex) { + if (ex.getMessageCode() == SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES) { + // This means the operand is not incrementable + throw new SpelEvaluationException(operand.getStartPosition(), + SpelMessage.OPERAND_NOT_INCREMENTABLE, operand.toStringAST()); + } + throw ex; + } + } + + // set the name value + try { + valueRef.setValue(newValue.getValue()); + } + catch (SpelEvaluationException see) { + // if unable to set the value the operand is not writable (e.g. 1++ ) + if (see.getMessageCode() == SpelMessage.SETVALUE_NOT_SUPPORTED) { + throw new SpelEvaluationException(operand.getStartPosition(), + SpelMessage.OPERAND_NOT_INCREMENTABLE); + } + else { + throw see; + } + } + + if (!this.postfix) { + // the return value is the new value, not the original value + returnValue = newValue; + } + + return returnValue; + } + + @Override + public String toStringAST() { + return new StringBuilder().append(getLeftOperand().toStringAST()).append("++").toString(); + } + + @Override + public SpelNodeImpl getRightOperand() { + return null; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpLE.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpLE.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpLE.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; +import org.springframework.util.NumberUtils; + +/** + * Implements the less-than-or-equal operator. + * + * @author Andy Clement + * @author Giovanni Dall'Oglio Risso + * @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 BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return BooleanTypedValue.forValue(leftBigDecimal.compareTo(rightBigDecimal) <= 0); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return BooleanTypedValue.forValue(leftNumber.doubleValue() <= rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return BooleanTypedValue.forValue(leftNumber.floatValue() <= rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return BooleanTypedValue.forValue(leftNumber.longValue() <= rightNumber.longValue()); + } + + return BooleanTypedValue.forValue(leftNumber.intValue() <= rightNumber.intValue()); + } + + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) <= 0); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpLT.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpLT.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpLT.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2014 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; +import org.springframework.util.NumberUtils; + +/** + * Implements the less-than operator. + * + * @author Andy Clement + * @author Juergen Hoeller + * @author Giovanni Dall'Oglio Risso + * @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(); + + if (left instanceof Number && right instanceof Number) { + Number leftNumber = (Number) left; + Number rightNumber = (Number) right; + + if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return BooleanTypedValue.forValue(leftBigDecimal.compareTo(rightBigDecimal) < 0); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return BooleanTypedValue.forValue(leftNumber.doubleValue() < rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return BooleanTypedValue.forValue(leftNumber.floatValue() < rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return BooleanTypedValue.forValue(leftNumber.longValue() < rightNumber.longValue()); + } + + return BooleanTypedValue.forValue(leftNumber.intValue() < rightNumber.intValue()); + } + + if (left instanceof CharSequence && right instanceof CharSequence) { + left = left.toString(); + right = right.toString(); + } + + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) < 0); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpMinus.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpMinus.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpMinus.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.util.NumberUtils; + +/** + * The minus operator supports: + *
    + *
  • subtraction of {@code BigDecimal} + *
  • subtraction of doubles (floats are represented as doubles) + *
  • subtraction of longs + *
  • subtraction of integers + *
  • subtraction of an int from a string of one character (effectively decreasing that + * character), so 'd'-3='a' + *
+ * It can be used as a unary operator for numbers ({@code BigDecimal}/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 + * @author Giovanni Dall'Oglio Risso + * @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 BigDecimal) { + BigDecimal bdn = (BigDecimal) n; + return new TypedValue(bdn.negate()); + } + + if (operand instanceof Double) { + return new TypedValue(0 - n.doubleValue()); + } + + if (operand instanceof Float) { + return new TypedValue(0 - n.floatValue()); + } + + if (operand instanceof Long) { + return new TypedValue(0 - n.longValue()); + } + + return new TypedValue(0 - n.intValue()); + } + + return state.operate(Operation.SUBTRACT, operand, null); + } + + Object left = leftOp.getValueInternal(state).getValue(); + Object right = rightOp.getValueInternal(state).getValue(); + + if (left instanceof Number && right instanceof Number) { + Number leftNumber = (Number) left; + Number rightNumber = (Number) right; + + if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return new TypedValue(leftBigDecimal.subtract(rightBigDecimal)); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return new TypedValue(leftNumber.doubleValue() - rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return new TypedValue(leftNumber.floatValue() - rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return new TypedValue(leftNumber.longValue() - rightNumber.longValue()); + } + + return new TypedValue(leftNumber.intValue() - rightNumber.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(); + } + @Override + public SpelNodeImpl getRightOperand() { + if (this.children.length<2) {return null;} + return this.children[1]; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpModulus.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpModulus.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpModulus.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.util.NumberUtils; + +/** + * Implements the modulus operator. + * + * @author Andy Clement + * @author Giovanni Dall'Oglio Risso + * @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 leftOperand = getLeftOperand().getValueInternal(state).getValue(); + Object rightOperand = getRightOperand().getValueInternal(state).getValue(); + if (leftOperand instanceof Number && rightOperand instanceof Number) { + Number leftNumber = (Number) leftOperand; + Number rightNumber = (Number) rightOperand; + + if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return new TypedValue(leftBigDecimal.remainder(rightBigDecimal)); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return new TypedValue(leftNumber.doubleValue() % rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return new TypedValue(leftNumber.floatValue() % rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return new TypedValue(leftNumber.longValue() % rightNumber.longValue()); + } + + return new TypedValue(leftNumber.intValue() % rightNumber.intValue()); + } + + return state.operate(Operation.MODULUS, leftOperand, rightOperand); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpMultiply.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpMultiply.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpMultiply.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.util.NumberUtils; + +/** + * Implements the {@code multiply} operator. + * + *

Conversions and promotions are handled as defined in + * Section + * 5.6.2 of the Java Language Specification, with the addiction of {@code BigDecimal} management: + * + *

If any of the operands is of a reference type, unboxing conversion (Section 5.1.8) + * is performed. Then:
+ * If either operand is of type {@code BigDecimal}, the other is converted to {@code BigDecimal}.
+ * If either operand is of type double, the other is converted to double.
+ * Otherwise, if either operand is of type float, the other is converted to float.
+ * Otherwise, if either operand is of type long, the other is converted to long.
+ * Otherwise, both operands are converted to type int. + * + * @author Andy Clement + * @author Sam Brannen + * @author Giovanni Dall'Oglio Risso + * @since 3.0 + */ +public class OpMultiply extends Operator { + + + public OpMultiply(int pos, SpelNodeImpl... operands) { + super("*", pos, operands); + } + + + /** + * Implements the {@code multiply} operator directly here for certain types + * of supported operands and otherwise delegates to any registered overloader + * for types not supported here. + *

Supported operand types: + *

    + *
  • {@code BigDecimal} + *
  • doubles + *
  • longs + *
  • integers + *
  • String and int ('abc' * 2 == 'abcabc') + *
+ */ + @Override + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + + Object leftOperand = getLeftOperand().getValueInternal(state).getValue(); + Object rightOperand = getRightOperand().getValueInternal(state).getValue(); + + if (leftOperand instanceof Number && rightOperand instanceof Number) { + Number leftNumber = (Number) leftOperand; + Number rightNumber = (Number) rightOperand; + if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return new TypedValue(leftBigDecimal.multiply(rightBigDecimal)); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return new TypedValue(leftNumber.floatValue() * rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return new TypedValue(leftNumber.longValue() * rightNumber.longValue()); + } + + return new TypedValue(leftNumber.intValue() * rightNumber.intValue()); + } + else if (leftOperand instanceof String && rightOperand instanceof Integer) { + int repeats = (Integer) rightOperand; + StringBuilder result = new StringBuilder(); + for (int i = 0; i < repeats; i++) { + result.append(leftOperand); + } + return new TypedValue(result.toString()); + } + + return state.operate(Operation.MULTIPLY, leftOperand, rightOperand); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpNE.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpNE.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpNE.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2014 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(); + return BooleanTypedValue.forValue(!equalityCheck(state, left, right)); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpOr.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpOr.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpOr.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2013 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.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 + * @author Oliver Becker + * @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 { + if (getBooleanValue(state, getLeftOperand())) { + // no need to evaluate right operand + return BooleanTypedValue.TRUE; + } + return BooleanTypedValue.forValue(getBooleanValue(state, getRightOperand())); + } + + private boolean getBooleanValue(ExpressionState state, SpelNodeImpl operand) { + try { + Boolean value = operand.getValue(state, Boolean.class); + assertValueNotNull(value); + return value; + } + catch (SpelEvaluationException ee) { + ee.setPosition(operand.getStartPosition()); + throw ee; + } + } + + private void assertValueNotNull(Boolean value) { + if (value == null) { + throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean"); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpPlus.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpPlus.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OpPlus.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +import org.springframework.expression.TypeConverter; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.util.Assert; +import org.springframework.util.NumberUtils; + +/** + * The plus operator will: + *
    + *
  • add {@code BigDecimal} + *
  • add doubles (floats are represented as doubles) + *
  • add longs + *
  • add integers + *
  • concatenate strings + *
+ * It can be used as a unary operator for numbers ({@code BigDecimal}/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 + * @author Ivo Smid + * @author Giovanni Dall'Oglio Risso + * @since 3.0 + */ +public class OpPlus extends Operator { + + + public OpPlus(int pos, SpelNodeImpl... operands) { + super("+", pos, operands); + Assert.notEmpty(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 || operandOne instanceof Long || operandOne instanceof BigDecimal) { + return new TypedValue(operandOne); + } + if (operandOne instanceof Float) { + return new TypedValue(((Number) operandOne).floatValue()); + } + return new TypedValue(((Number) operandOne).intValue()); + } + return state.operate(Operation.ADD, operandOne, null); + } + + final TypedValue operandOneValue = leftOp.getValueInternal(state); + final Object leftOperand = operandOneValue.getValue(); + + final TypedValue operandTwoValue = rightOp.getValueInternal(state); + final Object rightOperand = operandTwoValue.getValue(); + + if (leftOperand instanceof Number && rightOperand instanceof Number) { + Number leftNumber = (Number) leftOperand; + Number rightNumber = (Number) rightOperand; + + if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return new TypedValue(leftBigDecimal.add(rightBigDecimal)); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return new TypedValue(leftNumber.doubleValue() + rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return new TypedValue(leftNumber.floatValue() + rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return new TypedValue(leftNumber.longValue() + rightNumber.longValue()); + } + + // TODO what about overflow? + return new TypedValue(leftNumber.intValue() + rightNumber.intValue()); + } + + if (leftOperand instanceof String && rightOperand instanceof String) { + return new TypedValue(new StringBuilder((String) leftOperand).append( + (String) rightOperand).toString()); + } + + if (leftOperand instanceof String) { + StringBuilder result = new StringBuilder((String) leftOperand); + result.append((rightOperand == null ? "null" : convertTypedValueToString( + operandTwoValue, state))); + return new TypedValue(result.toString()); + } + + if (rightOperand instanceof String) { + StringBuilder result = new StringBuilder((leftOperand == null ? "null" + : convertTypedValueToString(operandOneValue, state))); + result.append((String) rightOperand); + return new TypedValue(result.toString()); + } + + return state.operate(Operation.ADD, leftOperand, rightOperand); + } + + @Override + public String toStringAST() { + if (this.children.length<2) { // unary plus + return new StringBuilder().append("+").append(getLeftOperand().toStringAST()).toString(); + } + return super.toStringAST(); + } + + @Override + public SpelNodeImpl getRightOperand() { + if (this.children.length < 2) { + return null; + } + return this.children[1]; + } + + /** + * Convert operand value to string using registered converter or using + * {@code toString} method. + * + * @param value typed value to be converted + * @param state expression state + * @return {@code TypedValue} instance converted to {@code String} + */ + private static String convertTypedValueToString(TypedValue value, ExpressionState state) { + final TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter(); + final TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(String.class); + + if (typeConverter.canConvert(value.getTypeDescriptor(), typeDescriptor)) { + final Object obj = typeConverter.convertValue(value.getValue(), + value.getTypeDescriptor(), typeDescriptor); + return String.valueOf(obj); + } + + return String.valueOf(value.getValue()); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Operator.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Operator.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Operator.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2014 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.math.BigDecimal; + +import org.springframework.expression.spel.ExpressionState; +import org.springframework.util.ClassUtils; +import org.springframework.util.NumberUtils; +import org.springframework.util.ObjectUtils; + +/** + * 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 + * @author Juergen Hoeller + * @author Giovanni Dall'Oglio Risso + * @since 3.0 + */ +public abstract class Operator extends SpelNodeImpl { + + private final String operatorName; + + + public Operator(String payload,int pos,SpelNodeImpl... operands) { + super(pos, operands); + this.operatorName = payload; + } + + + public SpelNodeImpl getLeftOperand() { + return this.children[0]; + } + + public SpelNodeImpl getRightOperand() { + return this.children[1]; + } + + public final String getOperatorName() { + return this.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(); + } + + + protected boolean equalityCheck(ExpressionState state, Object left, Object right) { + if (left instanceof Number && right instanceof Number) { + Number leftNumber = (Number) left; + Number rightNumber = (Number) right; + + if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return (leftBigDecimal == null ? rightBigDecimal == null : leftBigDecimal.compareTo(rightBigDecimal) == 0); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return (leftNumber.doubleValue() == rightNumber.doubleValue()); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return (leftNumber.floatValue() == rightNumber.floatValue()); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + return (leftNumber.longValue() == rightNumber.longValue()); + } + + return (leftNumber.intValue() == rightNumber.intValue()); + } + + if (left instanceof CharSequence && right instanceof CharSequence) { + return left.toString().equals(right.toString()); + } + + if (ObjectUtils.nullSafeEquals(left, right)) { + return true; + } + + if (left instanceof Comparable && right instanceof Comparable) { + Class ancestor = ClassUtils.determineCommonAncestor(left.getClass(), right.getClass()); + if (ancestor != null && Comparable.class.isAssignableFrom(ancestor)) { + return (state.getTypeComparator().compare(left, right) == 0); + } + } + + return false; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorBetween.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorBetween.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorBetween.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2013 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; + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorInstanceof.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorInstanceof.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorInstanceof.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2013 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} 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())); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorMatches.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorMatches.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorMatches.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2013 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); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorNot.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorNot.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorNot.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2013 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.SpelEvaluationException; +import org.springframework.expression.spel.SpelMessage; +import org.springframework.expression.spel.support.BooleanTypedValue; + +/** + * Represents a NOT operation. + * + * @author Andy Clement + * @author Mark Fisher + * @author Oliver Becker + * @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 { + Boolean value = this.children[0].getValue(state, Boolean.class); + if (value == null) { + throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean"); + } + return BooleanTypedValue.forValue(!value); + } + catch (SpelEvaluationException ex) { + ex.setPosition(getChild(0).getStartPosition()); + throw ex; + } + } + + @Override + public String toStringAST() { + StringBuilder sb = new StringBuilder(); + sb.append("!").append(getChild(0).toStringAST()); + return sb.toString(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorPower.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorPower.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/OperatorPower.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.util.NumberUtils; + +/** + * The power operator. + * + * @author Andy Clement + * @author Giovanni Dall'Oglio Risso + * @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 leftOperand = leftOp.getValueInternal(state).getValue(); + Object rightOperand = rightOp.getValueInternal(state).getValue(); + + if (leftOperand instanceof Number && rightOperand instanceof Number) { + Number leftNumber = (Number) leftOperand; + Number rightNumber = (Number) rightOperand; + + if (leftNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + return new TypedValue(leftBigDecimal.pow(rightNumber.intValue())); + } + + if (leftNumber instanceof Double || rightNumber instanceof Double) { + return new TypedValue(Math.pow(leftNumber.doubleValue(), rightNumber.doubleValue())); + } + + if (leftNumber instanceof Float || rightNumber instanceof Float) { + return new TypedValue(Math.pow(leftNumber.floatValue(), rightNumber.floatValue())); + } + + if (leftNumber instanceof Long || rightNumber instanceof Long) { + double d = Math.pow(leftNumber.longValue(), rightNumber.longValue()); + return new TypedValue((long) d); + } + + double d = Math.pow(leftNumber.longValue(), rightNumber.longValue()); + if (d > Integer.MAX_VALUE) { + return new TypedValue((long) d); + } + else { + return new TypedValue((int) d); + } + } + + return state.operate(Operation.POWER, leftOperand, rightOperand); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Projection.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Projection.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Projection.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,160 @@ +/* + * Copyright 2002-2013 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 { + return getValueRef(state).getValue(); + } + + @Override + protected ValueRef getValueRef(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 result = new ArrayList(); + 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 ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); // TODO unable to build correct type descriptor + } + + if (operand instanceof Collection || operandIsArray) { + Collection data = (operand instanceof Collection ? (Collection) operand : + Arrays.asList(ObjectUtils.toObjectArray(operand))); + List result = new ArrayList(); + int idx = 0; + Class arrayElementType = null; + for (Object element : data) { + try { + state.pushActiveContextObject(new TypedValue(element)); + state.enterScope("index", idx); + Object value = this.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 ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); + } + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); + } + + if (operand==null) { + if (this.nullSafe) { + return ValueRef.NullValueRef.instance; + } + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, "null"); + } + + 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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/PropertyOrFieldReference.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/PropertyOrFieldReference.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/PropertyOrFieldReference.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,355 @@ +/* + * Copyright 2002-2013 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 ValueRef getValueRef(ExpressionState state) throws EvaluationException { + return new AccessorLValue(this, state.getActiveContextObject(), state.getEvaluationContext(), + state.getConfiguration().isAutoGrowNullReferences()); + } + + @Override + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), + state.getConfiguration().isAutoGrowNullReferences()); + } + + private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext, + boolean isAutoGrowNullReferences) throws EvaluationException { + + TypedValue result = readProperty(contextObject, eContext, this.name); + + // Dynamically create the objects if the user has requested that optional behavior + if (result.getValue() == null && 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 (isWritableProperty(this.name,contextObject,eContext)) { + List newList = ArrayList.class.newInstance(); + writeProperty(contextObject, eContext, this.name, newList); + result = readProperty(contextObject, eContext, 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 (isWritableProperty(this.name,contextObject,eContext)) { + Map newMap = HashMap.class.newInstance(); + writeProperty(contextObject, eContext, this.name, newMap); + result = readProperty(contextObject, eContext, 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 (isWritableProperty(this.name,contextObject,eContext)) { + Object newObject = result.getTypeDescriptor().getType().newInstance(); + writeProperty(contextObject, eContext, this.name, newObject); + result = readProperty(contextObject, eContext, 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.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue); + } + + @Override + public boolean isWritable(ExpressionState state) throws SpelEvaluationException { + return isWritableProperty(this.name, state.getActiveContextObject(), state.getEvaluationContext()); + } + + @Override + public String toStringAST() { + return this.name; + } + + /** + * Attempt to read the named property from the current context object. + * @return the value of the property + * @throws SpelEvaluationException if any problem accessing the property or it cannot be found + */ + private TypedValue readProperty(TypedValue contextObject, EvaluationContext eContext, String name) throws EvaluationException { + Object targetObject = contextObject.getValue(); + if (targetObject == null && this.nullSafe) { + return TypedValue.NULL; + } + + PropertyAccessor accessorToUse = this.cachedReadAccessor; + if (accessorToUse != null) { + try { + return accessorToUse.read(eContext, 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; + } + } + + List accessorsToTry = getPropertyAccessorsToTry(contextObject.getValue(), eContext.getPropertyAccessors()); + // 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(getObjectClass(contextObject.getValue()))); + } + } + + private void writeProperty(TypedValue contextObject, EvaluationContext eContext, String name, Object newValue) throws SpelEvaluationException { + if (contextObject.getValue() == null && this.nullSafe) { + return; + } + + PropertyAccessor accessorToUse = this.cachedWriteAccessor; + if (accessorToUse != null) { + try { + accessorToUse.write(eContext, 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; + } + } + + List accessorsToTry = getPropertyAccessorsToTry(contextObject.getValue(), eContext.getPropertyAccessors()); + 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(getObjectClass(contextObject.getValue()))); + } + } + + public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext eContext) throws SpelEvaluationException { + List accessorsToTry = getPropertyAccessorsToTry(contextObject.getValue(), eContext.getPropertyAccessors()); + if (accessorsToTry != null) { + for (PropertyAccessor accessor : accessorsToTry) { + try { + if (accessor.canWrite(eContext, contextObject.getValue(), 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 contextObject the object upon which property access is being attempted + * @return a list of resolvers that should be tried in order to access the property + */ + private List getPropertyAccessorsToTry(Object contextObject, List propertyAccessors) { + Class targetType = (contextObject != null ? contextObject.getClass() : null); + + List specificAccessors = new ArrayList(); + List generalAccessors = new ArrayList(); + for (PropertyAccessor resolver : propertyAccessors) { + 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 resolvers = new ArrayList(); + resolvers.addAll(specificAccessors); + generalAccessors.removeAll(specificAccessors); + resolvers.addAll(generalAccessors); + return resolvers; + } + + + private static class AccessorLValue implements ValueRef { + + private final PropertyOrFieldReference ref; + + private final TypedValue contextObject; + + private final EvaluationContext eContext; + + private final boolean autoGrowNullReferences; + + public AccessorLValue(PropertyOrFieldReference propertyOrFieldReference, TypedValue activeContextObject, + EvaluationContext evaluationContext, boolean autoGrowNullReferences) { + this.ref = propertyOrFieldReference; + this.contextObject = activeContextObject; + this.eContext = evaluationContext; + this.autoGrowNullReferences = autoGrowNullReferences; + } + + @Override + public TypedValue getValue() { + return this.ref.getValueInternal(this.contextObject, this.eContext, this.autoGrowNullReferences); + } + + @Override + public void setValue(Object newValue) { + this.ref.writeProperty(this.contextObject, this.eContext, this.ref.name, newValue); + } + + @Override + public boolean isWritable() { + return true; + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/QualifiedIdentifier.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/QualifiedIdentifier.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/QualifiedIdentifier.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2013 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. + * + *

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 = this.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(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/RealLiteral.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/RealLiteral.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/RealLiteral.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2013 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 real literal. + * + * @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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Selection.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Selection.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Selection.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,199 @@ +/* + * Copyright 2002-2013 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.Assert; +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] + * + *

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 static final int ALL = 0; // ?[] + + public static final int FIRST = 1; // ^[] + + public static final int LAST = 2; // $[] + + private final int variant; + + private final boolean nullSafe; + + + public Selection(boolean nullSafe, int variant, int pos, SpelNodeImpl expression) { + super(pos, expression != null ? new SpelNodeImpl[] { expression } + : new SpelNodeImpl[] {}); + Assert.notNull(expression, "Expression must not be null"); + this.nullSafe = nullSafe; + this.variant = variant; + } + + @Override + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + return getValueRef(state).getValue(); + } + + @Override + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { + TypedValue op = state.getActiveContextObject(); + Object operand = op.getValue(); + + SpelNodeImpl selectionCriteria = this.children[0]; + if (operand instanceof Map) { + Map mapdata = (Map) operand; + // TODO don't lose generic info for the new map + Map result = new HashMap(); + 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 (this.variant == FIRST) { + result.put(entry.getKey(),entry.getValue()); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); + } + 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 ((this.variant == FIRST || this.variant == LAST) && result.size() == 0) { + return new ValueRef.TypedValueHolderValueRef(new TypedValue(null),this); + } + + if (this.variant == LAST) { + Map resultMap = new HashMap(); + Object lastValue = result.get(lastKey); + resultMap.put(lastKey,lastValue); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultMap),this); + } + + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); + } + + if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) { + List data = new ArrayList(); + Collection c = (operand instanceof Collection) ? + (Collection) operand : Arrays.asList(ObjectUtils.toObjectArray(operand)); + data.addAll(c); + List result = new ArrayList(); + 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 (this.variant == FIRST) { + return new ValueRef.TypedValueHolderValueRef(new TypedValue(element),this); + } + 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 ((this.variant == FIRST || this.variant == LAST) && result.size() == 0) { + return ValueRef.NullValueRef.instance; + } + + if (this.variant == LAST) { + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)),this); + } + + if (operand instanceof Collection) { + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); + } + 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 ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); + } + if (operand==null) { + if (this.nullSafe) { + return ValueRef.NullValueRef.instance; + } + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.INVALID_TYPE_FOR_SELECTION, "null"); + } + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.INVALID_TYPE_FOR_SELECTION, operand.getClass().getName()); + } + + @Override + public String toStringAST() { + StringBuilder sb = new StringBuilder(); + switch (this.variant) { + case ALL: + sb.append("?["); + break; + case FIRST: + sb.append("^["); + break; + case LAST: + sb.append("$["); + break; + } + return sb.append(getChild(0).toStringAST()).append("]").toString(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/SpelNodeImpl.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/SpelNodeImpl.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/SpelNodeImpl.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,173 @@ +/* + * Copyright 2002-2014 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 (this.parent != null) { + for (SpelNodeImpl child : this.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 (this.parent != null) { + SpelNodeImpl[] peers = this.parent.children; + for (int i = 0, max = peers.length; i < max; i++) { + if (peers[i] == this) { + if ((i + 1) >= max) { + return false; + } + + Class clazz = peers[i + 1].getClass(); + for (Class desiredClazz : clazzes) { + if (clazz.equals(desiredClazz)) { + return true; + } + } + + return false; + } + } + } + return false; + } + + @Override + 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())); + } + } + + @Override + 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 + @Override + public boolean isWritable(ExpressionState expressionState) throws EvaluationException { + return false; + } + + @Override + public void setValue(ExpressionState expressionState, Object newValue) throws EvaluationException { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.SETVALUE_NOT_SUPPORTED, getClass()); + } + + @Override + public SpelNode getChild(int index) { + return this.children[index]; + } + + @Override + public int getChildCount() { + return this.children.length; + } + + @Override + public Class getObjectClass(Object obj) { + if (obj == null) { + return null; + } + return (obj instanceof Class ? ((Class) obj) : obj.getClass()); + } + + protected final T getValue(ExpressionState state, Class desiredReturnType) throws EvaluationException { + return ExpressionUtils.convertTypedValue(state.getEvaluationContext(), getValueInternal(state), desiredReturnType); + } + + @Override + public int getStartPosition() { + return (this.pos >> 16); + } + + @Override + public int getEndPosition() { + return (this.pos & 0xffff); + } + + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { + throw new SpelEvaluationException(this.pos, SpelMessage.NOT_ASSIGNABLE, toStringAST()); + } + + + public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException; + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/StringLiteral.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/StringLiteral.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/StringLiteral.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2013 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 string literal. + * + * @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("''", "'").replaceAll("\"\"", "\"")); + } + + + @Override + public TypedValue getLiteralValue() { + return this.value; + } + + @Override + public String toString() { + return "'" + getLiteralValue().getValue() + "'"; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/Ternary.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/Ternary.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/Ternary.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2013 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 = this.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 this.children[1].getValueInternal(state); + } + else { + return this.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(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/TypeCode.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/TypeCode.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/TypeCode.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2013 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 this.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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/ast/TypeReference.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/ast/TypeReference.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/ast/TypeReference.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2013 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 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 { + + private final int dimensions; + + + public TypeReference(int pos, SpelNodeImpl qualifiedId) { + this(pos,qualifiedId,0); + } + + public TypeReference(int pos, SpelNodeImpl qualifiedId, int dims) { + super(pos,qualifiedId); + this.dimensions = dims; + } + + + @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) this.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 + Class clazz = tc.getType(); + clazz = makeArrayIfNecessary(clazz); + return new TypedValue(clazz); + } + } + Class clazz = state.findType(typename); + clazz = makeArrayIfNecessary(clazz); + return new TypedValue(clazz); + } + + private Class makeArrayIfNecessary(Class clazz) { + if (this.dimensions!=0) { + for (int i=0;i 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 final Stack constructedNodes = new Stack(); + + private final 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(); + this.tokenStream = tokenizer.getTokens(); + this.tokenStreamLength = this.tokenStream.size(); + this.tokenStreamPointer = 0; + this.constructedNodes.clear(); + SpelNodeImpl ast = eatExpression(); + if (moreTokens()) { + throw new SpelParseException(peekToken().startpos,SpelMessage.MORE_INPUT,toString(nextToken())); + } + Assert.isTrue(this.constructedNodes.isEmpty()); + return new SpelExpression(expressionString, ast, this.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); + } + + 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); + } + + 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") || peekToken(TokenKind.SYMBOLIC_OR)) { + Token t = nextToken(); //consume OR + SpelNodeImpl rhExpr = eatLogicalAndExpression(); + checkOperands(t,expr,rhExpr); + expr = new OpOr(toPos(t),expr,rhExpr); + } + return expr; + } + + // logicalAndExpression : relationalExpression (AND^ relationalExpression)*; + private SpelNodeImpl eatLogicalAndExpression() { + SpelNodeImpl expr = eatRelationalExpression(); + while (peekIdentifierToken("and") || peekToken(TokenKind.SYMBOLIC_AND)) { + Token t = nextToken();// consume 'AND' + SpelNodeImpl rhExpr = eatRelationalExpression(); + checkOperands(t,expr,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(); + checkOperands(t,expr,rhExpr); + TokenKind tk = relationalOperatorToken.kind; + + if (relationalOperatorToken.isNumericRelationalOperator()) { + int pos = toPos(t); + if (tk == TokenKind.GT) { + return new OpGT(pos, expr, rhExpr); + } + if (tk == TokenKind.LT) { + return new OpLT(pos, expr, rhExpr); + } + if (tk == TokenKind.LE) { + return new OpLE(pos, expr, rhExpr); + } + if (tk == TokenKind.GE) { + return new OpGE(pos, expr, rhExpr); + } + if (tk == TokenKind.EQ) { + return new OpEQ(pos, expr, rhExpr); + } + Assert.isTrue(tk == TokenKind.NE); + return new OpNE(pos, expr, rhExpr); + } + + if (tk == TokenKind.INSTANCEOF) { + return new OperatorInstanceof(toPos(t), expr, rhExpr); + } + + if (tk == TokenKind.MATCHES) { + return new OperatorMatches(toPos(t), expr, rhExpr); + } + + Assert.isTrue(tk == TokenKind.BETWEEN); + return new OperatorBetween(toPos(t), expr, rhExpr); + } + return expr; + } + + //sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*; + private SpelNodeImpl eatSumExpression() { + SpelNodeImpl expr = eatProductExpression(); + while (peekToken(TokenKind.PLUS, TokenKind.MINUS, TokenKind.INC)) { + Token t = nextToken();//consume PLUS or MINUS or INC + SpelNodeImpl rhExpr = eatProductExpression(); + checkRightOperand(t,rhExpr); + if (t.kind == TokenKind.PLUS) { + expr = new OpPlus(toPos(t), expr, rhExpr); + } + else if (t.kind == TokenKind.MINUS) { + expr = new OpMinus(toPos(t), expr, rhExpr); + } + } + return expr; + } + + // productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ; + private SpelNodeImpl eatProductExpression() { + SpelNodeImpl expr = eatPowerIncDecExpression(); + while (peekToken(TokenKind.STAR, TokenKind.DIV, TokenKind.MOD)) { + Token t = nextToken(); // consume STAR/DIV/MOD + SpelNodeImpl rhExpr = eatPowerIncDecExpression(); + checkOperands(t,expr,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)? (INC || DEC) ; + private SpelNodeImpl eatPowerIncDecExpression() { + 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); + } + + if (expr!=null && peekToken(TokenKind.INC,TokenKind.DEC)) { + Token t = nextToken();//consume INC/DEC + if (t.getKind()==TokenKind.INC) { + return new OpInc(toPos(t),true,expr); + } + return new OpDec(toPos(t),true,expr); + } + + return expr; + } + + // unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) 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); + } + + if (t.kind == TokenKind.PLUS) { + return new OpPlus(toPos(t), expr); + } + Assert.isTrue(t.kind == TokenKind.MINUS); + return new OpMinus(toPos(t), expr); + + } + if (peekToken(TokenKind.INC, TokenKind.DEC)) { + Token t = nextToken(); + SpelNodeImpl expr = eatUnaryExpression(); + if (t.getKind() == TokenKind.INC) { + return new OpInc(toPos(t), false, expr); + } + return new OpDec(toPos(t), false, expr); + } + + return eatPrimaryExpression(); + } + + // primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?); + private SpelNodeImpl eatPrimaryExpression() { + List nodes = new ArrayList(); + SpelNodeImpl start = eatStartNode(); // always a start node + nodes.add(start); + while (maybeEatNode()) { + nodes.add(pop()); + } + if (nodes.size() == 1) { + return nodes.get(0); + } + 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; + } + + 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 args = new ArrayList(); + consumeArguments(args); + eatToken(TokenKind.RPAREN); + return args.toArray(new SpelNodeImpl[args.size()]); + } + + private void eatConstructorArgs(List accumulatedArguments) { + if (!peekToken(TokenKind.LPAREN)) { + throw new InternalParseException(new SpelParseException(this.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 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 this.expressionString.length(); + } + 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); + this.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 + // Are there array dimensions? + int dims = 0; + while (peekToken(TokenKind.LSQUARE,true)) { + eatToken(TokenKind.RSQUARE); + dims++; + } + eatToken(TokenKind.RPAREN); + this.constructedNodes.push(new TypeReference(toPos(typeName),node,dims)); + return true; + } + return false; + } + + private boolean maybeEatNullReference() { + if (peekToken(TokenKind.IDENTIFIER)) { + Token nullToken = peekToken(); + if (!nullToken.stringValue().equalsIgnoreCase("null")) { + return false; + } + nextToken(); + this.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); + this.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 listElements = new ArrayList(); + 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()])); + } + this.constructedNodes.push(expr); + return true; + } + + private boolean maybeEatIndexer() { + Token t = peekToken(); + if (!peekToken(TokenKind.LSQUARE, true)) { + return false; + } + SpelNodeImpl expr = eatExpression(); + eatToken(TokenKind.RSQUARE); + this.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(); + if(expr == null) { + raiseInternalException(toPos(t), SpelMessage.MISSING_SELECTION_EXPRESSION); + } + eatToken(TokenKind.RSQUARE); + if (t.kind == TokenKind.SELECT_FIRST) { + this.constructedNodes.push(new Selection(nullSafeNavigation, Selection.FIRST, toPos(t), expr)); + } + else if (t.kind == TokenKind.SELECT_LAST) { + this.constructedNodes.push(new Selection(nullSafeNavigation, Selection.LAST, toPos(t), expr)); + } + else { + this.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() { + LinkedList qualifiedIdPieces = new LinkedList(); + Token node = peekToken(); + while (isValidQualifiedId(node)) { + nextToken(); + if(node.kind != TokenKind.DOT) { + qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node))); + } + node = peekToken(); + } + if(qualifiedIdPieces.isEmpty()) { + if(node == null) { + raiseInternalException( this.expressionString.length(), SpelMessage.OOD); + } + raiseInternalException(node.startpos, SpelMessage.NOT_EXPECTED_TOKEN, + "qualified ID", node.getKind().toString().toLowerCase()); + } + int pos = toPos(qualifiedIdPieces.getFirst().getStartPosition(), qualifiedIdPieces.getLast().getEndPosition()); + return new QualifiedIdentifier(pos, qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()])); + } + + private boolean isValidQualifiedId(Token node) { + if(node == null || node.kind == TokenKind.LITERAL_STRING) { + return false; + } + if(node.kind == TokenKind.DOT || node.kind == TokenKind.IDENTIFIER) { + return true; + } + String value = node.stringValue(); + return StringUtils.hasLength(value) && VALID_QUALIFIED_ID_PATTERN.matcher(value).matches(); + } + + // 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; + } + // 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 nodes = new ArrayList(); + nodes.add(possiblyQualifiedConstructorName); + if (peekToken(TokenKind.LSQUARE)) { + // array initializer + List dimensions = new ArrayList(); + 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) { + this.constructedNodes.push(newNode); + } + + private SpelNodeImpl pop() { + return this.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(); + } + if (idString.equalsIgnoreCase("matches")) { + return t.asMatchesToken(); + } + if (idString.equalsIgnoreCase("between")) { + return t.asBetweenToken(); + } + } + return null; + } + + private Token eatToken(TokenKind expectedKind) { + Token t = nextToken(); + if (t==null) { + raiseInternalException( this.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) { + this.tokenStreamPointer++; + } + return true; + } + + 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 this.tokenStreamPointer= this.tokenStreamLength) { + return null; + } + return this.tokenStream.get(this.tokenStreamPointer++); + } + + private Token peekToken() { + if (this.tokenStreamPointer >= this.tokenStreamLength) { + return null; + } + return this.tokenStream.get(this.tokenStreamPointer); + } + + private void raiseInternalException(int pos, SpelMessage message, Object... inserts) { + throw new InternalParseException(new SpelParseException(this.expressionString, + pos, message, inserts)); + } + + public String toString(Token t) { + if (t.getKind().hasPayload()) { + return t.stringValue(); + } + return t.kind.toString().toLowerCase(); + } + + private void checkOperands(Token token, SpelNodeImpl left, SpelNodeImpl right) { + checkLeftOperand(token, left); + checkRightOperand(token, right); + } + + private void checkLeftOperand(Token token, SpelNodeImpl operandExpression) { + if (operandExpression==null) { + raiseInternalException(token.startpos,SpelMessage.LEFT_OPERAND_PROBLEM); + } + } + + 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; + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/standard/SpelExpression.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/standard/SpelExpression.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/standard/SpelExpression.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,251 @@ +/* + * Copyright 2002-2013 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 {@code SpelExpression} 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, and 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 + + @Override + public Object getValue() throws EvaluationException { + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); + return this.ast.getValue(expressionState); + } + + @Override + public Object getValue(Object rootObject) throws EvaluationException { + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); + return this.ast.getValue(expressionState); + } + + @Override + public T getValue(Class expectedResultType) throws EvaluationException { + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); + TypedValue typedResultValue = this.ast.getTypedValue(expressionState); + return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType); + } + + @Override + public T getValue(Object rootObject, Class expectedResultType) throws EvaluationException { + ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); + TypedValue typedResultValue = this.ast.getTypedValue(expressionState); + return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType); + } + + @Override + public Object getValue(EvaluationContext context) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + return this.ast.getValue(new ExpressionState(context, this.configuration)); + } + + @Override + public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + return this.ast.getValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); + } + + @Override + public T getValue(EvaluationContext context, Class expectedResultType) throws EvaluationException { + TypedValue typedResultValue = this.ast.getTypedValue(new ExpressionState(context, this.configuration)); + return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); + } + + @Override + public T getValue(EvaluationContext context, Object rootObject, Class expectedResultType) throws EvaluationException { + TypedValue typedResultValue = this.ast.getTypedValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); + return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); + } + + @Override + public Class getValueType() throws EvaluationException { + return getValueType(getEvaluationContext()); + } + + @Override + public Class getValueType(Object rootObject) throws EvaluationException { + return getValueType(getEvaluationContext(), rootObject); + } + + @Override + public Class getValueType(EvaluationContext context) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + ExpressionState eState = new ExpressionState(context, this.configuration); + TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor(); + return typeDescriptor != null ? typeDescriptor.getType() : null; + } + + @Override + public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { + ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); + TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor(); + return typeDescriptor != null ? typeDescriptor.getType() : null; + } + + @Override + public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { + return getValueTypeDescriptor(getEvaluationContext()); + } + + @Override + public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + ExpressionState eState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); + return this.ast.getValueInternal(eState).getTypeDescriptor(); + } + + @Override + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + ExpressionState eState = new ExpressionState(context, this.configuration); + return this.ast.getValueInternal(eState).getTypeDescriptor(); + } + + @Override + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); + return this.ast.getValueInternal(eState).getTypeDescriptor(); + } + + @Override + public String getExpressionString() { + return this.expression; + } + + @Override + public boolean isWritable(EvaluationContext context) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + return this.ast.isWritable(new ExpressionState(context, this.configuration)); + } + + @Override + public boolean isWritable(Object rootObject) throws EvaluationException { + return this.ast.isWritable(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration)); + } + + @Override + public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + return this.ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); + } + + @Override + public void setValue(EvaluationContext context, Object value) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + this.ast.setValue(new ExpressionState(context, this.configuration), value); + } + + @Override + public void setValue(Object rootObject, Object value) throws EvaluationException { + this.ast.setValue(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration), value); + } + + @Override + public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { + Assert.notNull(context, "The EvaluationContext is required"); + this.ast.setValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration), value); + } + + // impl only + + /** + * @return return the Abstract Syntax Tree for the expression + */ + public SpelNode getAST() { + return this.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 this.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 (this.defaultContext == null) { + this.defaultContext = new StandardEvaluationContext(); + } + return this.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); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/standard/SpelExpressionParser.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/standard/SpelExpressionParser.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/standard/SpelExpressionParser.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2012 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); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/standard/Token.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/standard/Token.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/standard/Token.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2013 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 this.kind; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("[").append(this.kind.toString()); + if (this.kind.hasPayload()) { + s.append(":").append(this.data); + } + s.append("]"); + s.append("(").append(this.startpos).append(",").append(this.endpos).append(")"); + return s.toString(); + } + + public boolean isIdentifier() { + return this.kind==TokenKind.IDENTIFIER; + } + + public boolean isNumericRelationalOperator() { + return this.kind==TokenKind.GT || this.kind==TokenKind.GE || this.kind==TokenKind.LT || this.kind==TokenKind.LE || this.kind==TokenKind.EQ || this.kind==TokenKind.NE; + } + + public String stringValue() { + return this.data; + } + + public Token asInstanceOfToken() { + return new Token(TokenKind.INSTANCEOF,this.startpos,this.endpos); + } + + public Token asMatchesToken() { + return new Token(TokenKind.MATCHES,this.startpos,this.endpos); + } + + public Token asBetweenToken() { + return new Token(TokenKind.BETWEEN,this.startpos,this.endpos); + } +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/standard/TokenKind.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/standard/TokenKind.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/standard/TokenKind.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2013 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; + +/** + * Token Kinds. + * + * @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("@"), + + SYMBOLIC_OR("||"), + + SYMBOLIC_AND("&&"), + + INC("++"), + + DEC("--"); + + + char[] tokenChars; + + private boolean hasPayload; // is there more to this token than simply the kind + + + private TokenKind(String tokenString) { + this.tokenChars = tokenString.toCharArray(); + this.hasPayload = this.tokenChars.length==0; + } + + private TokenKind() { + this(""); + } + + + @Override + public String toString() { + return this.name()+(this.tokenChars.length!=0?"("+new String(this.tokenChars)+")":""); + } + + public boolean hasPayload() { + return this.hasPayload; + } + + public int getLength() { + return this.tokenChars.length; + } +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/standard/Tokenizer.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/standard/Tokenizer.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/standard/Tokenizer.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,598 @@ +/* + * Copyright 2002-2013 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 + * @author Phillip Webb + * @since 3.0 + */ +class Tokenizer { + + // if this is changed, it must remain sorted + private static final String[] ALTERNATIVE_OPERATOR_NAMES = { "DIV", "EQ", "GE", "GT", + "LE", "LT", "MOD", "NE", "NOT" }; + + 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; + } + } + + + String expressionString; + + char[] toProcess; + + int pos; + + int max; + + List tokens = new ArrayList(); + + + public Tokenizer(String inputdata) { + this.expressionString = inputdata; + this.toProcess = (inputdata + "\0").toCharArray(); + this.max = this.toProcess.length; + this.pos = 0; + process(); + } + + + public void process() { + while (this.pos < this.max) { + char ch = this.toProcess[this.pos]; + if (isAlphabetic(ch)) { + lexIdentifier(); + } + else { + switch (ch) { + case '+': + if (isTwoCharToken(TokenKind.INC)) { + pushPairToken(TokenKind.INC); + } + else { + pushCharToken(TokenKind.PLUS); + } + break; + case '_': // the other way to start an identifier + lexIdentifier(); + break; + case '-': + if (isTwoCharToken(TokenKind.DEC)) { + pushPairToken(TokenKind.DEC); + } + else { + 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.SYMBOLIC_AND)) { + throw new InternalParseException(new SpelParseException( + this.expressionString, this.pos, SpelMessage.MISSING_CHARACTER, + "&")); + } + pushPairToken(TokenKind.SYMBOLIC_AND); + break; + case '|': + if (!isTwoCharToken(TokenKind.SYMBOLIC_OR)) { + throw new InternalParseException(new SpelParseException( + this.expressionString, this.pos, SpelMessage.MISSING_CHARACTER, + "|")); + } + pushPairToken(TokenKind.SYMBOLIC_OR); + 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 + this.pos++; + break; + case '\'': + lexQuotedStringLiteral(); + break; + case '"': + lexDoubleQuotedStringLiteral(); + break; + case 0: + // hit sentinel at end of value + this.pos++; // will take us to the end + break; + case '\\': + throw new InternalParseException( + new SpelParseException(this.expressionString, this.pos, + SpelMessage.UNEXPECTED_ESCAPE_CHAR)); + default: + throw new IllegalStateException("Cannot handle (" + + Integer.valueOf(ch) + ") '" + ch + "'"); + } + } + } + } + + public List getTokens() { + return this.tokens; + } + + // STRING_LITERAL: '\''! (APOS|~'\'')* '\''!; + private void lexQuotedStringLiteral() { + int start = this.pos; + boolean terminated = false; + while (!terminated) { + this.pos++; + char ch = this.toProcess[this.pos]; + if (ch == '\'') { + // may not be the end if the char after is also a ' + if (this.toProcess[this.pos + 1] == '\'') { + this.pos++; // skip over that too, and continue + } + else { + terminated = true; + } + } + if (ch == 0) { + throw new InternalParseException(new SpelParseException(this.expressionString, + start, SpelMessage.NON_TERMINATING_QUOTED_STRING)); + } + } + this.pos++; + this.tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start, this.pos), start, this.pos)); + } + + // DQ_STRING_LITERAL: '"'! (~'"')* '"'!; + private void lexDoubleQuotedStringLiteral() { + int start = this.pos; + boolean terminated = false; + while (!terminated) { + this.pos++; + char ch = this.toProcess[this.pos]; + if (ch == '"') { + // may not be the end if the char after is also a " + if (this.toProcess[this.pos + 1] == '"') { + this.pos++; // skip over that too, and continue + } + else { + terminated = true; + } + } + if (ch == 0) { + throw new InternalParseException(new SpelParseException(this.expressionString, + start, SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING)); + } + } + this.pos++; + this.tokens.add(new Token(TokenKind.LITERAL_STRING, subarray(start, this.pos), start, this.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 = this.pos; + char ch = this.toProcess[this.pos + 1]; + boolean isHex = ch == 'x' || ch == 'X'; + + // deal with hexadecimal + if (firstCharIsZero && isHex) { + this.pos = this.pos + 1; + do { + this.pos++; + } + while (isHexadecimalDigit(this.toProcess[this.pos])); + if (isChar('L', 'l')) { + pushHexIntToken(subarray(start + 2, this.pos), true, start, this.pos); + this.pos++; + } + else { + pushHexIntToken(subarray(start + 2, this.pos), false, start, this.pos); + } + return; + } + + // real numbers must have leading digits + + // Consume first part of number + do { + this.pos++; + } + while (isDigit(this.toProcess[this.pos])); + + // a '.' indicates this number is a real + ch = this.toProcess[this.pos]; + if (ch == '.') { + isReal = true; + int dotpos = this.pos; + // carry on consuming digits + do { + this.pos++; + } + while (isDigit(this.toProcess[this.pos])); + if (this.pos == dotpos + 1) { + // the number is something like '3.'. It is really an int but may be + // part of something like '3.toString()'. In this case process it as + // an int and leave the dot as a separate token. + this.pos = dotpos; + pushIntToken(subarray(start, this.pos), false, start, this.pos); + return; + } + } + + int endOfNumber = this.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(this.expressionString, + start, SpelMessage.REAL_CANNOT_BE_LONG)); + } + pushIntToken(subarray(start, endOfNumber), true, start, endOfNumber); + this.pos++; + } + else if (isExponentChar(this.toProcess[this.pos])) { + isReal = true; // if it wasn't before, it is now + this.pos++; + char possibleSign = this.toProcess[this.pos]; + if (isSign(possibleSign)) { + this.pos++; + } + + // exponent digits + do { + this.pos++; + } + while (isDigit(this.toProcess[this.pos])); + boolean isFloat = false; + if (isFloatSuffix(this.toProcess[this.pos])) { + isFloat = true; + endOfNumber = ++this.pos; + } + else if (isDoubleSuffix(this.toProcess[this.pos])) { + endOfNumber = ++this.pos; + } + pushRealToken(subarray(start, this.pos), isFloat, start, this.pos); + } + else { + ch = this.toProcess[this.pos]; + boolean isFloat = false; + if (isFloatSuffix(ch)) { + isReal = true; + isFloat = true; + endOfNumber = ++this.pos; + } + else if (isDoubleSuffix(ch)) { + isReal = true; + endOfNumber = ++this.pos; + } + if (isReal) { + pushRealToken(subarray(start, endOfNumber), isFloat, start, endOfNumber); + } + else { + pushIntToken(subarray(start, endOfNumber), false, start, endOfNumber); + } + } + } + + private void lexIdentifier() { + int start = this.pos; + do { + this.pos++; + } + while (isIdentifier(this.toProcess[this.pos])); + char[] subarray = subarray(start, this.pos); + + // Check if this is the alternative (textual) representation of an operator (see + // alternativeOperatorNames) + if ((this.pos - start) == 2 || (this.pos - start) == 3) { + String asString = new String(subarray).toUpperCase(); + int idx = Arrays.binarySearch(ALTERNATIVE_OPERATOR_NAMES, asString); + if (idx >= 0) { + pushOneCharOrTwoCharToken(TokenKind.valueOf(asString), start, subarray); + return; + } + } + this.tokens.add(new Token(TokenKind.IDENTIFIER, subarray, start, this.pos)); + } + + private void pushIntToken(char[] data, boolean isLong, int start, int end) { + if (isLong) { + this.tokens.add(new Token(TokenKind.LITERAL_LONG, data, start, end)); + } + else { + this.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(this.expressionString, + start, SpelMessage.NOT_A_LONG, this.expressionString.substring(start, + end + 1))); + } + else { + throw new InternalParseException(new SpelParseException(this.expressionString, + start, SpelMessage.NOT_AN_INTEGER, this.expressionString.substring( + start, end))); + } + } + if (isLong) { + this.tokens.add(new Token(TokenKind.LITERAL_HEXLONG, data, start, end)); + } + else { + this.tokens.add(new Token(TokenKind.LITERAL_HEXINT, data, start, end)); + } + } + + private void pushRealToken(char[] data, boolean isFloat, int start, int end) { + if (isFloat) { + this.tokens.add(new Token(TokenKind.LITERAL_REAL_FLOAT, data, start, end)); + } + else { + this.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(this.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(this.toProcess[this.pos] == kind.tokenChars[0]); + return this.toProcess[this.pos + 1] == kind.tokenChars[1]; + } + + /** + * Push a token of just one character in length. + */ + private void pushCharToken(TokenKind kind) { + this.tokens.add(new Token(kind, this.pos, this.pos + 1)); + this.pos++; + } + + /** + * Push a token of two characters in length. + */ + private void pushPairToken(TokenKind kind) { + this.tokens.add(new Token(kind, this.pos, this.pos + 2)); + this.pos += 2; + } + + private void pushOneCharOrTwoCharToken(TokenKind kind, int pos, char[] data) { + this.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 = this.toProcess[this.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; + } +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/BooleanTypedValue.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/BooleanTypedValue.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/BooleanTypedValue.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2013 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) { + return (b ? TRUE : FALSE); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectionHelper.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectionHelper.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectionHelper.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,404 @@ +/* + * Copyright 2002-2014 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.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 expectedArgTypes, List suppliedArgTypes, TypeConverter typeConverter) { + + Assert.isTrue(expectedArgTypes.size() == suppliedArgTypes.size(), + "Expected argument types and supplied argument types should be arrays of same length"); + + ArgumentsMatchKind match = ArgumentsMatchKind.EXACT; + 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 != ArgumentsMatchKind.REQUIRES_CONVERSION) { + match = ArgumentsMatchKind.CLOSE; + } + } + else if (typeConverter.canConvert(suppliedArg, expectedArg)) { + match = ArgumentsMatchKind.REQUIRES_CONVERSION; + } + else { + match = null; + } + } + } + } + return (match != null ? new ArgumentsMatchInfo(match) : null); + } + + /** + * Based on {@link MethodInvoker#getTypeDifferenceWeight(Class[], Object[])} but operates on TypeDescriptors. + */ + public static int getTypeDifferenceWeight(List paramTypes, List argTypes) { + int result = 0; + for (int i = 0; i < paramTypes.size(); i++) { + TypeDescriptor paramType = paramTypes.get(i); + TypeDescriptor argType = argTypes.get(i); + if (argType == null) { + if (paramType.isPrimitive()) { + return Integer.MAX_VALUE; + } + } + else { + Class paramTypeClazz = paramType.getType(); + if (!ClassUtils.isAssignable(paramTypeClazz, argType.getType())) { + return Integer.MAX_VALUE; + } + if (paramTypeClazz.isPrimitive()) { + paramTypeClazz = Object.class; + } + Class superClass = argType.getType().getSuperclass(); + while (superClass != null) { + if (paramTypeClazz.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 expectedArgTypes, List 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)"); + + ArgumentsMatchKind match = ArgumentsMatchKind.EXACT; + + // 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 != ArgumentsMatchKind.REQUIRES_CONVERSION) { + match = ArgumentsMatchKind.CLOSE; + } + } + else if (typeConverter.canConvert(suppliedArg, expectedArg)) { + match = ArgumentsMatchKind.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. + TypeDescriptor varargsDesc = expectedArgTypes.get(expectedArgTypes.size() - 1); + Class varargsParamType = varargsDesc.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 (varargsParamType.isPrimitive()) { + match = null; + } + } + else { + if (varargsParamType != suppliedArg.getType()) { + if (ClassUtils.isAssignable(varargsParamType, suppliedArg.getType())) { + if (match != ArgumentsMatchKind.REQUIRES_CONVERSION) { + match = ArgumentsMatchKind.CLOSE; + } + } + else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsParamType))) { + match = ArgumentsMatchKind.REQUIRES_CONVERSION; + } + else { + match = null; + } + } + } + } + } + + return (match != null ? new ArgumentsMatchInfo(match) : null); + } + + /** + * Takes an input set of argument values and 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 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, + 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 = new TypeDescriptor(methodParam).getElementTypeDescriptor(); + 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 argPos = 0; argPos < arguments.length; argPos++) { + TypeDescriptor targetType; + if (varargsPosition != null && argPos >= varargsPosition) { + MethodParameter methodParam = new MethodParameter(method, varargsPosition); + targetType = TypeDescriptor.nested(methodParam, 1); + } + else { + targetType = new TypeDescriptor(new MethodParameter(method, argPos)); + } + try { + Object argument = arguments[argPos]; + if (argument != null && !targetType.getObjectType().isInstance(argument)) { + if (converter == null) { + throw new SpelEvaluationException( + SpelMessage.TYPE_CONVERSION_ERROR, argument.getClass().getName(), targetType); + } + arguments[argPos] = 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[argPos].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 ? args[argumentCount - 1].getClass() : null)) { + + 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]; + System.arraycopy(args, 0, newArgs, 0, newArgs.length - 1); + + // 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(); + Object repackagedArgs = Array.newInstance(componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + Array.set(repackagedArgs, i, args[parameterCount - 1 + i]); + } + newArgs[newArgs.length - 1] = repackagedArgs; + return newArgs; + } + return args; + } + + + static enum ArgumentsMatchKind { + + /** An exact match is where the parameter types exactly match what the method/constructor is expecting */ + EXACT, + + /** A close match is where the parameter types either exactly match or are assignment-compatible */ + 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. + */ + static class ArgumentsMatchInfo { + + private final ArgumentsMatchKind kind; + + ArgumentsMatchInfo(ArgumentsMatchKind kind) { + this.kind = kind; + } + + public boolean isExactMatch() { + return (this.kind == ArgumentsMatchKind.EXACT); + } + + public boolean isCloseMatch() { + return (this.kind == ArgumentsMatchKind.CLOSE); + } + + public boolean isMatchRequiringConversion() { + return (this.kind == ArgumentsMatchKind.REQUIRES_CONVERSION); + } + + @Override + public String toString() { + return "ArgumentMatchInfo: " + this.kind; + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2013 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; + + + public ReflectiveConstructorExecutor(Constructor ctor) { + this.ctor = ctor; + if (ctor.isVarArgs()) { + Class[] paramTypes = ctor.getParameterTypes(); + this.varargsPosition = paramTypes.length - 1; + } + else { + this.varargsPosition = null; + } + } + + @Override + public TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException { + try { + if (arguments != null) { + ReflectionHelper.convertArguments(context.getTypeConverter(), arguments, this.ctor, 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); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2013 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: + *
    + *
  1. An exact match where the types of the arguments match the types of the constructor + *
  2. An in-exact match where the types we are looking for are subtypes of those defined on the constructor + *
  3. A match where we are able to convert the arguments into those expected by the constructor, according to the + * registered type converter. + *
+ */ + @Override + public ConstructorExecutor resolve(EvaluationContext context, String typename, List argumentTypes) + throws AccessException { + + try { + TypeConverter typeConverter = context.getTypeConverter(); + Class type = context.getTypeLocator().findType(typename); + Constructor[] ctors = type.getConstructors(); + + Arrays.sort(ctors, new Comparator>() { + @Override + 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; + Constructor matchRequiringConversion = null; + + for (Constructor ctor : ctors) { + Class[] paramTypes = ctor.getParameterTypes(); + List paramDescriptors = new ArrayList(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.isExactMatch()) { + return new ReflectiveConstructorExecutor(ctor); + } + else if (matchInfo.isCloseMatch()) { + closeMatch = ctor; + } + else if (matchInfo.isMatchRequiringConversion()) { + matchRequiringConversion = ctor; + } + } + } + + if (closeMatch != null) { + return new ReflectiveConstructorExecutor(closeMatch); + } + else if (matchRequiringConversion != null) { + return new ReflectiveConstructorExecutor(matchRequiringConversion); + } + else { + return null; + } + } + catch (EvaluationException ex) { + throw new AccessException("Failed to resolve constructor", ex); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2012 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; + + + public ReflectiveMethodExecutor(Method method) { + this.method = method; + if (method.isVarArgs()) { + Class[] paramTypes = method.getParameterTypes(); + this.varargsPosition = paramTypes.length - 1; + } + else { + this.varargsPosition = null; + } + } + + + @Override + public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException { + try { + if (arguments != null) { + ReflectionHelper.convertArguments(context.getTypeConverter(), arguments, this.method, 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); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveMethodResolver.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveMethodResolver.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectiveMethodResolver.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,226 @@ +/* + * Copyright 2002-2013 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.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.BridgeMethodResolver; +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; + +/** + * Reflection-based {@link MethodResolver} used by default in {@link StandardEvaluationContext} + * unless explicit method resolvers have been specified. + * + * @author Andy Clement + * @author Juergen Hoeller + * @author Chris Beams + * @since 3.0 + * @see StandardEvaluationContext#addMethodResolver(MethodResolver) + */ +public class ReflectiveMethodResolver implements MethodResolver { + + // Using distance will ensure a more accurate match is discovered, + // more closely following the Java rules. + private final boolean useDistance; + + private Map, MethodFilter> filters; + + + public ReflectiveMethodResolver() { + this.useDistance = false; + } + + /** + * 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; + } + + + public void registerMethodFilter(Class type, MethodFilter filter) { + if (this.filters == null) { + this.filters = new HashMap, MethodFilter>(); + } + if (filter != null) { + this.filters.put(type, filter); + } + else { + this.filters.remove(type); + } + } + + + /** + * Locate a method on a type. There are three kinds of match that might occur: + *
    + *
  1. An exact match where the types of the arguments match the types of the constructor + *
  2. An in-exact match where the types we are looking for are subtypes of those defined on the constructor + *
  3. A match where we are able to convert the arguments into those expected by the constructor, + * according to the registered type converter. + *
+ */ + @Override + public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, + List argumentTypes) throws AccessException { + + try { + TypeConverter typeConverter = context.getTypeConverter(); + Class type = (targetObject instanceof Class ? (Class) targetObject : targetObject.getClass()); + List methods = new ArrayList(Arrays.asList(getMethods(type, targetObject))); + + // If a filter is registered for this type, call it + MethodFilter filter = (this.filters != null ? this.filters.get(type) : null); + if (filter != null) { + List filtered = filter.filter(methods); + methods = (filtered instanceof ArrayList ? filtered : new ArrayList(filtered)); + } + + // Sort methods into a sensible order + if (methods.size() > 1) { + Collections.sort(methods, new Comparator() { + @Override + public int compare(Method m1, Method m2) { + int m1pl = m1.getParameterTypes().length; + int m2pl = m2.getParameterTypes().length; + return (new Integer(m1pl)).compareTo(m2pl); + } + }); + } + + // Resolve any bridge methods + for (int i = 0; i < methods.size(); i++) { + methods.set(i, BridgeMethodResolver.findBridgedMethod(methods.get(i))); + } + + // Remove duplicate methods (possible due to resolved bridge methods) + Set methodsToIterate = new LinkedHashSet(methods); + + Method closeMatch = null; + int closeMatchDistance = Integer.MAX_VALUE; + Method matchRequiringConversion = null; + boolean multipleOptions = false; + + for (Method method : methodsToIterate) { + if (method.getName().equals(name)) { + Class[] paramTypes = method.getParameterTypes(); + List paramDescriptors = new ArrayList(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.isExactMatch()) { + return new ReflectiveMethodExecutor(method); + } + else if (matchInfo.isCloseMatch()) { + if (!this.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.isMatchRequiringConversion()) { + if (matchRequiringConversion != null) { + multipleOptions = true; + } + matchRequiringConversion = method; + } + } + } + } + if (closeMatch != null) { + return new ReflectiveMethodExecutor(closeMatch); + } + else if (matchRequiringConversion != null) { + if (multipleOptions) { + throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name); + } + return new ReflectiveMethodExecutor(matchRequiringConversion); + } + else { + return null; + } + } + catch (EvaluationException ex) { + throw new AccessException("Failed to resolve method", ex); + } + } + + private Method[] getMethods(Class type, Object targetObject) { + if (targetObject instanceof Class) { + Set methods = new HashSet(); + methods.addAll(Arrays.asList(getMethods(type))); + methods.addAll(Arrays.asList(getMethods(targetObject.getClass()))); + return methods.toArray(new Method[methods.size()]); + } + return getMethods(type); + } + + /** + * Return the set of methods for this type. The default implementation returns the + * result of {@link Class#getMethods()} for the given {@code type}, but subclasses + * may override in order to alter the results, e.g. specifying static methods + * declared elsewhere. + * @param type the class for which to return the methods + * @since 3.1.1 + */ + protected Method[] getMethods(Class type) { + return type.getMethods(); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,652 @@ +/* + * Copyright 2002-2014 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.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +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.core.style.ToStringCreator; +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 {@link PropertyAccessor} that uses reflection to access properties + * for reading and writing. + * + *

A property can be accessed through a public getter method (when being read) + * or a public setter method (when being written), and also as a public field. + * + * @author Andy Clement + * @author Juergen Hoeller + * @author Phillip Webb + * @since 3.0 + */ +public class ReflectivePropertyAccessor implements PropertyAccessor { + + private static final Set> BOOLEAN_TYPES; + static { + Set> booleanTypes = new HashSet>(); + booleanTypes.add(Boolean.class); + booleanTypes.add(Boolean.TYPE); + BOOLEAN_TYPES = Collections.unmodifiableSet(booleanTypes); + } + + private static final Set> ANY_TYPES = Collections.emptySet(); + + + private final Map readerCache = new ConcurrentHashMap(64); + + private final Map writerCache = new ConcurrentHashMap(64); + + private final Map typeDescriptorCache = new ConcurrentHashMap(64); + + + /** + * Returns {@code null} which means this is a general purpose accessor. + */ + @Override + public Class[] getSpecificTargetClasses() { + return null; + } + + @Override + 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, target instanceof Class); + if (this.readerCache.containsKey(cacheKey)) { + return true; + } + Method method = findGetterForProperty(name, type, target); + 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); + 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; + } + + @Override + 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, target instanceof Class); + 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); + 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); + 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 + "'"); + } + + @Override + 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, target instanceof Class); + if (this.writerCache.containsKey(cacheKey)) { + return true; + } + Method method = findSetterForProperty(name, type, target); + 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); + if (field != null) { + this.writerCache.put(cacheKey, field); + this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field)); + return true; + } + } + return false; + } + + @Override + 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, target instanceof Class); + Member cachedMember = this.writerCache.get(cacheKey); + + if (cachedMember == null || cachedMember instanceof Method) { + Method method = (Method) cachedMember; + if (method == null) { + method = findSetterForProperty(name, type, target); + 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); + 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, target instanceof Class); + 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; + } + + private Method findGetterForProperty(String propertyName, Class clazz, Object target) { + Method method = findGetterForProperty(propertyName, clazz, target instanceof Class); + if (method == null && target instanceof Class) { + method = findGetterForProperty(propertyName, target.getClass(), false); + } + return method; + } + + private Method findSetterForProperty(String propertyName, Class clazz, Object target) { + Method method = findSetterForProperty(propertyName, clazz, target instanceof Class); + if (method == null && target instanceof Class) { + method = findSetterForProperty(propertyName, target.getClass(), false); + } + return method; + } + + private Field findField(String name, Class clazz, Object target) { + Field field = findField(name, clazz, target instanceof Class); + if (field == null && target instanceof Class) { + field = findField(name, target.getClass(), false); + } + return field; + } + + /** + * Find a getter method for the specified property. + */ + protected Method findGetterForProperty(String propertyName, Class clazz, boolean mustBeStatic) { + Method method = findMethodForProperty(getPropertyMethodSuffixes(propertyName), + "get", clazz, mustBeStatic, 0, ANY_TYPES); + if (method == null) { + method = findMethodForProperty(getPropertyMethodSuffixes(propertyName), + "is", clazz, mustBeStatic, 0, BOOLEAN_TYPES); + } + return method; + } + + /** + * Find a setter method for the specified property. + */ + protected Method findSetterForProperty(String propertyName, Class clazz, boolean mustBeStatic) { + return findMethodForProperty(getPropertyMethodSuffixes(propertyName), + "set", clazz, mustBeStatic, 1, ANY_TYPES); + } + + private Method findMethodForProperty(String[] methodSuffixes, String prefix, Class clazz, + boolean mustBeStatic, int numberOfParams, Set> requiredReturnTypes) { + + Method[] methods = getSortedClassMethods(clazz); + for (String methodSuffix : methodSuffixes) { + for (Method method : methods) { + if (method.getName().equals(prefix + methodSuffix) + && method.getParameterTypes().length == numberOfParams + && (!mustBeStatic || Modifier.isStatic(method.getModifiers())) + && (requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) { + return method; + } + } + } + return null; + + } + + /** + * Returns class methods ordered with non bridge methods appearing higher. + */ + private Method[] getSortedClassMethods(Class clazz) { + Method[] methods = clazz.getMethods(); + Arrays.sort(methods, new Comparator() { + @Override + public int compare(Method o1, Method o2) { + return (o1.isBridge() == o2.isBridge()) ? 0 : (o1.isBridge() ? 1 : -1); + } + }); + return methods; + } + + /** + * Return the method suffixes for a given property name. The default implementation + * uses JavaBean conventions with additional support for properties of the form 'xY' + * where the method 'getXY()' is used in preference to the JavaBean convention of + * 'getxY()'. + */ + protected String[] getPropertyMethodSuffixes(String propertyName) { + String suffix = getPropertyMethodSuffix(propertyName); + if (suffix.length() > 0 && Character.isUpperCase(suffix.charAt(0))) { + return new String[] { suffix }; + } + return new String[] { suffix, StringUtils.capitalize(suffix) }; + } + + /** + * Return the method suffix for a given property name. The default implementation + * uses JavaBean conventions. + */ + protected String getPropertyMethodSuffix(String propertyName) { + if (propertyName.length() > 1 && Character.isUpperCase(propertyName.charAt(1))) { + return propertyName; + } + return StringUtils.capitalize(propertyName); + } + + /** + * 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; + } + } + // We'll search superclasses and implemented interfaces explicitly, + // although it shouldn't be necessary - however, see SPR-10125. + if (clazz.getSuperclass() != null) { + Field field = findField(name, clazz.getSuperclass(), mustBeStatic); + if (field != null) { + return field; + } + } + for (Class implementedInterface : clazz.getInterfaces()) { + Field field = findField(name, implementedInterface, mustBeStatic); + if (field != null) { + return field; + } + } + return null; + } + + /** + * 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, target instanceof Class); + 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); + 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; + } + + + /** + * 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; + + private boolean targetIsClass; + + public CacheKey(Class clazz, String name, boolean targetIsClass) { + this.clazz = clazz; + this.name = name; + this.targetIsClass = targetIsClass; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof CacheKey)) { + return false; + } + CacheKey otherKey = (CacheKey) other; + boolean rtn = true; + rtn &= this.clazz.equals(otherKey.clazz); + rtn &= this.name.equals(otherKey.name); + rtn &= this.targetIsClass == otherKey.targetIsClass; + return rtn; + } + + @Override + public int hashCode() { + return this.clazz.hashCode() * 29 + this.name.hashCode(); + } + + @Override + public String toString() { + return new ToStringCreator(this).append("clazz", this.clazz).append("name", + this.name).append("targetIsClass", this.targetIsClass).toString(); + } + } + + + /** + * 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. + */ + private 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) this.member; + this.needsToBeMadeAccessible = (!Modifier.isPublic(field.getModifiers()) || + !Modifier.isPublic(field.getDeclaringClass().getModifiers())) && !field.isAccessible(); + } + else { + Method method = (Method) this.member; + this.needsToBeMadeAccessible = ((!Modifier.isPublic(method.getModifiers()) || + !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()); + } + } + + @Override + public Class[] getSpecificTargetClasses() { + throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); + } + + @Override + 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 (this.member instanceof Method) { + Method method = (Method) this.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) this.member; + return field.getName().equals(name); + } + } + + @Override + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + if (this.member instanceof Method) { + try { + if (this.needsToBeMadeAccessible) { + ReflectionUtils.makeAccessible((Method) this.member); + } + Object value = ((Method) this.member).invoke(target); + return new TypedValue(value, this.typeDescriptor.narrow(value)); + } + catch (Exception ex) { + throw new AccessException("Unable to access property '" + name + "' through getter", ex); + } + } + if (this.member instanceof Field) { + try { + if (this.needsToBeMadeAccessible) { + ReflectionUtils.makeAccessible((Field) this.member); + } + Object value = ((Field) this.member).get(target); + return new TypedValue(value, this.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 + "'"); + } + + @Override + public boolean canWrite(EvaluationContext context, Object target, String name) { + throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); + } + + @Override + public void write(EvaluationContext context, Object target, String name, Object newValue) { + throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardEvaluationContext.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardEvaluationContext.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardEvaluationContext.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,291 @@ +/* + * Copyright 2002-2013 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. + * + *

To resolve properties/methods/fields this context uses a reflection mechanism. + * + * @author Andy Clement + * @author Juergen Hoeller + * @author Sam Brannen + * @since 3.0 + */ +public class StandardEvaluationContext implements EvaluationContext { + + private TypedValue rootObject; + + private List constructorResolvers; + + private List methodResolvers; + + private ReflectiveMethodResolver reflectiveMethodResolver; + + private List propertyAccessors; + + private TypeLocator typeLocator; + + private TypeConverter typeConverter; + + private TypeComparator typeComparator = new StandardTypeComparator(); + + private OperatorOverloader operatorOverloader = new StandardOperatorOverloader(); + + private final Map variables = new HashMap(); + + 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); + } + + @Override + 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 void setConstructorResolvers(List constructorResolvers) { + this.constructorResolvers = constructorResolvers; + } + + @Override + public List getConstructorResolvers() { + ensureConstructorResolversInitialized(); + return this.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 void setMethodResolvers(List methodResolvers) { + this.methodResolvers = methodResolvers; + } + + @Override + public List getMethodResolvers() { + ensureMethodResolversInitialized(); + return this.methodResolvers; + } + + public void setBeanResolver(BeanResolver beanResolver) { + this.beanResolver = beanResolver; + } + + @Override + public BeanResolver getBeanResolver() { + return this.beanResolver; + } + + 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 void setPropertyAccessors(List propertyAccessors) { + this.propertyAccessors = propertyAccessors; + } + + @Override + public List getPropertyAccessors() { + ensurePropertyAccessorsInitialized(); + return this.propertyAccessors; + } + + public void setTypeLocator(TypeLocator typeLocator) { + Assert.notNull(typeLocator, "TypeLocator must not be null"); + this.typeLocator = typeLocator; + } + + @Override + 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; + } + + @Override + 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; + } + + @Override + public TypeComparator getTypeComparator() { + return this.typeComparator; + } + + public void setOperatorOverloader(OperatorOverloader operatorOverloader) { + Assert.notNull(operatorOverloader, "OperatorOverloader must not be null"); + this.operatorOverloader = operatorOverloader; + } + + @Override + public OperatorOverloader getOperatorOverloader() { + return this.operatorOverloader; + } + + @Override + public void setVariable(String name, Object value) { + this.variables.put(name, value); + } + + public void setVariables(Map variables) { + this.variables.putAll(variables); + } + + public void registerFunction(String name, Method method) { + this.variables.put(name, method); + } + + @Override + public Object lookupVariable(String name) { + return this.variables.get(name); + } + + /** + * Register a {@code MethodFilter} which will be called during method resolution + * for the specified type. + *

The {@code 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 {@code MethodFilter}, or {@code null} to unregister a filter for the type + * @throws IllegalStateException if the {@link ReflectiveMethodResolver} is not in use + */ + public void registerMethodFilter(Class type, MethodFilter filter) throws IllegalStateException { + ensureMethodResolversInitialized(); + if (this.reflectiveMethodResolver != null) { + this.reflectiveMethodResolver.registerMethodFilter(type, filter); + } + else { + throw new IllegalStateException("Method filter cannot be set as the reflective method resolver is not in use"); + } + } + + private void ensurePropertyAccessorsInitialized() { + if (this.propertyAccessors == null) { + initializePropertyAccessors(); + } + } + + private synchronized void initializePropertyAccessors() { + if (this.propertyAccessors == null) { + List defaultAccessors = new ArrayList(); + defaultAccessors.add(new ReflectivePropertyAccessor()); + this.propertyAccessors = defaultAccessors; + } + } + + private void ensureMethodResolversInitialized() { + if (this.methodResolvers == null) { + initializeMethodResolvers(); + } + } + + private synchronized void initializeMethodResolvers() { + if (this.methodResolvers == null) { + List defaultResolvers = new ArrayList(); + this.reflectiveMethodResolver = new ReflectiveMethodResolver(); + defaultResolvers.add(this.reflectiveMethodResolver); + this.methodResolvers = defaultResolvers; + } + } + + private void ensureConstructorResolversInitialized() { + if (this.constructorResolvers == null) { + initializeConstructorResolvers(); + } + } + + private synchronized void initializeConstructorResolvers() { + if (this.constructorResolvers == null) { + List defaultResolvers = new ArrayList(); + defaultResolvers.add(new ReflectiveConstructorResolver()); + this.constructorResolvers = defaultResolvers; + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardOperatorOverloader.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardOperatorOverloader.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardOperatorOverloader.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2012 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 { + + @Override + public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand) + throws EvaluationException { + return false; + } + + @Override + public Object operate(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException { + throw new EvaluationException("No operation overloaded by default"); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeComparator.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeComparator.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeComparator.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2013 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.math.BigDecimal; + +import org.springframework.expression.TypeComparator; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.SpelMessage; +import org.springframework.util.NumberUtils; + +/** + * A simple basic {@link TypeComparator} implementation. + * It supports comparison of Numbers and types implementing Comparable. + * + * @author Andy Clement + * @author Juergen Hoeller + * @author Giovanni Dall'Oglio Risso + * @since 3.0 + */ +public class StandardTypeComparator implements TypeComparator { + + @Override + 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; + } + + @Override + @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 at this point + } + + // Basic number comparisons + if (left instanceof Number && right instanceof Number) { + Number leftNumber = (Number) left; + Number rightNumber = (Number) right; + + if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) { + BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); + BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class); + return leftBigDecimal.compareTo(rightBigDecimal); + } + else if (leftNumber instanceof Double || rightNumber instanceof Double) { + return Double.compare(leftNumber.doubleValue(), rightNumber.doubleValue()); + } + else if (leftNumber instanceof Float || rightNumber instanceof Float) { + return Float.compare(leftNumber.floatValue(), rightNumber.floatValue()); + } + else if (leftNumber instanceof Long || rightNumber instanceof Long) { + // Don't call Long.compare here - only available on JDK 1.7+ + return compare(leftNumber.longValue(), rightNumber.longValue()); + } + else { + // Don't call Integer.compare here - only available on JDK 1.7+ + return compare(leftNumber.intValue(), rightNumber.intValue()); + } + } + + try { + if (left instanceof Comparable) { + return ((Comparable) left).compareTo(right); + } + } + catch (ClassCastException ex) { + throw new SpelEvaluationException(ex, SpelMessage.NOT_COMPARABLE, left.getClass(), right.getClass()); + } + + throw new SpelEvaluationException(SpelMessage.NOT_COMPARABLE, left.getClass(), right.getClass()); + } + + + private static int compare(int x, int y) { + return (x < y ? -1 : (x > y ? 1 : 0)); + } + + private static int compare(long x, long y) { + return (x < y ? -1 : (x > y ? 1 : 0)); + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeConverter.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeConverter.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeConverter.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2013 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.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; + + + /** + * Create a StandardTypeConverter for the default ConversionService. + */ + public StandardTypeConverter() { + synchronized (this) { + if (defaultConversionService == null) { + defaultConversionService = new DefaultConversionService(); + } + } + this.conversionService = defaultConversionService; + } + + /** + * Create a StandardTypeConverter for the given ConversionService. + * @param conversionService the ConversionService to delegate to + */ + public StandardTypeConverter(ConversionService conversionService) { + Assert.notNull(conversionService, "ConversionService must not be null"); + this.conversionService = conversionService; + } + + + @Override + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + return this.conversionService.canConvert(sourceType, targetType); + } + + @Override + public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) { + try { + return this.conversionService.convert(value, sourceType, targetType); + } + catch (ConversionException ex) { + throw new SpelEvaluationException( + ex, SpelMessage.TYPE_CONVERSION_ERROR, sourceType.toString(), targetType.toString()); + } + } + +} Index: 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeLocator.java =================================================================== diff -u --- 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeLocator.java (revision 0) +++ 3rdParty_sources/spring/org/springframework/expression/spel/support/StandardTypeLocator.java (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2014 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.Collections; +import java.util.LinkedList; +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 simple implementation of {@link 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 final ClassLoader classLoader; + + private final List knownPackagePrefixes = new LinkedList(); + + + /** + * Create a StandardTypeLocator for the default ClassLoader + * (typically, the thread context ClassLoader). + */ + public StandardTypeLocator() { + this(ClassUtils.getDefaultClassLoader()); + } + + /** + * Create a StandardTypeLocator for the given ClassLoader. + * @param classLoader the ClassLoader to delegate to + */ + public StandardTypeLocator(ClassLoader classLoader) { + this.classLoader = classLoader; + // Similar to when writing regular Java code, it only knows about java.lang by default + registerImport("java.lang"); + } + + + /** + * 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); + } + + /** + * Remove that specified prefix from this locator's list of imports. + * @param prefix the prefix to remove + */ + public void removeImport(String prefix) { + this.knownPackagePrefixes.remove(prefix); + } + + /** + * Return a list of all the import prefixes registered with this StandardTypeLocator. + * @return a list of registered import prefixes + */ + public List getImportPrefixes() { + return Collections.unmodifiableList(this.knownPackagePrefixes); + } + + + /** + * Find a (possibly unqualified) type reference - first using the type name as-is, + * then trying any registered prefixes if the type name cannot be found. + * @param typeName the type to locate + * @return the class object for the type + * @throws EvaluationException if the type cannot be found + */ + @Override + public Class findType(String typeName) throws EvaluationException { + String nameToLookup = typeName; + try { + return ClassUtils.forName(nameToLookup, this.classLoader); + } + catch (ClassNotFoundException ey) { + // try any registered prefixes before giving up + } + for (String prefix : this.knownPackagePrefixes) { + try { + nameToLookup = prefix + "." + typeName; + return ClassUtils.forName(nameToLookup, this.classLoader); + } + catch (ClassNotFoundException ex) { + // might be a different prefix + } + } + throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName); + } + +} Index: 3rdParty_sources/versions.txt =================================================================== diff -u -r2bcede3acbf80a5d06c659badf47c9df44171452 -r59bce63ef2931ab2a2eb6fab2efc3d952291c674 --- 3rdParty_sources/versions.txt (.../versions.txt) (revision 2bcede3acbf80a5d06c659badf47c9df44171452) +++ 3rdParty_sources/versions.txt (.../versions.txt) (revision 59bce63ef2931ab2a2eb6fab2efc3d952291c674) @@ -48,7 +48,7 @@ Servlet API 3.1 -Spring 2.5.6 +Spring 4.0.6 Struts 1.2.9