new feature for binder - automatically create lists and entries in lists upon referencing nulls
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1300 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
e9288fdb4e
commit
1e2cecfd76
|
|
@ -19,6 +19,7 @@ import org.springframework.expression.Expression;
|
|||
import org.springframework.expression.ExpressionException;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
|
@ -68,7 +69,10 @@ public class Binder<T> {
|
|||
public Binder(T model) {
|
||||
this.model = model;
|
||||
bindings = new HashMap<String, Binding>();
|
||||
expressionParser = new SpelExpressionParser();
|
||||
int parserConfig =
|
||||
SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull |
|
||||
SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize;
|
||||
expressionParser = new SpelExpressionParser(parserConfig);
|
||||
typeConverter = new DefaultTypeConverter();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
|
|
@ -146,21 +148,39 @@ public class BinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void bindHandleNullValueInNestedPath() {
|
||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
||||
TestBean testbean = new TestBean();
|
||||
Binder<TestBean> binder = new Binder<TestBean>(testbean);
|
||||
Map<String, String> propertyValues = new HashMap<String, String>();
|
||||
// TODO should auto add(new Address) at 0
|
||||
|
||||
// EL configured with some options from SpelExpressionParserConfiguration:
|
||||
// (see where Binder creates the parser)
|
||||
// - new addresses List is created if null
|
||||
// - new entries automatically built if List is currently too short - all new entries
|
||||
// are new instances of the type of the list entry, they are not null.
|
||||
// not currently doing anything for maps or arrays
|
||||
|
||||
propertyValues.put("addresses[0].street", "4655 Macy Lane");
|
||||
propertyValues.put("addresses[0].city", "Melbourne");
|
||||
propertyValues.put("addresses[0].state", "FL");
|
||||
propertyValues.put("addresses[0].state", "35452");
|
||||
// TODO should auto add(new Address) at 1
|
||||
|
||||
// Auto adds new Address at 1
|
||||
propertyValues.put("addresses[1].street", "1234 Rostock Circle");
|
||||
propertyValues.put("addresses[1].city", "Palm Bay");
|
||||
propertyValues.put("addresses[1].state", "FL");
|
||||
propertyValues.put("addresses[1].state", "32901");
|
||||
|
||||
// Auto adds new Address at 5 (plus intermediates 2,3,4)
|
||||
propertyValues.put("addresses[5].street", "1234 Rostock Circle");
|
||||
propertyValues.put("addresses[5].city", "Palm Bay");
|
||||
propertyValues.put("addresses[5].state", "FL");
|
||||
propertyValues.put("addresses[5].state", "32901");
|
||||
|
||||
binder.bind(propertyValues);
|
||||
Assert.assertEquals(6,testbean.addresses.size());
|
||||
Assert.assertEquals("Palm Bay",testbean.addresses.get(1).city);
|
||||
Assert.assertNotNull(testbean.addresses.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import org.springframework.expression.OperatorOverloader;
|
|||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypeComparator;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||
|
||||
/**
|
||||
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to it are not seen by other
|
||||
|
|
@ -47,12 +48,20 @@ public class ExpressionState {
|
|||
private final Stack<VariableScope> variableScopes = new Stack<VariableScope>();
|
||||
|
||||
private final Stack<TypedValue> contextObjects = new Stack<TypedValue>();
|
||||
|
||||
private int configuration = 0;
|
||||
|
||||
public ExpressionState(EvaluationContext context) {
|
||||
this.relatedContext = context;
|
||||
createVariableScope();
|
||||
}
|
||||
|
||||
public ExpressionState(EvaluationContext context, int configuration) {
|
||||
this.relatedContext = context;
|
||||
this.configuration = configuration;
|
||||
createVariableScope();
|
||||
}
|
||||
|
||||
// create an empty top level VariableScope
|
||||
private void createVariableScope() {
|
||||
this.variableScopes.add(new VariableScope());
|
||||
|
|
@ -204,4 +213,12 @@ public class ExpressionState {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean configuredToGrowCollection() {
|
||||
return (configuration & SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize)!=0;
|
||||
}
|
||||
|
||||
public boolean configuredToCreateCollection() {
|
||||
return (configuration & SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull)!=0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,13 +37,15 @@ public class SpelExpression implements Expression {
|
|||
|
||||
public final SpelNodeImpl ast;
|
||||
|
||||
public final int configuration;
|
||||
|
||||
/**
|
||||
* Construct an expression, only used by the parser.
|
||||
*/
|
||||
public SpelExpression(String expression, SpelNodeImpl ast) {
|
||||
public SpelExpression(String expression, SpelNodeImpl ast, int configuration) {
|
||||
this.expression = expression;
|
||||
this.ast = ast;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -57,7 +59,7 @@ public class SpelExpression implements Expression {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public Object getValue() throws EvaluationException {
|
||||
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
|
||||
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(),configuration);
|
||||
return this.ast.getValue(expressionState);
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +67,7 @@ public class SpelExpression implements Expression {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public Object getValue(EvaluationContext context) throws EvaluationException {
|
||||
return this.ast.getValue(new ExpressionState(context));
|
||||
return this.ast.getValue(new ExpressionState(context,configuration));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,7 +75,7 @@ public class SpelExpression implements Expression {
|
|||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
|
||||
Object result = ast.getValue(new ExpressionState(context));
|
||||
Object result = ast.getValue(new ExpressionState(context,configuration));
|
||||
|
||||
if (result != null && expectedResultType != null) {
|
||||
Class<?> resultType = result.getClass();
|
||||
|
|
@ -89,14 +91,14 @@ public class SpelExpression implements Expression {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
|
||||
this.ast.setValue(new ExpressionState(context), value);
|
||||
this.ast.setValue(new ExpressionState(context,configuration), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean isWritable(EvaluationContext context) throws EvaluationException {
|
||||
return this.ast.isWritable(new ExpressionState(context));
|
||||
return this.ast.isWritable(new ExpressionState(context,configuration));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,7 +123,7 @@ public class SpelExpression implements Expression {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public Class getValueType(EvaluationContext context) throws EvaluationException {
|
||||
ExpressionState eState = new ExpressionState(context);
|
||||
ExpressionState eState = new ExpressionState(context,configuration);
|
||||
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
|
||||
return typeDescriptor.getType();
|
||||
}
|
||||
|
|
@ -130,7 +132,7 @@ public class SpelExpression implements Expression {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
|
||||
ExpressionState eState = new ExpressionState(context);
|
||||
ExpressionState eState = new ExpressionState(context,configuration);
|
||||
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
|
||||
return typeDescriptor;
|
||||
}
|
||||
|
|
@ -139,14 +141,14 @@ public class SpelExpression implements Expression {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public Class getValueType() throws EvaluationException {
|
||||
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext())).getTypeDescriptor().getType();
|
||||
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(),configuration)).getTypeDescriptor().getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
|
||||
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext())).getTypeDescriptor();
|
||||
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(),configuration)).getTypeDescriptor();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -154,7 +156,7 @@ public class SpelExpression implements Expression {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
|
||||
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
|
||||
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(),configuration);
|
||||
Object result = this.ast.getValue(expressionState);
|
||||
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ public enum SpelMessages {
|
|||
UNEXPECTED_DATA_AFTER_DOT(Kind.ERROR,1049,"Unexpected data after ''.'': ''{0}''"),//
|
||||
MISSING_CONSTRUCTOR_ARGS(Kind.ERROR,1050,"The arguments '(...)' for the constructor call are missing"),//
|
||||
RUN_OUT_OF_ARGUMENTS(Kind.ERROR,1051,"Unexpected ran out of arguments"),//
|
||||
UNABLE_TO_GROW_COLLECTION(Kind.ERROR,1052,"Unable to grow collection"),//
|
||||
UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE(Kind.ERROR,1053,"Unable to grow collection: unable to determine list element type"),//
|
||||
UNABLE_TO_CREATE_LIST_FOR_INDEXING(Kind.ERROR,1054,"Unable to create a List for the following indexer"),//
|
||||
;
|
||||
|
||||
private Kind kind;
|
||||
|
|
|
|||
|
|
@ -67,9 +67,32 @@ public class Indexer extends SpelNodeImpl {
|
|||
if (targetObject.getClass().isArray()) {
|
||||
return new TypedValue(accessArrayElement(targetObject, idx),TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType()));
|
||||
} else if (targetObject instanceof Collection) {
|
||||
Collection<?> c = (Collection<?>) targetObject;
|
||||
Collection c = (Collection) targetObject;
|
||||
if (idx >= c.size()) {
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessages.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
|
||||
if (state.configuredToGrowCollection()) {
|
||||
// Grow the collection
|
||||
Object newCollectionElement = null;
|
||||
try {
|
||||
int newElements = idx-c.size();
|
||||
Class elementClass = targetObjectTypeDescriptor.getElementType();
|
||||
if (elementClass == null) {
|
||||
throw new SpelEvaluationException(getStartPosition(), SpelMessages.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
||||
}
|
||||
while (newElements>0) {
|
||||
c.add(elementClass.newInstance());
|
||||
newElements--;
|
||||
}
|
||||
newCollectionElement = targetObjectTypeDescriptor.getElementType().newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
throw new SpelEvaluationException(getStartPosition(), e, SpelMessages.UNABLE_TO_GROW_COLLECTION);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new SpelEvaluationException(getStartPosition(), e, SpelMessages.UNABLE_TO_GROW_COLLECTION);
|
||||
}
|
||||
c.add(newCollectionElement);
|
||||
return new TypedValue(newCollectionElement,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType()));
|
||||
} else {
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessages.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
|
||||
}
|
||||
}
|
||||
int pos = 0;
|
||||
for (Object o : c) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
|
|
@ -52,7 +53,22 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||
|
||||
@Override
|
||||
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
|
||||
return readProperty(state, this.name);
|
||||
TypedValue result = readProperty(state, this.name);
|
||||
if (result.getValue()==null && state.configuredToCreateCollection() && result.getTypeDescriptor().getType().equals(List.class) && nextChildIs(Indexer.class)) {
|
||||
// Create a new list ready for the indexer
|
||||
try {
|
||||
if (isWritable(state)) {
|
||||
List newList = ArrayList.class.newInstance();
|
||||
writeProperty(state, name, newList);
|
||||
result = readProperty(state, this.name);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
throw new SpelEvaluationException(getStartPosition(), e, SpelMessages.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new SpelEvaluationException(getStartPosition(), e, SpelMessages.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -172,6 +188,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||
|
||||
public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException {
|
||||
Object contextObject = state.getActiveContextObject().getValue();
|
||||
TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
|
||||
EvaluationContext eContext = state.getEvaluationContext();
|
||||
|
||||
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public abstract class SpelNodeImpl implements SpelNode, CommonTypeDescriptors {
|
|||
|
||||
protected int pos; // start = top 16bits, end = bottom 16bits
|
||||
protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
|
||||
private SpelNodeImpl parent;
|
||||
|
||||
public SpelNodeImpl(int pos, SpelNodeImpl... operands) {
|
||||
this.pos = pos;
|
||||
|
|
@ -44,13 +45,40 @@ public abstract class SpelNodeImpl implements SpelNode, CommonTypeDescriptors {
|
|||
assert pos!=0;
|
||||
if (operands!=null && operands.length>0) {
|
||||
this.children = operands;
|
||||
for (SpelNodeImpl childnode: operands) {
|
||||
childnode.parent = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SpelNodeImpl getPreviousChild() {
|
||||
SpelNodeImpl result = null;
|
||||
if (parent!=null) {
|
||||
for (SpelNodeImpl child: parent.children) {
|
||||
if (this==child) break;
|
||||
result = child;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected boolean nextChildIs(Class clazz) {
|
||||
if (parent!=null) {
|
||||
SpelNodeImpl[] peers = parent.children;
|
||||
for (int i=0,max=peers.length;i<max;i++) {
|
||||
if (peers[i]==this) {
|
||||
return (i+1)<max && peers[i+1].getClass().equals(clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final Object getValue(ExpressionState expressionState) throws EvaluationException {
|
||||
if (expressionState != null) {
|
||||
return getValueInternal(expressionState).getValue();
|
||||
} else {
|
||||
// configuration not set - does that matter?
|
||||
return getValue(new ExpressionState(new StandardEvaluationContext()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,8 +86,20 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser {
|
|||
|
||||
// For rules that build nodes, they are stacked here for return
|
||||
private Stack<SpelNodeImpl> constructedNodes = new Stack<SpelNodeImpl>();
|
||||
|
||||
private int configuration;
|
||||
|
||||
public SpelExpressionParser() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a parser with some configured behaviour. Supported configuration
|
||||
* bit flags can be seen in @see {@link SpelExpressionParserConfiguration}
|
||||
* @param configuration bitflags for configuration options
|
||||
*/
|
||||
public SpelExpressionParser(int configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public SpelExpression parse(String expressionString) throws ParseException {
|
||||
|
|
@ -108,7 +120,7 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser {
|
|||
throw new SpelParseException(peekToken().startpos,SpelMessages.MORE_INPUT,toString(nextToken()));
|
||||
}
|
||||
assert constructedNodes.isEmpty();
|
||||
return new SpelExpression(expressionString,ast);
|
||||
return new SpelExpression(expressionString, ast, configuration);
|
||||
} catch (InternalParseException ipe) {
|
||||
throw ipe.getCause();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2008-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.expression.spel.standard;
|
||||
|
||||
/**
|
||||
* Bit flags that configure optional behaviour in the parser. Pass the necessary
|
||||
* bits when calling the expression parser constructor.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface SpelExpressionParserConfiguration {
|
||||
|
||||
static final int CreateListsOnAttemptToIndexIntoNull = 0x0001;
|
||||
static final int GrowListsOnIndexBeyondSize = 0x0002;
|
||||
|
||||
}
|
||||
|
|
@ -16,14 +16,18 @@
|
|||
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardTypeLocator;
|
||||
|
||||
|
|
@ -34,6 +38,38 @@ import org.springframework.expression.spel.support.StandardTypeLocator;
|
|||
*/
|
||||
public class EvaluationTests extends ExpressionTestCase {
|
||||
|
||||
@Test
|
||||
public void testCreateListsOnAttemptToIndexNull01() throws EvaluationException, ParseException {
|
||||
ExpressionParser parser = new SpelExpressionParser(SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull | SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize);
|
||||
Expression expression = parser.parseExpression("list[0]");
|
||||
TestClass testClass = new TestClass();
|
||||
Object o = null;
|
||||
o = expression.getValue(new StandardEvaluationContext(testClass));
|
||||
Assert.assertEquals("",o);
|
||||
o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass));
|
||||
Assert.assertEquals("",o);
|
||||
Assert.assertEquals(4, testClass.list.size());
|
||||
try {
|
||||
o = parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass));
|
||||
Assert.fail();
|
||||
} catch (EvaluationException ee) {
|
||||
// success!
|
||||
}
|
||||
o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass));
|
||||
Assert.assertEquals("",o);
|
||||
Assert.assertEquals(4, testClass.getFoo().size());
|
||||
}
|
||||
|
||||
static class TestClass {
|
||||
public List<String> list;
|
||||
public List list2;
|
||||
|
||||
private List<String> foo;
|
||||
public List<String> getFoo() { return this.foo; }
|
||||
public void setFoo(List<String> newfoo) { this.foo = newfoo; }
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testElvis01() {
|
||||
evaluate("'Andy'?:'Dave'","Andy",String.class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue