Compare commits

...

9 Commits

15 changed files with 1214 additions and 1172 deletions

6
.gitignore vendored
View File

@ -1,2 +1,4 @@
target/* target/*
.pmd .pmd
.scala_dependencies
.settings/

View File

@ -3,6 +3,18 @@ SpelHelper provides additional functionalities to work with
SpelHelper is available under GNU Lesser General Public License (GNU LGPL). SpelHelper is available under GNU Lesser General Public License (GNU LGPL).
**Maven Usage**
To use SpelHelper with Maven, add the following snippet in the dependencies section of your project's POM file:
<dependency>
<groupId>net.abhinavsarkar</groupId>
<artifactId>SpelHelper</artifactId>
<version>1.1</version>
</dependency>
**Functionalities**
The addition functionalities provided are: The addition functionalities provided are:
1. Implicit methods 1. Implicit methods
@ -10,7 +22,7 @@ The addition functionalities provided are:
3. Simplified extension functions 3. Simplified extension functions
4. Simplified constructors 4. Simplified constructors
**Implicit Methods** _**Implicit methods**_
Implicit methods allow one to registers methods with SpelHelper and attach Implicit methods allow one to registers methods with SpelHelper and attach
them to particular classes. After that, when that method is called on an them to particular classes. After that, when that method is called on an
@ -27,7 +39,7 @@ So when an expression like `"#list(1,4,2).sorted()"` is evaluated, the
first parameter and its return value is used in further evaluation of the first parameter and its return value is used in further evaluation of the
expression. expression.
**Implicit Properties** _**Implicit properties**_
Implicit properties allow one to treat no argument methods of an object Implicit properties allow one to treat no argument methods of an object
as properties of the object. SpelHelper intercepts the property resolution as properties of the object. SpelHelper intercepts the property resolution
@ -42,7 +54,7 @@ the same value as the last example.
Implicit property resolution considers both the actual methods of the object Implicit property resolution considers both the actual methods of the object
and the implicit methods registered on the object's class. and the implicit methods registered on the object's class.
**Simplified extension functions** _**Simplified extension functions**_
SpEL [allows][2] to register extension function on the context by providing a SpEL [allows][2] to register extension function on the context by providing a
name and a java.lang.reflect.Method object. SpelHelper simplifies this by taking a class name and a java.lang.reflect.Method object. SpelHelper simplifies this by taking a class
@ -54,7 +66,7 @@ registered by SpelHelper. Hence the method [ExtensionFunctions#list(Object...)][
can be called from inside a SpEL expression using the function call syntax: can be called from inside a SpEL expression using the function call syntax:
`"#list(1,2,3)`". `"#list(1,2,3)`".
**Simplified constructors** _**Simplified constructors**_
SpEL [allows][3] calling constructors from inside a SpEL expression using the SpEL [allows][3] calling constructors from inside a SpEL expression using the
`new` operator. But they have to be called with their full name like: `new` operator. But they have to be called with their full name like:

View File

@ -3,7 +3,7 @@
<groupId>net.abhinavsarkar</groupId> <groupId>net.abhinavsarkar</groupId>
<artifactId>SpelHelper</artifactId> <artifactId>SpelHelper</artifactId>
<version>1.0</version> <version>1.3-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>SpelHelper</name> <name>SpelHelper</name>
@ -19,6 +19,7 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<scala.version>2.7.7</scala.version> <scala.version>2.7.7</scala.version>
<spring.version>3.0.6.RELEASE</spring.version>
</properties> </properties>
<issueManagement> <issueManagement>
@ -189,6 +190,7 @@
<groupId>org.scala-lang</groupId> <groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId> <artifactId>scala-library</artifactId>
<version>${scala.version}</version> <version>${scala.version}</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.scalatest</groupId> <groupId>org.scalatest</groupId>
@ -205,12 +207,12 @@
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId> <artifactId>spring-core</artifactId>
<version>3.0.2.RELEASE</version> <version>${spring.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId> <artifactId>spring-expression</artifactId>
<version>3.0.2.RELEASE</version> <version>${spring.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -1,97 +1,97 @@
/* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net> /* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net>
* *
* This file is a part of SpelHelper library. * This file is a part of SpelHelper library.
* *
* SpelHelper library is free software: you can redistribute it and/or modify * SpelHelper library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License (GNU LGPL) as * it under the terms of the GNU Lesser General Public License (GNU LGPL) as
* published by the Free Software Foundation, either version 3 of the License, * published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version. * or (at your option) any later version.
* *
* SpelHelper library is distributed in the hope that it will be useful, * SpelHelper library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>. * along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet; import static java.util.Collections.unmodifiableSet;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Provides some extension functions to create some basic collection types * Provides some extension functions to create some basic collection types
* inline in SpEL expressions. * inline in SpEL expressions.
* These functions are automatically registered with {@link SpelHelper}. * These functions are automatically registered with {@link SpelHelper}.
* *
* **See Also:** * **See Also:**
* [Spring Docs on extension functions](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html#expressions-ref-functions) * [Spring Docs on extension functions](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html#expressions-ref-functions)
* @author Abhinav Sarkar _abhinav@abhinavsarkar.net_ * @author Abhinav Sarkar _abhinav@abhinavsarkar.net_
*/ */
public final class ExtensionFunctions { public final class ExtensionFunctions {
private ExtensionFunctions() { private ExtensionFunctions() {
} }
/** /**
* Creates an unmodifiable {@link List} of the arguments provided. * Creates an unmodifiable {@link List} of the arguments provided.
* *
* Example use: `"#list('one', 'two', 'three')"` * Example use: `"#list('one', 'two', 'three')"`
* @param <T> Type of the arguments provided. * @param <T> Type of the arguments provided.
* @param args Arguments to create list of. * @param args Arguments to create list of.
* @return An unmodifiable list of the arguments provided. * @return An unmodifiable list of the arguments provided.
*/ */
public static <T> List<T> list(final T... args) { public static <T> List<T> list(final T... args) {
return unmodifiableList(Arrays.asList(args)); return unmodifiableList(Arrays.asList(args));
} }
/** /**
* Creates an unmodifiable {@link Set} of the arguments provided. * Creates an unmodifiable {@link Set} of the arguments provided.
* *
* Example use: `"#set('one', 'two', 'three')"` * Example use: `"#set('one', 'two', 'three')"`
* @param <T> Type of the arguments provided. * @param <T> Type of the arguments provided.
* @param args Arguments to create set of. * @param args Arguments to create set of.
* @return An unmodifiable set of the arguments provided. * @return An unmodifiable set of the arguments provided.
*/ */
public static <T> Set<T> set(final T... args) { public static <T> Set<T> set(final T... args) {
return unmodifiableSet(new HashSet<T>(list(args))); return unmodifiableSet(new HashSet<T>(list(args)));
} }
/** /**
* Creates an unmodifiable {@link Map} using the {@link List} of keys * Creates an unmodifiable {@link Map} using the {@link List} of keys
* provided as the first argument and the {@link List} of values provided * provided as the first argument and the {@link List} of values provided
* as the second argument. * as the second argument.
* *
* Example use: `"#map(#list('one', 'two', 'three'), #list(1, 2, 3))"` * Example use: `"#map(#list('one', 'two', 'three'), #list(1, 2, 3))"`
* @param <K> Type of the keys of the map. * @param <K> Type of the keys of the map.
* @param <V> Type of the values of map. * @param <V> Type of the values of map.
* @param keys List of the keys. * @param keys List of the keys.
* @param values List of the values. * @param values List of the values.
* @return A unmodifiable map created from the key and value lists. * @return A unmodifiable map created from the key and value lists.
* @throws IllegalArgumentException if the number of keys and the number of * @throws IllegalArgumentException if the number of keys and the number of
* values is not equal. * values is not equal.
*/ */
public static <K,V> Map<K,V> map(final List<? extends K> keys, public static <K,V> Map<K,V> map(final List<? extends K> keys,
final List<? extends V> values) { final List<? extends V> values) {
Assert.isTrue(keys.size() == values.size(), Assert.isTrue(keys.size() == values.size(),
"There should be equal number of keys and values"); "There should be equal number of keys and values");
Map<K,V> map = new HashMap<K,V>(); Map<K,V> map = new HashMap<K,V>();
int length = keys.size(); int length = keys.size();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
map.put(keys.get(i), values.get(i)); map.put(keys.get(i), values.get(i));
} }
return unmodifiableMap(map); return unmodifiableMap(map);
} }
} }

View File

@ -1,48 +1,51 @@
/* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net> /* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net>
* *
* This file is a part of SpelHelper library. * This file is a part of SpelHelper library.
* *
* SpelHelper library is free software: you can redistribute it and/or modify * SpelHelper library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License (GNU LGPL) as * it under the terms of the GNU Lesser General Public License (GNU LGPL) as
* published by the Free Software Foundation, either version 3 of the License, * published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version. * or (at your option) any later version.
* *
* SpelHelper library is distributed in the hope that it will be useful, * SpelHelper library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>. * along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Arrays; import java.util.List;
import org.springframework.expression.AccessException; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.ConstructorExecutor; import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorResolver; import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.spel.support.ReflectiveConstructorResolver; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.ReflectiveConstructorResolver;
final class ImplicitConstructorResolver implements
ConstructorResolver { final class ImplicitConstructorResolver implements ConstructorResolver {
private final ReflectiveConstructorResolver delegate = new ReflectiveConstructorResolver(); private final ReflectiveConstructorResolver delegate = new ReflectiveConstructorResolver();
public ConstructorExecutor resolve(final EvaluationContext context, @Override
final String typeName, final Class<?>[] argumentTypes) throws AccessException { public ConstructorExecutor resolve(
try { final EvaluationContext context, final String typeName,
return delegate.resolve(context, typeName, argumentTypes); final List<TypeDescriptor> argumentTypes)
} catch (AccessException ex) { throws AccessException {
Object variable = ((SpelHelper) context.lookupVariable(SpelHelper.CONTEXT_LOOKUP_KEY)) try {
.lookupImplicitConstructor(typeName + Arrays.toString(argumentTypes)); return delegate.resolve(context, typeName, argumentTypes);
if (variable instanceof Constructor<?>) { } catch (AccessException ex) {
Constructor<?> constructor = (Constructor<?>) variable; Object variable = ((SpelHelper) context.lookupVariable(SpelHelper.CONTEXT_LOOKUP_KEY))
return delegate.resolve(context, constructor.getDeclaringClass().getName(), argumentTypes); .lookupImplicitConstructor(typeName + argumentTypes.toString());
} if (variable instanceof Constructor<?>) {
return null; Constructor<?> constructor = (Constructor<?>) variable;
} return delegate.resolve(context, constructor.getDeclaringClass().getName(), argumentTypes);
} }
return null;
}
}
} }

View File

@ -1,113 +1,121 @@
/* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net> /* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net>
* *
* This file is a part of SpelHelper library. * This file is a part of SpelHelper library.
* *
* SpelHelper library is free software: you can redistribute it and/or modify * SpelHelper library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License (GNU LGPL) as * it under the terms of the GNU Lesser General Public License (GNU LGPL) as
* published by the Free Software Foundation, either version 3 of the License, * published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version. * or (at your option) any later version.
* *
* SpelHelper library is distributed in the hope that it will be useful, * SpelHelper library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>. * along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentHashMap; import java.util.ArrayList;
import java.util.List;
import org.springframework.expression.AccessException; import java.util.concurrent.ConcurrentHashMap;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.MethodExecutor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.MethodResolver; import org.springframework.expression.AccessException;
import org.springframework.expression.TypedValue; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.ReflectiveMethodResolver; import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodResolver;
final class ImplicitMethodResolver implements MethodResolver { import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
private static final ConcurrentHashMap<String, MethodExecutor> CACHE =
new ConcurrentHashMap<String, MethodExecutor>(); final class ImplicitMethodResolver implements MethodResolver {
private static final MethodExecutor NULL_ME = new MethodExecutor() { private static final ConcurrentHashMap<String, MethodExecutor> CACHE =
public TypedValue execute(final EvaluationContext context, final Object target, new ConcurrentHashMap<String, MethodExecutor>();
final Object... arguments) throws AccessException {
return null; private final ReflectiveMethodResolver delegate = new ReflectiveMethodResolver();
}
}; private static final MethodExecutor NULL_ME = new MethodExecutor() {
@Override
private static final class ImplicitMethodExecutor implements public TypedValue execute(final EvaluationContext context, final Object target,
MethodExecutor { final Object... arguments) throws AccessException {
private final MethodExecutor executor; throw new UnsupportedOperationException("This method should never be called");
}
public ImplicitMethodExecutor(final MethodExecutor executor) { };
this.executor = executor;
} private static final class ImplicitMethodExecutor implements MethodExecutor {
private final MethodExecutor executor;
public TypedValue execute(final EvaluationContext context, final Object target,
final Object... arguments) throws AccessException { public ImplicitMethodExecutor(final MethodExecutor executor) {
Object[] modifiedArguments = new Object[arguments.length + 1]; this.executor = executor;
modifiedArguments[0] = target; }
System.arraycopy(arguments, 0, modifiedArguments, 1, arguments.length);
return executor.execute(context, null, modifiedArguments); @Override
} public TypedValue execute(final EvaluationContext context, final Object target,
} final Object... arguments) throws AccessException {
Object[] modifiedArguments = new Object[arguments.length + 1];
public MethodExecutor resolve(final EvaluationContext context, modifiedArguments[0] = target;
final Object targetObject, final String name, final Class<?>[] argumentTypes) System.arraycopy(arguments, 0, modifiedArguments, 1, arguments.length);
throws AccessException { return executor.execute(context, null, modifiedArguments);
if (targetObject == null) { }
return null; }
}
Class<?> type = targetObject.getClass(); @Override
String cacheKey = type.getName() + "." + name; public MethodExecutor resolve(
if (CACHE.containsKey(cacheKey)) { final EvaluationContext context, final Object targetObject,
MethodExecutor executor = CACHE.get(cacheKey); final String name, final List<TypeDescriptor> argumentTypes)
return executor == NULL_ME ? null : executor; throws AccessException {
} if (targetObject == null) {
return null;
Method method = lookupMethod(context, type, name); }
if (method != null) { Class<?> type = targetObject.getClass();
int modifiers = method.getModifiers(); String cacheKey = type.getName() + "." + name;
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) { if (CACHE.containsKey(cacheKey)) {
Class<?>[] parameterTypes = method.getParameterTypes(); MethodExecutor executor = CACHE.get(cacheKey);
Class<?> firstParamType = parameterTypes[0]; return executor == NULL_ME ? null : executor;
if (parameterTypes.length > 0 }
&& firstParamType.isAssignableFrom(type)) {
Class<?>[] newArgumentTypes = new Class[argumentTypes.length + 1]; Method method = lookupMethod(context, type, name);
newArgumentTypes[0] = firstParamType; if (method != null) {
System.arraycopy(argumentTypes, 0, newArgumentTypes, int modifiers = method.getModifiers();
1, argumentTypes.length); if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) {
MethodExecutor executor = new ReflectiveMethodResolver() Class<?>[] parameterTypes = method.getParameterTypes();
.resolve(context, method.getDeclaringClass(), name, Class<?> firstParamType = parameterTypes[0];
newArgumentTypes); if (parameterTypes.length > 0
MethodExecutor wrappedExecutor = executor == null ? null && firstParamType.isAssignableFrom(type)) {
: new ImplicitMethodExecutor(executor); List<TypeDescriptor> newArgumentTypes = new ArrayList<TypeDescriptor>();
if (wrappedExecutor == null) { newArgumentTypes.add(TypeDescriptor.valueOf(firstParamType));
CACHE.putIfAbsent(cacheKey, NULL_ME); newArgumentTypes.addAll(argumentTypes);
}
return wrappedExecutor; MethodExecutor executor =
} delegate.resolve(context, method.getDeclaringClass(),
} name, newArgumentTypes);
} MethodExecutor wrappedExecutor = executor == null ? null
CACHE.putIfAbsent(cacheKey, NULL_ME); : new ImplicitMethodExecutor(executor);
return null; if (wrappedExecutor == null) {
} CACHE.putIfAbsent(cacheKey, NULL_ME);
}
private static Method lookupMethod(final EvaluationContext context, return wrappedExecutor;
final Class<?> type, final String name) { }
for (Class<?> clazz : InheritenceUtil.getInheritance(type)) { }
Object variable = ((SpelHelper) context.lookupVariable(SpelHelper.CONTEXT_LOOKUP_KEY)) }
.lookupImplicitMethod(clazz.getName() + "." + name); CACHE.putIfAbsent(cacheKey, NULL_ME);
if (variable instanceof Method) { return null;
return (Method) variable; }
}
} private static Method lookupMethod(final EvaluationContext context,
return null; final Class<?> type, final String name) {
} for (Class<?> clazz : InheritenceUtil.getInheritance(type)) {
Object variable = ((SpelHelper) context.lookupVariable(SpelHelper.CONTEXT_LOOKUP_KEY))
.lookupImplicitMethod(clazz.getName() + "." + name);
if (variable instanceof Method) {
return (Method) variable;
}
}
return null;
}
} }

View File

@ -1,129 +1,129 @@
/* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net> /* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net>
* *
* This file is a part of SpelHelper library. * This file is a part of SpelHelper library.
* *
* SpelHelper library is free software: you can redistribute it and/or modify * SpelHelper library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License (GNU LGPL) as * it under the terms of the GNU Lesser General Public License (GNU LGPL) as
* published by the Free Software Foundation, either version 3 of the License, * published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version. * or (at your option) any later version.
* *
* SpelHelper library is distributed in the hope that it will be useful, * SpelHelper library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>. * along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableSet; import static java.util.Collections.unmodifiableSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
* Provides some implicit methods which can be invoked on the instances of * Provides some implicit methods which can be invoked on the instances of
* class of the first parameter of the method inside a SpEL expression. * class of the first parameter of the method inside a SpEL expression.
* @author Abhinav Sarkar _abhinav@abhinavsarkar.net_ * @author Abhinav Sarkar _abhinav@abhinavsarkar.net_
*/ */
public final class ImplicitMethods { public final class ImplicitMethods {
private ImplicitMethods() { private ImplicitMethods() {
} }
/** /**
* Provides implicit method `distinct` on the {@link List} class. * Provides implicit method `distinct` on the {@link List} class.
* *
* Example: `"#list('a','b','a').distinct()" //should return List('a','b')` * Example: `"#list('a','b','a').distinct()" //should return List('a','b')`
* *
* With implicit property support provided by {@link SpelHelper} this can * With implicit property support provided by {@link SpelHelper} this can
* also be written as: * also be written as:
* *
* `"#list('a','b','a').distinct" //same output as earlier` * `"#list('a','b','a').distinct" //same output as earlier`
* @param <T> Type of the list's elements. * @param <T> Type of the list's elements.
* @param list The list to call this method upon. * @param list The list to call this method upon.
* @return An unmodifiable {@link Set} containing the distinct items of the list. * @return An unmodifiable {@link Set} containing the distinct items of the list.
*/ */
public static <T> Set<T> distinct(final List<? extends T> list) { public static <T> Set<T> distinct(final List<? extends T> list) {
return unmodifiableSet(new HashSet<T>(list)); return unmodifiableSet(new HashSet<T>(list));
} }
/** /**
* Provides implicit method `sorted` on the {@link List} class. * Provides implicit method `sorted` on the {@link List} class.
* *
* Example: `"#list('c','b','a').sorted()" //should return List('a','b','c')` * Example: `"#list('c','b','a').sorted()" //should return List('a','b','c')`
* *
* With implicit property support provided by {@link SpelHelper} this can * With implicit property support provided by {@link SpelHelper} this can
* also be written as: * also be written as:
* *
* `"#list('c','b','a').sorted" //same output as earlier` * `"#list('c','b','a').sorted" //same output as earlier`
* @param <T> Type of the list's elements. * @param <T> Type of the list's elements.
* @param list The list to call this method upon. * @param list The list to call this method upon.
* @return An unmodifiable {@link List} containing the sorted items * @return An unmodifiable {@link List} containing the sorted items
* of the list. * of the list.
* @see Collections#sort(List) * @see Collections#sort(List)
*/ */
public static <T extends Comparable<? super T>> List<T> sorted( public static <T extends Comparable<? super T>> List<T> sorted(
final List<? extends T> list) { final List<? extends T> list) {
List<T> temp = new ArrayList<T>(list); List<T> temp = new ArrayList<T>(list);
Collections.sort(temp); Collections.sort(temp);
return unmodifiableList(temp); return unmodifiableList(temp);
} }
/** /**
* Provides implicit method `reversed` on the {@link List} class. * Provides implicit method `reversed` on the {@link List} class.
* *
* Example: `"#list('c','b','a').reversed()" //should return List('a','b','c')` * Example: `"#list('c','b','a').reversed()" //should return List('a','b','c')`
* *
* With implicit property support provided by {@link SpelHelper} this can * With implicit property support provided by {@link SpelHelper} this can
* also be written as: * also be written as:
* *
* `"#list('c','b','a').reversed" //same output as earlier` * `"#list('c','b','a').reversed" //same output as earlier`
* @param <T> Type of the list's elements. * @param <T> Type of the list's elements.
* @param list The list to call this method upon. * @param list The list to call this method upon.
* @return An unmodifiable {@link List} containing the items of the * @return An unmodifiable {@link List} containing the items of the
* list in reverse order. * list in reverse order.
* @see Collections#reverse(List) * @see Collections#reverse(List)
*/ */
public static <T> List<T> reversed(final List<? extends T> list) { public static <T> List<T> reversed(final List<? extends T> list) {
List<T> temp = new ArrayList<T>(list); List<T> temp = new ArrayList<T>(list);
Collections.reverse(temp); Collections.reverse(temp);
return unmodifiableList(temp); return unmodifiableList(temp);
} }
/** /**
* Provides implicit method `take` on the {@link List} class. * Provides implicit method `take` on the {@link List} class.
* *
* Example: `"#list('c','b','a').take(2)" //should return List('a','b')` * Example: `"#list('c','b','a').take(2)" //should return List('a','b')`
* *
* @param <T> Type of the list's elements. * @param <T> Type of the list's elements.
* @param list The list to call this method upon. * @param list The list to call this method upon.
* @param n Number of items to _take_ from the list. * @param n Number of items to _take_ from the list.
* @return An unmodifiable {@link List} containing the first `n` items * @return An unmodifiable {@link List} containing the first `n` items
* of the list. * of the list.
*/ */
public static <T> List<T> take(final List<T> list, final int n) { public static <T> List<T> take(final List<T> list, final int n) {
return unmodifiableList(list.subList(0, n)); return unmodifiableList(list.subList(0, n));
} }
/** /**
* Provides implicit method `drop` on the {@link List} class. * Provides implicit method `drop` on the {@link List} class.
* *
* Example: `"#list('c','b','a').drop(2)" //should return List('a')` * Example: `"#list('c','b','a').drop(2)" //should return List('a')`
* *
* @param <T> Type of the list's elements. * @param <T> Type of the list's elements.
* @param list The list to call this method upon. * @param list The list to call this method upon.
* @param n Number of items to _drop_ from the list. * @param n Number of items to _drop_ from the list.
* @return An unmodifiable {@link List} containing the items after the * @return An unmodifiable {@link List} containing the items after the
* first `n` items of the list. * first `n` items of the list.
*/ */
public static <T> List<T> drop(final List<T> list, final int n) { public static <T> List<T> drop(final List<T> list, final int n) {
return unmodifiableList(list.subList(n, list.size())); return unmodifiableList(list.subList(n, list.size()));
} }
} }

