diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/AccessException.java b/org.springframework.expression/src/main/java/org/springframework/expression/AccessException.java new file mode 100644 index 00000000000..2a2e4d014a2 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/AccessException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2004-2008 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 + */ +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); + } + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/CacheablePropertyAccessor.java b/org.springframework.expression/src/main/java/org/springframework/expression/CacheablePropertyAccessor.java new file mode 100644 index 00000000000..4e654b54907 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/CacheablePropertyAccessor.java @@ -0,0 +1,57 @@ +package org.springframework.expression; + +// TODO (asc) Do we need a 'caching is allowed' option to be configurable at parse time? +/** + * A CacheablePropertyAccessor is an optimized PropertyAccessor where the two parts of accessing the + * property are separated: (1) resolving the property and (2) retrieving its value. In some cases there is + * a large cost to discovering which property an expression refers to and once discovered it will + * always resolve to the same property. In these situations a CacheablePropertyAccessor enables the + * resolution to be done once and a reusable object (an executor) returned that can be called over and + * over to retrieve the property value without going through resolution again. + *

+ * + * @author Andy Clement + */ +public abstract class CacheablePropertyAccessor implements PropertyAccessor { + + /** + * Attempt to resolve the named property and return an executor that can be called to + * get the value of that property. Return null if the property cannot be resolved. + * + * @param context the evaluation context + * @param target the target upon which the property is being accessed + * @param name the name of the property being accessed + * @return a reusable executor that can retrieve the property value + */ + public abstract PropertyReaderExecutor getReaderAccessor(EvaluationContext context, Object target, Object name); + + /** + * Attempt to resolve the named property and return an executor that can be called to + * set the value of that property. Return null if the property cannot be resolved. + * + * @param context the evaluation context + * @param target the target upon which the property is being accessed + * @param name the name of the property to be set + * @return a reusable executor that can set the property value + */ + public abstract PropertyWriterExecutor getWriterAccessor(EvaluationContext context, Object target, Object name); + + // Implementation of PropertyAccessor follows, based on the resolver/executor model + + public final boolean canRead(EvaluationContext context, Object target, Object name) throws AccessException { + return getReaderAccessor(context,target,name) != null; + } + + public final boolean canWrite(EvaluationContext context, Object target, Object name) throws AccessException { + return getWriterAccessor(context,target,name) != null; + } + + public final Object read(EvaluationContext context, Object target, Object name) throws AccessException { + return getReaderAccessor(context,target,name).execute(context,target); + } + + public final void write(EvaluationContext context, Object target, Object name, Object newValue) throws AccessException { + getWriterAccessor(context,target,name).execute(context,target,newValue); + } + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorExecutor.java new file mode 100644 index 00000000000..11e1225f63e --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorExecutor.java @@ -0,0 +1,43 @@ +/* + * Copyright 2004-2008 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 (asc) 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 + */ +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 + */ + Object execute(EvaluationContext context, Object... arguments) throws AccessException; + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorResolver.java new file mode 100644 index 00000000000..87037c64c55 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorResolver.java @@ -0,0 +1,39 @@ +/* + * Copyright 2004-2008 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 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 + */ +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 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, Class[] argumentTypes) + throws AccessException; + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java b/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java new file mode 100644 index 00000000000..d6ac1f80e93 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java @@ -0,0 +1,87 @@ +/* + * Copyright 2004-2008 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.expression.spel.standard.StandardEvaluationContext; +import org.springframework.expression.spel.standard.StandardTypeUtilities; + +/** + * 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 StandardEvaluationContext} that can be extended, + * rather than having to implement everything. + * + * @author Andy Clement + */ +public interface EvaluationContext { + + /** + * @return the root context object against which unqualified properties/methods/etc should be resolved + */ + Object getRootContextObject(); + + /** + * @return a TypeUtilities implementation that can be used for looking up types, converting types, comparing types, + * and overloading basic operators for types. A standard implementation is provided in {@link StandardTypeUtilities} + */ + TypeUtils getTypeUtils(); + + /** + * Look up a named variable within this execution context. + * + * @param name variable to lookup + * @return the value of the variable + */ + Object lookupVariable(String name); + + /** + * Set a named variable within this execution 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); + + // TODO (asc) lookupReference() - is it too expensive to return all objects within a context? + /** + * Look up an object reference in a particular context. If no contextName is specified (null), assume the default + * context. If no objectName is specified (null), return all objects in the specified context (List). + * + * @param contextName the context in which to perform the lookup (or null for default context) + * @param objectName the object to lookup in the context (or null to get all objects) + * @return a specific object or List + */ + Object lookupReference(Object contextName, Object objectName) throws EvaluationException; + + /** + * @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(); + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationException.java b/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationException.java new file mode 100644 index 00000000000..264e0f7a0d6 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationException.java @@ -0,0 +1,100 @@ +/* + * Copyright 2004-2008 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 (asc) This class needs a better name? I might have used EvaluationException if it wasn't also used for parsing issues +/** + * Base class for exceptions occurring during expression parsing and evaluation. + * + * @author Andy Clement + */ +public class EvaluationException extends Exception { + + /** + * The expression string. + */ + private String expressionString; + + /** + * Creates a new expression exception. The expressionString field should be set by a later call to + * setExpressionString(). + * + * @param cause the underlying cause of this exception + */ + public EvaluationException(Throwable cause) { + super(cause); + } + + /** + * Creates a new expression parsing exception. + * + * @param expressionString the expression string that could not be parsed + * @param cause the underlying cause of this exception + */ + public EvaluationException(String expressionString, Throwable cause) { + this(expressionString, "Exception occurred whilst handling '" + expressionString + "'", cause); + } + + /** + * Creates a new expression exception. + * + * @param expressionString the expression string + * @param message a descriptive message + * @param cause the underlying cause of this exception + */ + public EvaluationException(String expressionString, String message, Throwable cause) { + super(message, cause); + this.expressionString = expressionString; + } + + /** + * Creates a new expression exception. + * + * @param expressionString the expression string + * @param message a descriptive message + */ + public EvaluationException(String expressionString, String message) { + super(message); + this.expressionString = expressionString; + } + + /** + * Creates a new expression exception. The expressionString field should be set by a later call to + * setExpressionString(). + * + * @param message a descriptive message + */ + public EvaluationException(String message) { + super(message); + } + + /** + * Set the expression string, called on exceptions where the expressionString is not known at the time of exception + * creation. + * + * @param expressionString the expression string + */ + protected final void setExpressionString(String expressionString) { + this.expressionString = expressionString; + } + + /** + * @return the expression string + */ + public final String getExpressionString() { + return expressionString; + } +} \ No newline at end of file diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/MethodExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/MethodExecutor.java new file mode 100644 index 00000000000..99bf35aabc6 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/MethodExecutor.java @@ -0,0 +1,43 @@ +/* + * Copyright 2004-2008 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 + */ +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 methodArguments 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 + */ + Object execute(EvaluationContext context, Object target, Object... methodArguments) throws AccessException; + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/MethodResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/MethodResolver.java new file mode 100644 index 00000000000..e766214eafb --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/MethodResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2008 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 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 + */ +public interface MethodResolver { + + /** + * Within the supplied context determine a suitable method on the supplied object that can handle the specified + * arguments. Return a MethodExecutor that can be used to invoke that method (or 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, Class[] argumentTypes) throws AccessException; + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/Operation.java b/org.springframework.expression/src/main/java/org/springframework/expression/Operation.java new file mode 100644 index 00000000000..4fd211bd67c --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/Operation.java @@ -0,0 +1,26 @@ +/* + * Copyright 2004-2008 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 + */ +public enum Operation { + ADD, SUBTRACT, DIVIDE, MULTIPLY, MODULUS; +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/OperatorOverloader.java b/org.springframework.expression/src/main/java/org/springframework/expression/OperatorOverloader.java new file mode 100644 index 00000000000..022cdc38f1c --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/OperatorOverloader.java @@ -0,0 +1,38 @@ +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 + */ +public interface OperatorOverloader { + + // TODO (asc) does this need a better type name? + // TODO (asc) needs some testing! + + /** + * 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; +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/ParseException.java b/org.springframework.expression/src/main/java/org/springframework/expression/ParseException.java new file mode 100644 index 00000000000..30531d2d141 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/ParseException.java @@ -0,0 +1,90 @@ +/* + * Copyright 2004-2008 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 (asc) This class needs a better name? I might have used EvaluationException if it wasn't also used for parsing issues +/** + * Base class for exceptions occurring during expression parsing and evaluation. + * + * @author Andy Clement + */ +public class ParseException extends Exception { + + /** + * The expression string. + */ + private String expressionString; + + /** + * Creates a new expression exception. The expressionString field should be set by a later call to + * setExpressionString(). + * + * @param cause the underlying cause of this exception + */ + public ParseException(Throwable cause) { + super(cause); + } + + /** + * Creates a new expression parsing exception. + * + * @param expressionString the expression string that could not be parsed + * @param cause the underlying cause of this exception + */ + public ParseException(String expressionString, Throwable cause) { + this(expressionString, "Exception occurred whilst handling '" + expressionString + "'", cause); + } + + /** + * Creates a new expression exception. + * + * @param expressionString the expression string + * @param message a descriptive message + * @param cause the underlying cause of this exception + */ + public ParseException(String expressionString, String message, Throwable cause) { + super(message, cause); + this.expressionString = expressionString; + } + + /** + * Creates a new expression exception. + * + * @param expressionString the expression string + * @param message a descriptive message + */ + public ParseException(String expressionString, String message) { + super(message); + this.expressionString = expressionString; + } + + /** + * Set the expression string, called on exceptions where the expressionString is not known at the time of exception + * creation. + * + * @param expressionString the expression string + */ + protected final void setExpressionString(String expressionString) { + this.expressionString = expressionString; + } + + /** + * @return the expression string + */ + public final String getExpressionString() { + return expressionString; + } +} \ No newline at end of file diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java new file mode 100644 index 00000000000..dd54f38c6b7 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression; + +/** + * A property accessor is able to read (and possibly write) to object properties. The interface places no restrictions + * and so implementors are free to access properties directly as fields or through getters or in any other way they see + * as appropriate. A resolver can optionally specify an array of target classes for which it should be called - but if + * it returns null from getSpecificTargetClasses() then it will be called for all property references and given a chance + * to determine if it can read or write them. Property resolvers are considered to be ordered and each will be called in + * turn. The only rule that affects the call order is that any naming the target class directly in + * getSpecifiedTargetClasses() will be called first, before the general resolvers. + *

+ * If the cost of locating the property is expensive, in relation to actually retrieving its value, consider + * extending CacheablePropertyAccessor rather than directly implementing PropertyAccessor. A CacheablePropertyAccessor + * enables the discovery (resolution) of the property to be done once and then an object (an executor) returned + * and cached by the infrastructure that can be used repeatedly to retrieve the property value. + * + * @author Andy Clement + */ +public interface PropertyAccessor { + + /** + * Return an array of classes for which this resolver should be called. Returning null indicates this is a general + * resolver that can be called in an attempt to resolve a property on any type. + * + * @return an array of classes that this resolver is suitable for (or null if a general resolver) + */ + @SuppressWarnings("unchecked") + public 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 + */ + public boolean canRead(EvaluationContext context, Object target, Object name) throws AccessException; + + /** + * Called to read a property from a specified target object + * + * @param context the evaluation context in which the access is being attempted + * @param target the target object upon which the property is being accessed + * @param name the name of the property being accessed + * @return Object the value of the property + * @throws AccessException if there is any problem accessing the property value + */ + public Object read(EvaluationContext context, Object target, Object 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 + */ + public boolean canWrite(EvaluationContext context, Object target, Object name) throws AccessException; + + /** + * Called to write to a property on a specified target object. Should only succeed if canWrite() also returns true. + * + * @param context the evaluation context in which the access is being attempted + * @param target the target object upon which the property is being accessed + * @param name the name of the property being accessed + * @param newValue the new value for the property + * @throws AccessException if there is any problem writing to the property value + */ + public void write(EvaluationContext context, Object target, Object name, Object newValue) throws AccessException; + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/PropertyReaderExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyReaderExecutor.java new file mode 100644 index 00000000000..d29f0ad9d19 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyReaderExecutor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2004-2008 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; + +/** + * If a property accessor is built upon the CacheablePropertyAccessor class then once the property + * has been resolved the accessor will return an instance of this PropertyReaderExecutor interface + * that can be cached and repeatedly called to access the value of the property. + *

+ * 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 + */ +public interface PropertyReaderExecutor { + + /** + * Return the value of a property for the specified target. + * + * @param context the evaluation context in which the command is being executed + * @param targetObject the target object on which property access is being attempted + * @return the property value + * @throws AccessException if there is a problem accessing the property or this executor has become stale + */ + Object execute(EvaluationContext context, Object targetObject) throws AccessException; + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/PropertyWriterExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyWriterExecutor.java new file mode 100644 index 00000000000..ad61d8ee4f5 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyWriterExecutor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2004-2008 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; + +/** + * If a property accessor is built upon the CacheablePropertyAccessor class then once the property + * has been resolved the accessor will return an instance of this PropertyWriterExecutor interface + * that can be cached and repeatedly called to set the value of the property. + *

+ * 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 + */ +public interface PropertyWriterExecutor { + + /** + * Set the value of a property to the supplied new value. + * + * @param context the evaluation context in which the command is being executed + * @param targetObject the target object on which property write is being attempted + * @param newValue the new value for the property + * @throws AccessException if there is a problem setting the property or this executor has become stale + */ + void execute(EvaluationContext evaluationContext, Object targetObject, Object newValue) throws AccessException; + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/TypeComparator.java b/org.springframework.expression/src/main/java/org/springframework/expression/TypeComparator.java new file mode 100644 index 00000000000..ef4329bf69d --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/TypeComparator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression; + +/** + * Instances of a type comparator should be able to compare pairs of objects for equality, the specification of the + * return value is the same as for {@link Comparable}. + * + * @author Andy Clement + */ +public interface TypeComparator { + + /** + * Compare two objects. + * + * @param firstObject the first object + * @param secondObject the second object + * @return 0 if they are equal, <0 if the first is smaller than the second, or >0 if the first is larger than the + * second + * @throws EvaluationException if a problem occurs during comparison (or they are not comparable) + */ + int compare(Object firstObject, Object secondObject) throws EvaluationException; + + /** + * Return true if the comparator can compare these two objects + * + * @param firstObject the first object + * @param secondObject the second object + * @return true if the comparator can compare these objects + */ + public boolean canCompare(Object firstObject, Object secondObject); + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/TypeConverter.java b/org.springframework.expression/src/main/java/org/springframework/expression/TypeConverter.java new file mode 100644 index 00000000000..e11f016fd4b --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/TypeConverter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2008 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.expression.spel.standard.StandardIndividualTypeConverter; +import org.springframework.expression.spel.standard.StandardTypeConverter; + +/** + * A type converter can convert values between different types. There is a default implementation called {@link StandardTypeConverter} + * that supports some basic conversions. That default implementation can be extended through subclassing or + * via registration of new {@link StandardIndividualTypeConverter} instances with the StandardTypeConverter. + * + * @author Andy Clement + */ +public interface TypeConverter { +// TODO (asc) replace this stuff with Keiths spring-binding conversion code +// TODO (asc) should ExpressionException be thrown for lost precision in the case of coercion? + + /** + * Convert (may coerce) a value from one type to another, for example from a boolean to a string. + * + * @param value the value to be converted + * @param targetType the type that the value should be converted to if possible + * @return the converted value + * @throws EvaluationException if conversion is not possible + */ + Object convertValue(Object value, Class targetType) throws EvaluationException; + + + /** + * Return true if the type converter can convert the specified type to the desired target type. + * + * @param sourceType the type to be converted from + * @param targetType the type to be converted to + * @return true if that conversion can be performed + */ + public boolean canConvert(Class sourceType, Class targetType); + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/TypeLocator.java b/org.springframework.expression/src/main/java/org/springframework/expression/TypeLocator.java new file mode 100644 index 00000000000..b35fa022cfb --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/TypeLocator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2008 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.expression.spel.standard.StandardTypeLocator; + +/** + * Implementors of this interface are expected to be able to locate types. They may use custom classloaders or the + * and deal with common package prefixes (java.lang, etc) however they wish. See + * {@link StandardTypeLocator} for an example implementation. + * + * @author Andy Clement + */ +public interface TypeLocator { + + /** + * Find a type by name. The name may or may not be fully qualified (eg. String or java.lang.String) + * + * @param type the type to be located + * @return the class object representing that type + * @throw ExpressionException if there is a problem finding it + */ + Class findType(String typename) throws EvaluationException; + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/TypeUtils.java b/org.springframework.expression/src/main/java/org/springframework/expression/TypeUtils.java new file mode 100644 index 00000000000..13d55d5a07a --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/TypeUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright 2004-2008 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; + +/** + * TypeUtilities brings together the various kinds of type related function that may occur + * whilst working with expressions. An implementor is providing support for four type related + * facilities: + *

    + *
  • a mechanism for finding types + *
  • a mechanism for comparing types + *
  • a mechanism for type conversion/coercion + *
  • a mechanism for overloading mathematical operations (add/subtract/etc) + *
+ * + * @author Andy Clement + */ +public interface TypeUtils { + + /** + * @return a type locator that can be used to find types, either by short or fully qualified name. + */ + TypeLocator getTypeLocator(); + + /** + * @return a type comparator for comparing pairs of objects for equality. + */ + TypeComparator getTypeComparator(); + + /** + * @return a type converter that can convert (or coerce) a value from one type to another. + */ + TypeConverter getTypeConverter(); + + /** + * @return an operator overloader that may support mathematical operations between more than the standard set of + * types + */ + OperatorOverloader getOperatorOverloader(); + +}