View File

@ -1,67 +1,80 @@
/* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net> /* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net>
* *
* This file is a part of SpelHelper library. * This file is a part of SpelHelper library.
* *
* SpelHelper library is free software: you can redistribute it and/or modify * SpelHelper library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License (GNU LGPL) as * it under the terms of the GNU Lesser General Public License (GNU LGPL) as
* published by the Free Software Foundation, either version 3 of the License, * published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version. * or (at your option) any later version.
* *
* SpelHelper library is distributed in the hope that it will be useful, * SpelHelper library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>. * along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.concurrent.ConcurrentHashMap; import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.MethodExecutor; import org.springframework.expression.AccessException;
import org.springframework.expression.MethodResolver; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue; import org.springframework.expression.MethodExecutor;
import org.springframework.util.Assert; import org.springframework.expression.MethodResolver;
import org.springframework.expression.TypedValue;
final class ImplicitPropertyAccessor extends ReadOnlyGenericPropertyAccessor { import org.springframework.util.Assert;
private static final ConcurrentHashMap<String, MethodExecutor> CACHE = final class ImplicitPropertyAccessor extends ReadOnlyGenericPropertyAccessor {
new ConcurrentHashMap<String, MethodExecutor>();
private static final ConcurrentHashMap<String, MethodExecutor> CACHE =
public boolean canRead(final EvaluationContext context, new ConcurrentHashMap<String, MethodExecutor>();
final Object target, final String name)
throws AccessException { private static final MethodExecutor NULL_ME = new MethodExecutor() {
Assert.notNull(target, "target is null"); @Override
String cacheKey = target.getClass().getName() + "." + name; public TypedValue execute(final EvaluationContext context, final Object target,
if (CACHE.containsKey(cacheKey)) { final Object... arguments) throws AccessException {
return CACHE.get(cacheKey) != null; throw new UnsupportedOperationException("This method should never be called");
} }
};
for (MethodResolver mr : context.getMethodResolvers()) {
MethodExecutor me = mr.resolve(context, target, name, new Class[0]); @Override
if (me != null) { public boolean canRead(final EvaluationContext context,
CACHE.putIfAbsent(cacheKey, me); final Object target, final String name)
return true; throws AccessException {
} Assert.notNull(target, "target is null");
} String cacheKey = target.getClass().getName() + "." + name;
if (CACHE.containsKey(cacheKey)) {
CACHE.putIfAbsent(cacheKey, null); return CACHE.get(cacheKey) != NULL_ME;
return false; }
}
for (MethodResolver mr : context.getMethodResolvers()) {
public TypedValue read(final EvaluationContext context, MethodExecutor me =
final Object target, final String name) mr.resolve(context, target, name, Collections.<TypeDescriptor>emptyList());
throws AccessException { if (me != null) {
if (canRead(context, target, name)) { CACHE.putIfAbsent(cacheKey, me);
String cacheKey = target.getClass().getName() + "." + name; return true;
return CACHE.get(cacheKey).execute(context, target, new Object[0]); }
} }
throw new AccessException(MessageFormat.format(
"Cannot read property: {0} of target: {1}", name, target)); CACHE.putIfAbsent(cacheKey, NULL_ME);
} return false;
}
@Override
public TypedValue read(final EvaluationContext context,
final Object target, final String name)
throws AccessException {
if (canRead(context, target, name)) {
String cacheKey = target.getClass().getName() + "." + name;
return CACHE.get(cacheKey).execute(context, target, new Object[0]);
}
throw new AccessException(MessageFormat.format(
"Cannot read property: {0} of target: {1}", name, target));
}
} }

View File

@ -1,86 +1,86 @@
/* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net> /* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net>
* *
* This file is a part of SpelHelper library. * This file is a part of SpelHelper library.
* *
* SpelHelper library is free software: you can redistribute it and/or modify * SpelHelper library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License (GNU LGPL) as * it under the terms of the GNU Lesser General Public License (GNU LGPL) as
* published by the Free Software Foundation, either version 3 of the License, * published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version. * or (at your option) any later version.
* *
* SpelHelper library is distributed in the hope that it will be useful, * SpelHelper library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>. * along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
final class InheritenceUtil { final class InheritenceUtil {
private InheritenceUtil() { private InheritenceUtil() {
} }
public static Set<Class<?>> getInheritance(final Class<?> clazz) { public static Set<Class<?>> getInheritance(final Class<?> clazz) {
LinkedHashSet<Class<?>> result = new LinkedHashSet<Class<?>>(); LinkedHashSet<Class<?>> result = new LinkedHashSet<Class<?>>();
result.add(clazz); result.add(clazz);
getInheritance(clazz, result); getInheritance(clazz, result);
return result; return result;
} }
/** /**
* Get inheritance of type. * Get inheritance of type.
* *
* @param clazz * @param clazz
* @param result * @param result
*/ */
private static void getInheritance(final Class<?> clazz, final Set<Class<?>> result) { private static void getInheritance(final Class<?> clazz, final Set<Class<?>> result) {
Class<?> superclass = getSuperclass(clazz); Class<?> superclass = getSuperclass(clazz);
if (superclass != null) { if (superclass != null) {
result.add(superclass); result.add(superclass);
getInheritance(superclass, result); getInheritance(superclass, result);
} }
getInterfaceInheritance(clazz, result); getInterfaceInheritance(clazz, result);
} }
/** /**
* Get interfaces that the type inherits from. * Get interfaces that the type inherits from.
* *
* @param clazz * @param clazz
* @param result * @param result
*/ */
private static void getInterfaceInheritance(final Class<?> clazz, private static void getInterfaceInheritance(final Class<?> clazz,
final Set<Class<?>> result) { final Set<Class<?>> result) {
for (Class<?> c : clazz.getInterfaces()) { for (Class<?> c : clazz.getInterfaces()) {
result.add(c); result.add(c);
getInterfaceInheritance(c, result); getInterfaceInheritance(c, result);
} }
} }
/** /**
* Get superclass of class. * Get superclass of class.
* *
* @param clazz * @param clazz
* @return * @return
*/ */
private static Class<?> getSuperclass(final Class<?> clazz) { private static Class<?> getSuperclass(final Class<?> clazz) {
if (clazz == null) { if (clazz == null) {
return null; return null;
} }
if (clazz.isArray() && clazz != Object[].class) { if (clazz.isArray() && clazz != Object[].class) {
Class<?> type = clazz.getComponentType(); Class<?> type = clazz.getComponentType();
while (type.isArray()) { while (type.isArray()) {
type = type.getComponentType(); type = type.getComponentType();
} }
return type; return type;
} }
return clazz.getSuperclass(); return clazz.getSuperclass();
} }
} }

View File

@ -1,45 +1,47 @@
/* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net> /* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net>
* *
* This file is a part of SpelHelper library. * This file is a part of SpelHelper library.
* *
* SpelHelper library is free software: you can redistribute it and/or modify * SpelHelper library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License (GNU LGPL) as * it under the terms of the GNU Lesser General Public License (GNU LGPL) as
* published by the Free Software Foundation, either version 3 of the License, * published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version. * or (at your option) any later version.
* *
* SpelHelper library is distributed in the hope that it will be useful, * SpelHelper library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>. * along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
import java.text.MessageFormat; import java.text.MessageFormat;
import org.springframework.expression.AccessException; import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor; import org.springframework.expression.PropertyAccessor;
abstract class ReadOnlyGenericPropertyAccessor implements abstract class ReadOnlyGenericPropertyAccessor implements PropertyAccessor {
PropertyAccessor {
@Override
public final boolean canWrite(final EvaluationContext context, public final boolean canWrite(final EvaluationContext context,
final Object target, final String name) throws AccessException { final Object target, final String name) throws AccessException {
return false; return false;
} }
@SuppressWarnings("rawtypes") @Override
public final Class[] getSpecificTargetClasses() { @SuppressWarnings("rawtypes")
return null; public final Class[] getSpecificTargetClasses() {
} return null;
}
public final void write(final EvaluationContext context, final Object target,
final String name, final Object newValue) throws AccessException { @Override
throw new AccessException(MessageFormat.format( public final void write(final EvaluationContext context, final Object target,
"Cannot write property: {0} of target: {1}", name, target)); final String name, final Object newValue) throws AccessException {
} throw new AccessException(MessageFormat.format(
"Cannot write property: {0} of target: {1}", name, target));
}
} }

View File

@ -1,349 +1,349 @@
/* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net> /* Copyright 2010 Abhinav Sarkar <abhinav@abhinavsarkar.net>
* *
* This file is a part of SpelHelper library. * This file is a part of SpelHelper library.
* *
* SpelHelper library is free software: you can redistribute it and/or modify * SpelHelper library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License (GNU LGPL) as * it under the terms of the GNU Lesser General Public License (GNU LGPL) as
* published by the Free Software Foundation, either version 3 of the License, * published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version. * or (at your option) any later version.
* *
* SpelHelper library is distributed in the hope that it will be useful, * SpelHelper library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>. * along with SpelHelper library. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.springframework.expression.ConstructorResolver; import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser; import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* SpelHelper provides additional functionalities to work with * SpelHelper provides additional functionalities to work with
* [Spring Expression Language (SpEL)][1]. * [Spring Expression Language (SpEL)][1].
* *
* The addition functionalities provided are: * The addition functionalities provided are:
* *
* 1. Implicit methods * 1. Implicit methods
* 2. Implicit properties * 2. Implicit properties
* 3. Simplified extension functions * 3. Simplified extension functions
* 4. Simplified constructors * 4. Simplified constructors
* *
* **Implicit Methods** * **Implicit Methods**
* *
* Implicit methods allow one to registers methods with SpelHelper and attach * Implicit methods allow one to registers methods with SpelHelper and attach
* them to particular classes. After that, when that method is called on an * them to particular classes. After that, when that method is called on an
* object of that particular class inside a SpEL expression, SpelHelper * object of that particular class inside a SpEL expression, SpelHelper
* redirects the method call to the registered method. * redirects the method call to the registered method.
* *
* Example: {@link ImplicitMethods#sorted(List)} method is automatically * Example: {@link ImplicitMethods#sorted(List)} method is automatically
* registered by SpelHelper. The class that the method should be invoked for * registered by SpelHelper. The class that the method should be invoked for
* is the type of the first parameter of the method. In this case, the class is * is the type of the first parameter of the method. In this case, the class is
* {@link List}. * {@link List}.
* *
* So when an expression like `"#list(1,4,2).sorted()"` is evaluated, the * So when an expression like `"#list(1,4,2).sorted()"` is evaluated, the
* {@link ImplicitMethods#sorted(List)} method is invoked with the list as its * {@link ImplicitMethods#sorted(List)} method is invoked with the list as its
* first parameter and its return value is used in further evaluation of the * first parameter and its return value is used in further evaluation of the
* expression. * expression.
* *
* See {@link SpelHelper#registerImplicitMethodsFromClass(Class)}. * See {@link SpelHelper#registerImplicitMethodsFromClass(Class)}.
* *
* **Implicit Properties** * **Implicit Properties**
* *
* Implicit properties allow one to treat no argument methods of an object * Implicit properties allow one to treat no argument methods of an object
* as properties of the object. SpelHelper intercepts the property resolution * as properties of the object. SpelHelper intercepts the property resolution
* of SpEL and if the property name is same as some no-arg method of the target * of SpEL and if the property name is same as some no-arg method of the target
* object then it invokes the method on the object and provides its return value * object then it invokes the method on the object and provides its return value
* as the property value for further evaluation of the expression. * as the property value for further evaluation of the expression.
* *
* Example: Using implicit properties, the example of implicit methods can be * Example: Using implicit properties, the example of implicit methods can be
* written as: `"#list(1,4,2).sorted"` - dropping the parens - and it will return * written as: `"#list(1,4,2).sorted"` - dropping the parens - and it will return
* the same value as the last example. * the same value as the last example.
* *
* Implicit property resolution considers both the actual methods of the object * Implicit property resolution considers both the actual methods of the object
* and the implicit methods registered on the object's class. * and the implicit methods registered on the object's class.
* *
* **Simplified extension functions** * **Simplified extension functions**
* *
* SpEL [allows][2] to register extension function on the context by providing a * SpEL [allows][2] to register extension function on the context by providing a
* name and a {@link Method} object. SpelHelper simplifies this by taking a class * name and a {@link Method} object. SpelHelper simplifies this by taking a class
* and registering all the `public static` methods of the class which do not * and registering all the `public static` methods of the class which do not
* have a `void` return type. The methods are registered by their simple name. * have a `void` return type. The methods are registered by their simple name.
* *
* Example: All the methods of {@link ExtensionFunctions} class are automatically * Example: All the methods of {@link ExtensionFunctions} class are automatically
* registered by SpelHelper. Hence the method {@link ExtensionFunctions#list(Object...)} * registered by SpelHelper. Hence the method {@link ExtensionFunctions#list(Object...)}
* can be called from inside a SpEL expression using the function call syntax: * can be called from inside a SpEL expression using the function call syntax:
* `"#list(1,2,3)`". * `"#list(1,2,3)`".
* *
* See {@link SpelHelper#registerFunctionsFromClass(Class)}. * See {@link SpelHelper#registerFunctionsFromClass(Class)}.
* *
* **Simplified constructors** * **Simplified constructors**
* *
* SpEL [allows][3] calling constructors from inside a SpEL expression using the * SpEL [allows][3] calling constructors from inside a SpEL expression using the
* `new` operator. But they have to be called with their full name like: * `new` operator. But they have to be called with their full name like:
* `"new org.example.Foo('bar')"`. SpelHelper simplifies this by taking a class * `"new org.example.Foo('bar')"`. SpelHelper simplifies this by taking a class
* and registering all its public constructors to the SpEL context by their * and registering all its public constructors to the SpEL context by their
* simple name. * simple name.
* *
* Example: After registering the `org.example.Foo` class with SpelHelper, its * Example: After registering the `org.example.Foo` class with SpelHelper, its
* constructor can be called from inside a SpEL expression by: `"new Foo('bar')"`. * constructor can be called from inside a SpEL expression by: `"new Foo('bar')"`.
* *
* See {@link SpelHelper#registerConstructorsFromClass(Class)}. * See {@link SpelHelper#registerConstructorsFromClass(Class)}.
* *
* In addition to all the above functionalities, SpelHelper automatically registers * In addition to all the above functionalities, SpelHelper automatically registers
* some extension functions and implicit methods which are always available in * some extension functions and implicit methods which are always available in
* the SpEL expressions evaluated through SpelHelper. See {@link ExtensionFunctions} * the SpEL expressions evaluated through SpelHelper. See {@link ExtensionFunctions}
* and {@link ImplicitMethods} for further details. * and {@link ImplicitMethods} for further details.
* *
* [1]: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html * [1]: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html
* [2]: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html#expressions-ref-functions * [2]: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html#expressions-ref-functions
* [3]: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html#d0e11927 * [3]: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html#d0e11927
* *
* @author Abhinav Sarkar _abhinav@abhinavsarkar.net_ * @author Abhinav Sarkar _abhinav@abhinavsarkar.net_
*/ */
public final class SpelHelper { public final class SpelHelper {
static final String CONTEXT_LOOKUP_KEY = SpelHelper.class.getName(); static final String CONTEXT_LOOKUP_KEY = SpelHelper.class.getName();
private final ExpressionParser PARSER = new SpelExpressionParser(); private final ExpressionParser PARSER = new SpelExpressionParser();
private static final ThreadLocal<EvaluationContext> CURRENT_CONTEXT = private static final ThreadLocal<EvaluationContext> CURRENT_CONTEXT =
new ThreadLocal<EvaluationContext>(); new ThreadLocal<EvaluationContext>();
private final Set<Method> registeredFunctions = new HashSet<Method>(); private final Set<Method> registeredFunctions = new HashSet<Method>();
private final Map<String,Method> registeredMethods = private final Map<String,Method> registeredMethods =
new ConcurrentHashMap<String, Method>(); new ConcurrentHashMap<String, Method>();
private final Map<String,Constructor<?>> registeredConstructors = private final Map<String,Constructor<?>> registeredConstructors =
new ConcurrentHashMap<String, Constructor<?>>(); new ConcurrentHashMap<String, Constructor<?>>();
/** /**
* Creates an instance of SpelHelper. * Creates an instance of SpelHelper.
*/ */
public SpelHelper() { public SpelHelper() {
registerFunctionsFromClass(ExtensionFunctions.class); registerFunctionsFromClass(ExtensionFunctions.class);
registerImplicitMethodsFromClass(ImplicitMethods.class); registerImplicitMethodsFromClass(ImplicitMethods.class);
} }
/** /**
* Registers the public static methods in the class `clazz` as implicit * Registers the public static methods in the class `clazz` as implicit
* methods for the class of the first parameter of the methods. * methods for the class of the first parameter of the methods.
* *
* Only registers the public static methods with non void return type and at * Only registers the public static methods with non void return type and at
* least one argument. * least one argument.
* @see ImplicitMethods * @see ImplicitMethods
* @param clazz The class to register the methods from. * @param clazz The class to register the methods from.
* @return The current instance of SpelHelper. This is for chaining * @return The current instance of SpelHelper. This is for chaining
* the methods calls. * the methods calls.
*/ */
public SpelHelper registerImplicitMethodsFromClass(final Class<?> clazz) { public SpelHelper registerImplicitMethodsFromClass(final Class<?> clazz) {
for (Method method : filterMethods(clazz)) { for (Method method : filterMethods(clazz)) {
registeredMethods.put(String.format( registeredMethods.put(String.format(
"%s.%s", method.getParameterTypes()[0].getName(), method.getName()), "%s.%s", method.getParameterTypes()[0].getName(), method.getName()),
method); method);
} }
return this; return this;
} }
/** /**
* Registers the public static methods in the class `clazz` as functions * Registers the public static methods in the class `clazz` as functions
* which can be called from SpEL expressions. * which can be called from SpEL expressions.
* The functions are registered with the simple name of the methods. * The functions are registered with the simple name of the methods.
* *
* Only registers the public static methods with non void return type. * Only registers the public static methods with non void return type.
* @see ExtensionFunctions * @see ExtensionFunctions
* @param clazz The class to register the functions from. * @param clazz The class to register the functions from.
* @return The current instance of SpelHelper. This is for chaining * @return The current instance of SpelHelper. This is for chaining
* the methods calls. * the methods calls.
*/ */
public SpelHelper registerFunctionsFromClass(final Class<?> clazz) { public SpelHelper registerFunctionsFromClass(final Class<?> clazz) {
registeredFunctions.addAll(filterFunctions(clazz)); registeredFunctions.addAll(filterFunctions(clazz));
return this; return this;
} }
/** /**
* Registers the public constructors of the class `clazz` so that they * Registers the public constructors of the class `clazz` so that they
* can be called by their simple name from SpEL expressions. * can be called by their simple name from SpEL expressions.
* @param clazz The class to register the constructors from. * @param clazz The class to register the constructors from.
* @return The current instance of SpelHelper. This is for chaining * @return The current instance of SpelHelper. This is for chaining
* the methods calls. * the methods calls.
*/ */
public SpelHelper registerConstructorsFromClass(final Class<?> clazz) { public SpelHelper registerConstructorsFromClass(final Class<?> clazz) {
for (Constructor<?> constructor : asList(clazz.getConstructors())) { for (Constructor<?> constructor : asList(clazz.getConstructors())) {
registeredConstructors.put( registeredConstructors.put(
constructor.getDeclaringClass().getSimpleName() constructor.getDeclaringClass().getSimpleName()
+ Arrays.toString(constructor.getParameterTypes()), + Arrays.toString(constructor.getParameterTypes()),
constructor); constructor);
} }
return this; return this;
} }
/** /**
* Evaluates a SpEL expression `expressionString` in the context * Evaluates a SpEL expression `expressionString` in the context
* of root element `rootElement` and gives back a result of type * of root element `rootElement` and gives back a result of type
* `desiredType`. * `desiredType`.
* @param <T> The type of the result desired. * @param <T> The type of the result desired.
* @param expressionString The SpEL expression to evaluate. * @param expressionString The SpEL expression to evaluate.
* @param rootElement The root element in context of which the expression * @param rootElement The root element in context of which the expression
* is to be evaluated. * is to be evaluated.
* @param desiredType The class of the result desired. * @param desiredType The class of the result desired.
* @return The result of the evaluation of the expression. * @return The result of the evaluation of the expression.
* @see ExpressionParser#parseExpression(String) * @see ExpressionParser#parseExpression(String)
* @see Expression#getValue(EvaluationContext, Class) * @see Expression#getValue(EvaluationContext, Class)
*/ */
public <T> T evalExpression(final String expressionString, public <T> T evalExpression(final String expressionString,
final Object rootElement, final Class<T> desiredType) { final Object rootElement, final Class<T> desiredType) {
EvaluationContext evaluationContext = getEvaluationContext(rootElement); EvaluationContext evaluationContext = getEvaluationContext(rootElement);
CURRENT_CONTEXT.set(evaluationContext); CURRENT_CONTEXT.set(evaluationContext);
T value = evalExpression(expressionString, evaluationContext, desiredType); T value = evalExpression(expressionString, evaluationContext, desiredType);
CURRENT_CONTEXT.set(null); CURRENT_CONTEXT.set(null);
return value; return value;
} }
/** /**
* Evaluates a SpEL expression `expressionString` in the provided * Evaluates a SpEL expression `expressionString` in the provided
* context `evaluationContext` and gives back a result of type * context `evaluationContext` and gives back a result of type
* `desiredType`. * `desiredType`.
* @param <T> The type of the result desired. * @param <T> The type of the result desired.
* @param expressionString The SpEL expression to evaluate. * @param expressionString The SpEL expression to evaluate.
* @param evaluationContext The context in which the expression is to be evaluated. * @param evaluationContext The context in which the expression is to be evaluated.
* @param desiredType The class of the result desired. * @param desiredType The class of the result desired.
* @return The result of the evaluation of the expression. * @return The result of the evaluation of the expression.
* @see ExpressionParser#parseExpression(String) * @see ExpressionParser#parseExpression(String)
* @see Expression#getValue(EvaluationContext, Class) * @see Expression#getValue(EvaluationContext, Class)
*/ */
public <T> T evalExpression(final String expressionString, public <T> T evalExpression(final String expressionString,
final EvaluationContext evaluationContext, final Class<T> desiredType) { final EvaluationContext evaluationContext, final Class<T> desiredType) {
return PARSER.parseExpression(expressionString) return PARSER.parseExpression(expressionString)
.getValue(evaluationContext, desiredType); .getValue(evaluationContext, desiredType);
} }
/** /**
* Evaluates multiple SpEL expressions and returns the result of the last * Evaluates multiple SpEL expressions and returns the result of the last
* expression. * expression.
* @param <T> The type of the result desired. * @param <T> The type of the result desired.
* @param expressionStrings The SpEL expressions to evaluate. * @param expressionStrings The SpEL expressions to evaluate.
* @param rootElement The root element in context of which the expressions * @param rootElement The root element in context of which the expressions
* are to be evaluated. * are to be evaluated.
* @param desiredType The class of the result desired. * @param desiredType The class of the result desired.
* @return The result of the evaluation of the last expression. * @return The result of the evaluation of the last expression.
* @see SpelHelper#evalExpression(String, EvaluationContext, Class) * @see SpelHelper#evalExpression(String, EvaluationContext, Class)
* @see SpelHelper#evalExpression(String, Object, Class) * @see SpelHelper#evalExpression(String, Object, Class)
*/ */
public <T> T evalExpressions(final String[] expressionStrings, public <T> T evalExpressions(final String[] expressionStrings,
final Object rootElement, final Class<T> desiredType) { final Object rootElement, final Class<T> desiredType) {
return evalExpressions( return evalExpressions(
expressionStrings, getEvaluationContext(rootElement), desiredType); expressionStrings, getEvaluationContext(rootElement), desiredType);
} }
/** /**
* Evaluates multiple SpEL expressions and returns the result of the last * Evaluates multiple SpEL expressions and returns the result of the last
* expression. * expression.
* @param <T> The type of the result desired. * @param <T> The type of the result desired.
* @param expressionStrings The SpEL expressions to evaluate. * @param expressionStrings The SpEL expressions to evaluate.
* @param evaluationContext The context in which the expression is to be evaluated. * @param evaluationContext The context in which the expression is to be evaluated.
* @param desiredType The class of the result desired. * @param desiredType The class of the result desired.
* @return The result of the evaluation of the last expression. * @return The result of the evaluation of the last expression.
* @see SpelHelper#evalExpression(String, EvaluationContext, Class) * @see SpelHelper#evalExpression(String, EvaluationContext, Class)
* @see SpelHelper#evalExpression(String, Object, Class) * @see SpelHelper#evalExpression(String, Object, Class)
*/ */
public <T> T evalExpressions(final String[] expressionStrings, public <T> T evalExpressions(final String[] expressionStrings,
final EvaluationContext evaluationContext, final Class<T> desiredType) { final EvaluationContext evaluationContext, final Class<T> desiredType) {
int length = expressionStrings.length; int length = expressionStrings.length;
Assert.isTrue(length > 0, Assert.isTrue(length > 0,
"expressionStrings should have length more than 0"); "expressionStrings should have length more than 0");
for (int i = 0; i < length - 1; i++) { for (int i = 0; i < length - 1; i++) {
evalExpression(expressionStrings[i], evaluationContext, Object.class); evalExpression(expressionStrings[i], evaluationContext, Object.class);
} }
return evalExpression(expressionStrings[length - 1], return evalExpression(expressionStrings[length - 1],
evaluationContext, desiredType); evaluationContext, desiredType);
} }
private EvaluationContext getEvaluationContext(final Object rootObject) { private EvaluationContext getEvaluationContext(final Object rootObject) {
StandardEvaluationContext newContext = new StandardEvaluationContext(rootObject); StandardEvaluationContext newContext = new StandardEvaluationContext(rootObject);
newContext.getMethodResolvers().add(new ImplicitMethodResolver()); newContext.getMethodResolvers().add(new ImplicitMethodResolver());
newContext.getPropertyAccessors().add(new ImplicitPropertyAccessor()); newContext.getPropertyAccessors().add(new ImplicitPropertyAccessor());
newContext.setConstructorResolvers( newContext.setConstructorResolvers(
asList((ConstructorResolver) new ImplicitConstructorResolver())); asList((ConstructorResolver) new ImplicitConstructorResolver()));
for (Method method : registeredFunctions) { for (Method method : registeredFunctions) {
newContext.setVariable(method.getName(), method); newContext.setVariable(method.getName(), method);
} }
newContext.setVariable(CONTEXT_LOOKUP_KEY, this); newContext.setVariable(CONTEXT_LOOKUP_KEY, this);
return newContext; return newContext;
} }
/** /**
* Looks up an implicit method registered with this instance. * Looks up an implicit method registered with this instance.
* @param lookup key to lookup which should be of form: * @param lookup key to lookup which should be of form:
* `method.getParameterTypes()[0].getName() + "." + method.getName()` * `method.getParameterTypes()[0].getName() + "." + method.getName()`
* @return The registered method if found, else null. * @return The registered method if found, else null.
*/ */
public Method lookupImplicitMethod(final String lookup) { public Method lookupImplicitMethod(final String lookup) {
Assert.notNull(lookup); Assert.notNull(lookup);
return registeredMethods.get(lookup); return registeredMethods.get(lookup);
} }
/** /**
* Looks up an implicit constructor registered with this instance. * Looks up an implicit constructor registered with this instance.
* @param lookup key to lookup which should be of form: * @param lookup key to lookup which should be of form:
* `constructor.getDeclaringClass().getSimpleName()` * `constructor.getDeclaringClass().getSimpleName()`
* `+ Arrays.toString(constructor.getParameterTypes())` * `+ Arrays.toString(constructor.getParameterTypes())`
* @return The registered constructor if found, else null. * @return The registered constructor if found, else null.
*/ */
public Constructor<?> lookupImplicitConstructor(final String lookup) { public Constructor<?> lookupImplicitConstructor(final String lookup) {
Assert.notNull(lookup); Assert.notNull(lookup);
return registeredConstructors.get(lookup); return registeredConstructors.get(lookup);
} }
/** /**
* Returns the current evaluation context. Null if there is no context. * Returns the current evaluation context. Null if there is no context.
* @return The current evaluation context. * @return The current evaluation context.
*/ */
public static EvaluationContext getCurrentContext() { public static EvaluationContext getCurrentContext() {
return CURRENT_CONTEXT.get(); return CURRENT_CONTEXT.get();
} }
private static List<Method> filterMethods(final Class<?> clazz) { private static List<Method> filterMethods(final Class<?> clazz) {
List<Method> allowedMethods = new ArrayList<Method>(); List<Method> allowedMethods = new ArrayList<Method>();
for (Method method : clazz.getMethods()) { for (Method method : clazz.getMethods()) {
int modifiers = method.getModifiers(); int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)
&& !method.getReturnType().equals(Void.TYPE) && !method.getReturnType().equals(Void.TYPE)
&& method.getParameterTypes().length > 0) { && method.getParameterTypes().length > 0) {
allowedMethods.add(method); allowedMethods.add(method);
} }
} }
return allowedMethods; return allowedMethods;
} }
private static List<Method> filterFunctions(final Class<?> clazz) { private static List<Method> filterFunctions(final Class<?> clazz) {
List<Method> allowedMethods = new ArrayList<Method>(); List<Method> allowedMethods = new ArrayList<Method>();
for (Method method : clazz.getMethods()) { for (Method method : clazz.getMethods()) {
int modifiers = method.getModifiers(); int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)
&& !method.getReturnType().equals(Void.TYPE)) { && !method.getReturnType().equals(Void.TYPE)) {
allowedMethods.add(method); allowedMethods.add(method);
} }
} }
return allowedMethods; return allowedMethods;
} }
} }

View File

@ -1,36 +1,36 @@
package net.abhinavsarkar.spelhelper; package net.abhinavsarkar.spelhelper;
public final class Functions { public final class Functions {
public static String test(final String str) { public static String test(final String str) {
return str; return str;
} }
static String testNonPublic(final String str) { static String testNonPublic(final String str) {
return str; return str;
} }
public String testNonStatic(final String str) { public String testNonStatic(final String str) {
return str; return str;
} }
public static void testVoid(final String str) { public static void testVoid(final String str) {
return; return;
} }
public static String testNoArg() { public static String testNoArg() {
return "a"; return "a";
} }
public static String testContext(final String str) { public static String testContext(final String str) {
if (SpelHelper.getCurrentContext() == null) { if (SpelHelper.getCurrentContext() == null) {
throw new AssertionError(); throw new AssertionError();
} }
return str; return str;
} }
@Override @Override
public boolean equals(final Object o) { public boolean equals(final Object o) {
return o instanceof Functions; return o instanceof Functions;
} }
} }

View File

@ -1,42 +1,42 @@
package net.abhinavsarkar.spelhelper package net.abhinavsarkar.spelhelper
import org.scalatest.junit.JUnitRunner import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.scalatest.FlatSpec import org.scalatest.FlatSpec
import org.scalatest.junit.ShouldMatchersForJUnit import org.scalatest.junit.ShouldMatchersForJUnit
import java.util.{Set => JSet, HashSet, import java.util.{Set => JSet, HashSet,
List => JList, ArrayList, List => JList, ArrayList,
Map => JMap, HashMap} Map => JMap, HashMap}
import org.springframework.expression.spel.SpelEvaluationException import org.springframework.expression.spel.SpelEvaluationException
@RunWith(classOf[JUnitRunner]) @RunWith(classOf[JUnitRunner])
class ExtensionFunctionsSpec extends FlatSpec with ShouldMatchersForJUnit { class ExtensionFunctionsSpec extends FlatSpec with ShouldMatchersForJUnit {
"Extension Function 'list'" should "return a java.util.List " in { "Extension Function 'list'" should "return a java.util.List " in {
val list: JList[String] = new ArrayList val list: JList[String] = new ArrayList
List("a", "b", "c") foreach { list add _ } List("a", "b", "c") foreach { list add _ }
new SpelHelper().evalExpression("#list('a','b','c')", new SpelHelper().evalExpression("#list('a','b','c')",
new {}, classOf[JList[String]]) should equal(list) new {}, classOf[JList[String]]) should equal(list)
} }
"Extension Function 'set'" should "return a java.util.Set " in { "Extension Function 'set'" should "return a java.util.Set " in {
val set: JSet[String] = new HashSet val set: JSet[String] = new HashSet
List("a", "b", "c") foreach { set add _ } List("a", "b", "c") foreach { set add _ }
new SpelHelper().evalExpression("#set('a','b','c')", new SpelHelper().evalExpression("#set('a','b','c')",
new {}, classOf[JSet[String]]) should equal(set) new {}, classOf[JSet[String]]) should equal(set)
} }
"Extension Function 'map'" should "return a java.util.Map " in { "Extension Function 'map'" should "return a java.util.Map " in {
val map: JMap[String,Int] = new HashMap val map: JMap[String,Int] = new HashMap
List("a", "b", "c").zipWithIndex.foreach { x => map.put(x._1, x._2) } List("a", "b", "c").zipWithIndex.foreach { x => map.put(x._1, x._2) }
new SpelHelper().evalExpression("#map(#list('a','b','c'),#list(0,1,2))", new SpelHelper().evalExpression("#map(#list('a','b','c'),#list(0,1,2))",
new {}, classOf[JMap[String,Int]]) should equal(map) new {}, classOf[JMap[String,Int]]) should equal(map)
} }
"Extension Function 'map'" should "throw SpelEvaluationException " + "Extension Function 'map'" should "throw SpelEvaluationException " +
"if length of key and values lists is not same " in { "if length of key and values lists is not same " in {
evaluating { new SpelHelper().evalExpression("#map(#list('a','b','c'),#list(1,2))", evaluating { new SpelHelper().evalExpression("#map(#list('a','b','c'),#list(1,2))",
new {}, classOf[JMap[String,Int]]) } should produce [SpelEvaluationException] new {}, classOf[JMap[String,Int]]) } should produce [SpelEvaluationException]
} }
} }

View File

@ -1,52 +1,52 @@
package net.abhinavsarkar.spelhelper package net.abhinavsarkar.spelhelper
import org.scalatest.junit.JUnitRunner import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.scalatest.FlatSpec import org.scalatest.FlatSpec
import org.scalatest.junit.ShouldMatchersForJUnit import org.scalatest.junit.ShouldMatchersForJUnit
import java.util.{HashSet, Set => JSet, List => JList, ArrayList} import java.util.{HashSet, Set => JSet, List => JList, ArrayList}
@RunWith(classOf[JUnitRunner]) @RunWith(classOf[JUnitRunner])
class ImplicitMethodsSpec extends FlatSpec with ShouldMatchersForJUnit { class ImplicitMethodsSpec extends FlatSpec with ShouldMatchersForJUnit {
"Implicit Function 'distinct' on List" should "Implicit Function 'distinct' on List" should
"return distinct items in a list " in { "return distinct items in a list " in {
val set: JSet[String] = new HashSet val set: JSet[String] = new HashSet
set add "a"; set add "b" set add "a"; set add "b"
new SpelHelper().evalExpression("#list('a','b','a').distinct()", new SpelHelper().evalExpression("#list('a','b','a').distinct()",
new {}, classOf[JSet[String]]) should equal(set) new {}, classOf[JSet[String]]) should equal(set)
} }
"Implicit Function 'sorted' on List" should "Implicit Function 'sorted' on List" should
"return a sorted list " in { "return a sorted list " in {
val list: JList[String] = new ArrayList val list: JList[String] = new ArrayList
List("a", "b", "c") foreach { list add _ } List("a", "b", "c") foreach { list add _ }
new SpelHelper().evalExpression("#list('c','b','a').sorted()", new SpelHelper().evalExpression("#list('c','b','a').sorted()",
new {}, classOf[JList[String]]) should equal(list) new {}, classOf[JList[String]]) should equal(list)
} }
"Implicit Function 'reversed' on List" should "Implicit Function 'reversed' on List" should
"return a reversed list " in { "return a reversed list " in {
val list: JList[String] = new ArrayList val list: JList[String] = new ArrayList
List("a", "b", "c") foreach { list add _ } List("a", "b", "c") foreach { list add _ }
new SpelHelper().evalExpression("#list('c','b','a').reversed()", new SpelHelper().evalExpression("#list('c','b','a').reversed()",
new {}, classOf[JList[String]]) should equal(list) new {}, classOf[JList[String]]) should equal(list)
} }
"Implicit Function 'take' on List" should "Implicit Function 'take' on List" should
"return a list containing first n items of a list " in { "return a list containing first n items of a list " in {
val list: JList[String] = new ArrayList val list: JList[String] = new ArrayList
List("a", "b", "c") foreach { list add _ } List("a", "b", "c") foreach { list add _ }
new SpelHelper().evalExpression("#list('a','b','c','d').take(3)", new SpelHelper().evalExpression("#list('a','b','c','d').take(3)",
new {}, classOf[JList[String]]) should equal(list) new {}, classOf[JList[String]]) should equal(list)
} }
"Implicit Function 'drop' on List" should "Implicit Function 'drop' on List" should
"return a list containing items after the first n items of a list " in { "return a list containing items after the first n items of a list " in {
val list: JList[String] = new ArrayList val list: JList[String] = new ArrayList
List("c", "d") foreach { list add _ } List("c", "d") foreach { list add _ }
new SpelHelper().evalExpression("#list('a','b','c','d').drop(2)", new SpelHelper().evalExpression("#list('a','b','c','d').drop(2)",
new {}, classOf[JList[String]]) should equal(list) new {}, classOf[JList[String]]) should equal(list)
} }
} }

View File

@ -1,103 +1,103 @@
package net.abhinavsarkar.spelhelper package net.abhinavsarkar.spelhelper
import org.scalatest.junit.JUnitRunner import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.scalatest.FlatSpec import org.scalatest.FlatSpec
import org.scalatest.junit.ShouldMatchersForJUnit import org.scalatest.junit.ShouldMatchersForJUnit
import org.springframework.expression.spel.SpelEvaluationException import org.springframework.expression.spel.SpelEvaluationException
@RunWith(classOf[JUnitRunner]) @RunWith(classOf[JUnitRunner])
class SpelHelperSpec extends FlatSpec with ShouldMatchersForJUnit { class SpelHelperSpec extends FlatSpec with ShouldMatchersForJUnit {
"SpelHelper" should "register and evaluate functions " in { "SpelHelper" should "register and evaluate functions " in {
new SpelHelper() new SpelHelper()
.registerFunctionsFromClass(classOf[Functions]) .registerFunctionsFromClass(classOf[Functions])
.evalExpression( .evalExpression(
"#test('check')", new {}, classOf[String]) should equal ("check") "#test('check')", new {}, classOf[String]) should equal ("check")
} }
it should "not register non public methods " in { it should "not register non public methods " in {
val spelHelper = new SpelHelper() val spelHelper = new SpelHelper()
.registerFunctionsFromClass(classOf[Functions]) .registerFunctionsFromClass(classOf[Functions])
evaluating { spelHelper.evalExpression("#testNonPublic('check')", evaluating { spelHelper.evalExpression("#testNonPublic('check')",
new {}, classOf[String]) } should produce [SpelEvaluationException] new {}, classOf[String]) } should produce [SpelEvaluationException]
} }
it should "not register non static methods " in { it should "not register non static methods " in {
val spelHelper = new SpelHelper() val spelHelper = new SpelHelper()
.registerFunctionsFromClass(classOf[Functions]) .registerFunctionsFromClass(classOf[Functions])
evaluating { spelHelper.evalExpression("#testNonStatic('check')", evaluating { spelHelper.evalExpression("#testNonStatic('check')",
new {}, classOf[String]) } should produce [SpelEvaluationException] new {}, classOf[String]) } should produce [SpelEvaluationException]
} }
it should "not register void methods " in { it should "not register void methods " in {
val spelHelper = new SpelHelper() val spelHelper = new SpelHelper()
.registerFunctionsFromClass(classOf[Functions]) .registerFunctionsFromClass(classOf[Functions])
evaluating { spelHelper.evalExpression("#testVoid('check')", evaluating { spelHelper.evalExpression("#testVoid('check')",
new {}, classOf[String]) } should produce [SpelEvaluationException] new {}, classOf[String]) } should produce [SpelEvaluationException]
} }
it should "register implicit methods " in { it should "register implicit methods " in {
new SpelHelper() new SpelHelper()
.registerImplicitMethodsFromClass(classOf[Functions]) .registerImplicitMethodsFromClass(classOf[Functions])
.lookupImplicitMethod("java.lang.String.test") should equal( .lookupImplicitMethod("java.lang.String.test") should equal(
classOf[Functions].getMethod("test", classOf[String])) classOf[Functions].getMethod("test", classOf[String]))
} }
it should "not register methods with no args as implicit methods " in { it should "not register methods with no args as implicit methods " in {
new SpelHelper() new SpelHelper()
.registerImplicitMethodsFromClass(classOf[Functions]) .registerImplicitMethodsFromClass(classOf[Functions])
.lookupImplicitMethod("java.lang.String.testNoArg") should be (null); .lookupImplicitMethod("java.lang.String.testNoArg") should be (null);
} }
it should "register implicit constructors " in { it should "register implicit constructors " in {
new SpelHelper() new SpelHelper()
.registerConstructorsFromClass(classOf[Functions]) .registerConstructorsFromClass(classOf[Functions])
.lookupImplicitConstructor("Functions[]") should equal( .lookupImplicitConstructor("Functions[]") should equal(
classOf[Functions].getConstructor()) classOf[Functions].getConstructor())
} }
it should "evaluate implicit methods " in { it should "evaluate implicit methods " in {
new SpelHelper() new SpelHelper()
.registerImplicitMethodsFromClass(classOf[Functions]) .registerImplicitMethodsFromClass(classOf[Functions])
.evalExpression( .evalExpression(
"'check'.test()", new {}, classOf[String]) should equal ("check") "'check'.test()", new {}, classOf[String]) should equal ("check")
} }
it should "evaluate implicit constructors " in { it should "evaluate implicit constructors " in {
new SpelHelper() new SpelHelper()
.registerConstructorsFromClass(classOf[Functions]) .registerConstructorsFromClass(classOf[Functions])
.evalExpression( .evalExpression(
"new Functions()", new {}, classOf[Functions]) should equal (new Functions) "new Functions()", new {}, classOf[Functions]) should equal (new Functions)
} }
it should "evaluate implicit properties " in { it should "evaluate implicit properties " in {
new SpelHelper().evalExpression( new SpelHelper().evalExpression(
"'abc'.hashCode", new {}, classOf[int]) should equal ("abc".hashCode) "'abc'.hashCode", new {}, classOf[int]) should equal ("abc".hashCode)
} }
it should "evaluate multiple expressions " in { it should "evaluate multiple expressions " in {
new SpelHelper().evalExpressions( new SpelHelper().evalExpressions(
Array("#s='check'", "#s"), new {}, classOf[String]) should equal ("check") Array("#s='check'", "#s"), new {}, classOf[String]) should equal ("check")
} }
it should "throw IllegalArgumentException when trying to evaluate " + it should "throw IllegalArgumentException when trying to evaluate " +
"blank multiple expressions " in { "blank multiple expressions " in {
evaluating { new SpelHelper().evalExpressions( evaluating { new SpelHelper().evalExpressions(
Array[String](), new {}, classOf[String]) } should produce [IllegalArgumentException] Array[String](), new {}, classOf[String]) } should produce [IllegalArgumentException]
} }
it should "return evaluation context inside a method called " + it should "return evaluation context inside a method called " +
"from SpEL expression " in { "from SpEL expression " in {
new SpelHelper() new SpelHelper()
.registerFunctionsFromClass(classOf[Functions]) .registerFunctionsFromClass(classOf[Functions])
.evalExpression( .evalExpression(
"#testContext('check')", new {}, classOf[String]) should equal ("check") "#testContext('check')", new {}, classOf[String]) should equal ("check")
} }
it should "not return evaluation context outside a method called " + it should "not return evaluation context outside a method called " +
"from SpEL expression " in { "from SpEL expression " in {
SpelHelper.getCurrentContext should be (null) SpelHelper.getCurrentContext should be (null)
} }
} }