diff --git a/spring-binding/.classpath b/spring-binding/.classpath index 20822dbe..4ccd1929 100644 --- a/spring-binding/.classpath +++ b/spring-binding/.classpath @@ -11,9 +11,10 @@ - + + diff --git a/spring-binding/.settings/org.eclipse.jdt.core.prefs b/spring-binding/.settings/org.eclipse.jdt.core.prefs index 127f96ce..93ca0d5e 100644 --- a/spring-binding/.settings/org.eclipse.jdt.core.prefs +++ b/spring-binding/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,4 @@ -#Mon Jun 30 01:12:48 EDT 2008 +#Thu Jul 03 00:22:38 EDT 2008 eclipse.preferences.version=1 org.eclipse.jdt.core.codeComplete.argumentPrefixes= org.eclipse.jdt.core.codeComplete.argumentSuffixes= diff --git a/spring-binding/ivy.xml b/spring-binding/ivy.xml index 848bc48f..8d8ebc13 100644 --- a/spring-binding/ivy.xml +++ b/spring-binding/ivy.xml @@ -23,7 +23,7 @@ - + diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java index e6f171ef..fb924342 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java @@ -46,4 +46,6 @@ public interface ConversionService { */ public Class getClassForAlias(String alias); + public ConversionExecutor[] getConversionExecutors(Class sourceClass); + } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java index 96e5c676..d4845585 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java @@ -16,11 +16,15 @@ package org.springframework.binding.convert.service; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.Set; import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionExecutorNotFoundException; @@ -174,6 +178,22 @@ public class GenericConversionService implements ConversionService { // subclassing support + public ConversionExecutor[] getConversionExecutors(Class sourceClass) { + Map sourceMap = getSourceMap(sourceClass); + if (sourceMap.isEmpty()) { + return new ConversionExecutor[0]; + } + Set entries = sourceMap.entrySet(); + List conversionExecutors = new ArrayList(entries.size()); + for (Iterator it = entries.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Class targetClass = (Class) entry.getKey(); + Converter converter = (Converter) entry.getValue(); + conversionExecutors.add(new StaticConversionExecutor(sourceClass, targetClass, converter)); + } + return (ConversionExecutor[]) conversionExecutors.toArray(new ConversionExecutor[entries.size()]); + } + /** * Returns an indexed map of converters. Each entry key is a source class that can be converted from, and each entry * value is a map of target classes that can be convertered to, ultimately mapping to a specific converter that can diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java new file mode 100644 index 00000000..297c00e8 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java @@ -0,0 +1,96 @@ +package org.springframework.binding.expression.beanwrapper; + +import java.beans.PropertyEditorSupport; + +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.BeansException; +import org.springframework.beans.InvalidPropertyException; +import org.springframework.binding.convert.ConversionExecutor; +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.expression.EvaluationAttempt; +import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.PropertyNotFoundException; + +public class BeanWrapperExpression implements Expression { + + private String expression; + + private ConversionService conversionService; + + public BeanWrapperExpression(String expression, ConversionService conversionService) { + this.expression = expression; + this.conversionService = conversionService; + } + + public boolean equals(Object o) { + if (!(o instanceof BeanWrapperExpression)) { + return false; + } + BeanWrapperExpression other = (BeanWrapperExpression) o; + return expression.equals(other.expression); + } + + public int hashCode() { + return expression.hashCode(); + } + + public Object getValue(Object context) throws EvaluationException { + try { + BeanWrapperImpl beanWrapper = new BeanWrapperImpl(context); + return beanWrapper.getPropertyValue(expression); + } catch (InvalidPropertyException e) { + throw new PropertyNotFoundException(new EvaluationAttempt(this, context), e); + } catch (BeansException e) { + throw new EvaluationException(new EvaluationAttempt(this, context), e); + } + } + + public void setValue(Object context, Object value) { + try { + BeanWrapperImpl beanWrapper = new BeanWrapperImpl(context); + ConversionExecutor[] converters = conversionService.getConversionExecutors(String.class); + for (int i = 0; i < converters.length; i++) { + ConversionExecutor converter = converters[i]; + beanWrapper.registerCustomEditor(converter.getTargetClass(), new PropertyEditorConverter(converter)); + } + beanWrapper.setPropertyValue(expression, value); + } catch (InvalidPropertyException e) { + throw new PropertyNotFoundException(new EvaluationAttempt(this, context), e); + } catch (BeansException e) { + throw new EvaluationException(new EvaluationAttempt(this, context), e); + } + } + + public Class getValueType(Object context) { + try { + BeanWrapperImpl beanWrapper = new BeanWrapperImpl(context); + return beanWrapper.getPropertyType(expression); + } catch (InvalidPropertyException e) { + throw new PropertyNotFoundException(new EvaluationAttempt(this, context), e); + } catch (BeansException e) { + throw new EvaluationException(new EvaluationAttempt(this, context), e); + } + } + + public String getExpressionString() { + return expression; + } + + public String toString() { + return expression; + } + + public static class PropertyEditorConverter extends PropertyEditorSupport { + + private ConversionExecutor converter; + + public PropertyEditorConverter(ConversionExecutor converter) { + this.converter = converter; + } + + public void setAsText(String text) throws IllegalArgumentException { + setValue(converter.execute(text)); + } + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParser.java new file mode 100644 index 00000000..0a344249 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParser.java @@ -0,0 +1,44 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.binding.expression.beanwrapper; + +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ParserContext; +import org.springframework.binding.expression.ParserException; +import org.springframework.binding.expression.support.AbstractExpressionParser; + +/** + * An expression parser that parses Ognl expressions. + * + * @author Keith Donald + */ +public class BeanWrapperExpressionParser extends AbstractExpressionParser { + + private ConversionService conversionService; + + public ConversionService getConversionService() { + return conversionService; + } + + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } + + protected Expression doParseExpression(String expressionString, ParserContext context) throws ParserException { + return new BeanWrapperExpression(expressionString, conversionService); + } +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java index 0e9e2b26..af22e7f9 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java @@ -40,24 +40,12 @@ import org.springframework.binding.expression.SetValueAttempt; */ class OgnlExpression implements Expression { - /** - * The expression. - */ private Object expression; - /** - * Expression variable initial values. - */ private Map variableExpressions; - /** - * The expected type of object returned from evaluating the expression. - */ private Class expectedResultType; - /** - * The original expression string. - */ private String expressionString; /** diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpressionParser.java index 34f28094..8a65a421 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpressionParser.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpressionParser.java @@ -15,233 +15,21 @@ */ package org.springframework.binding.expression.ognl; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - import ognl.Ognl; import ognl.OgnlException; -import ognl.OgnlRuntime; -import ognl.PropertyAccessor; import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.ExpressionVariable; import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.ParserException; -import org.springframework.binding.expression.support.CompositeStringExpression; -import org.springframework.binding.expression.support.LiteralExpression; -import org.springframework.binding.expression.support.NullParserContext; -import org.springframework.util.Assert; +import org.springframework.binding.expression.support.AbstractExpressionParser; /** * An expression parser that parses Ognl expressions. * * @author Keith Donald */ -public class OgnlExpressionParser implements ExpressionParser { - - /** - * The expression prefix. - */ - private static final String DEFAULT_EXPRESSION_PREFIX = "${"; - - /** - * The expression suffix. - */ - private static final String DEFAULT_EXPRESSION_SUFFIX = "}"; - - /** - * The marked expression delimter prefix. - */ - private String expressionPrefix = DEFAULT_EXPRESSION_PREFIX; - - /** - * The marked expression delimiter suffix. - */ - private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX; - - /** - * Should we allow delimited OGNL eval expressions like "#{foo.bar}"? If not, evalutable OGNL expressions must not - * be enclosed in delimiters like ${foo.bar} else an exception is thrown. Only here for compatability reasons, as - * Web Flow 1.0 allows delimited OGNL eval expressions while 2.x does not. - */ - private boolean allowDelimitedEvalExpressions; - - /** - * Returns the configured expression delimiter prefix. Defaults to "${". - */ - public String getExpressionPrefix() { - return expressionPrefix; - } - - /** - * Sets the expression delimiter prefix. - */ - public void setExpressionPrefix(String expressionPrefix) { - this.expressionPrefix = expressionPrefix; - } - - /** - * Returns the expression delimiter suffix. Defaults to "}". - */ - public String getExpressionSuffix() { - return expressionSuffix; - } - - /** - * Sets the expression delimiter suffix. - */ - public void setExpressionSuffix(String expressionSuffix) { - this.expressionSuffix = expressionSuffix; - } - - /** - * Returns if this parser allows delimited OGNL eval expressions like ${foo.bar}. - */ - public boolean getAllowDelimitedEvalExpressions() { - return allowDelimitedEvalExpressions; - } - - /** - * Sets if this parser allows OGNL eval expressions like ${foo.bar}. - */ - public void setAllowDelimitedEvalExpressions(boolean allowDelmitedEvalExpressions) { - this.allowDelimitedEvalExpressions = allowDelmitedEvalExpressions; - } - - /** - * Add a property access strategy for the given class. - * @param clazz the class that contains properties needing access - * @param propertyAccessor the property access strategy - */ - public void addPropertyAccessor(Class clazz, PropertyAccessor propertyAccessor) { - OgnlRuntime.setPropertyAccessor(clazz, propertyAccessor); - } - - // expression parser - - public Expression parseExpression(String expressionString, ParserContext context) throws ParserException { - Assert.notNull(expressionString, "The expression string to parse is required"); - if (context == null) { - context = NullParserContext.INSTANCE; - } - if (context.isTemplate()) { - return parseTemplate(expressionString, context); - } else { - if (expressionString.startsWith(getExpressionPrefix()) && expressionString.endsWith(getExpressionSuffix())) { - if (!allowDelimitedEvalExpressions) { - throw new ParserException( - expressionString, - "The expression '" - + expressionString - + "' being parsed is expected be a standard OGNL expression. Do not attempt to enclose such expression strings in ${} delimiters--this is redundant. If you need to parse a template that mixes literal text with evaluatable blocks, set the 'template' parser context attribute to true.", - null); - } else { - int lastIndex = expressionString.length() - getExpressionSuffix().length(); - String ognlExpression = expressionString.substring(getExpressionPrefix().length(), lastIndex); - return doParseExpression(ognlExpression, context); - } - } else { - return doParseExpression(expressionString, context); - } - } - } - - private Expression parseTemplate(String expressionString, ParserContext context) throws ParserException { - Assert.notNull(expressionString, "The expression string to parse is required"); - if (expressionString.length() == 0) { - return parseEmptyExpressionString(context); - } - Expression[] expressions = parseExpressions(expressionString, context); - if (expressions.length == 1) { - return expressions[0]; - } else { - return new CompositeStringExpression(expressions); - } - } - - // helper methods - - /** - * Helper that handles a empty expression string. - */ - private Expression parseEmptyExpressionString(ParserContext context) { - if (allowDelimitedEvalExpressions) { - // let the parser handle it - return doParseExpression("", context); - } else { - // return a literal expression containing the empty string - return new LiteralExpression(""); - } - } - - /** - * Helper that parses given expression string using the configured parser. The expression string can contain any - * number of expressions all contained in "${...}" markers. For instance: "foo${expr0}bar${expr1}". The static - * pieces of text will also be returned as Expressions that just return that static piece of text. As a result, - * evaluating all returned expressions and concatenating the results produces the complete evaluated string. - * @param expressionString the expression string - * @return the parsed expressions - * @throws ParserException when the expressions cannot be parsed - */ - private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParserException { - List expressions = new LinkedList(); - int startIdx = 0; - while (startIdx < expressionString.length()) { - int prefixIndex = expressionString.indexOf(getExpressionPrefix(), startIdx); - if (prefixIndex >= startIdx) { - // a inner expression was found - this is a composite - if (prefixIndex > startIdx) { - expressions.add(new LiteralExpression(expressionString.substring(startIdx, prefixIndex))); - startIdx = prefixIndex; - } - int nextPrefixIndex = expressionString.indexOf(getExpressionPrefix(), prefixIndex - + getExpressionPrefix().length()); - int suffixIndex; - if (nextPrefixIndex == -1) { - // this is the last expression in the expression string - suffixIndex = expressionString.lastIndexOf(getExpressionSuffix()); - } else { - // another expression exists after this one in the expression string - suffixIndex = expressionString.lastIndexOf(getExpressionSuffix(), nextPrefixIndex); - } - if (suffixIndex < (prefixIndex + getExpressionPrefix().length())) { - throw new ParserException(expressionString, "No ending suffix '" + getExpressionSuffix() - + "' for expression starting at character " + prefixIndex + ": " - + expressionString.substring(prefixIndex), null); - } else if (suffixIndex == prefixIndex + getExpressionPrefix().length()) { - throw new ParserException(expressionString, "No expression defined within delimiter '" - + getExpressionPrefix() + getExpressionSuffix() + "' at character " + prefixIndex, null); - } else { - String expr = expressionString.substring(prefixIndex + getExpressionPrefix().length(), suffixIndex); - expressions.add(doParseExpression(expr, context)); - startIdx = suffixIndex + 1; - } - } else { - if (startIdx == 0) { - // treat the entire string as one expression - if (allowDelimitedEvalExpressions) { - expressions.add(doParseExpression(expressionString, context)); - } else { - // treat entire string as a literal - expressions.add(new LiteralExpression(expressionString)); - } - } else { - // no more ${expressions} found in string, add rest as static text - expressions.add(new LiteralExpression(expressionString.substring(startIdx))); - } - startIdx = expressionString.length(); - } - } - return (Expression[]) expressions.toArray(new Expression[expressions.size()]); - } - - private Expression doParseExpression(String expressionString, ParserContext context) throws ParserException { - if (context == null) { - context = NullParserContext.INSTANCE; - } +public class OgnlExpressionParser extends AbstractExpressionParser { + protected Expression doParseExpression(String expressionString, ParserContext context) throws ParserException { try { return new OgnlExpression(Ognl.parseExpression(expressionString), parseVariableExpressions(context .getExpressionVariables()), context.getExpectedEvaluationResultType(), expressionString); @@ -249,16 +37,4 @@ public class OgnlExpressionParser implements ExpressionParser { throw new ParserException(expressionString, e); } } - - private Map parseVariableExpressions(ExpressionVariable[] variables) throws OgnlException { - if (variables == null || variables.length == 0) { - return null; - } - Map variableExpressions = new HashMap(variables.length, 1); - for (int i = 0; i < variables.length; i++) { - ExpressionVariable var = variables[i]; - variableExpressions.put(var.getName(), parseExpression(var.getValueExpression(), var.getParserContext())); - } - return variableExpressions; - } } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java new file mode 100644 index 00000000..242c9b79 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java @@ -0,0 +1,239 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.binding.expression.support; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ExpressionParser; +import org.springframework.binding.expression.ExpressionVariable; +import org.springframework.binding.expression.ParserContext; +import org.springframework.binding.expression.ParserException; +import org.springframework.util.Assert; + +/** + * An expression parser that parses Ognl expressions. + * + * @author Keith Donald + */ +public abstract class AbstractExpressionParser implements ExpressionParser { + + /** + * The expression prefix. + */ + private static final String DEFAULT_EXPRESSION_PREFIX = "${"; + + /** + * The expression suffix. + */ + private static final String DEFAULT_EXPRESSION_SUFFIX = "}"; + + /** + * The marked expression delimiter prefix. + */ + private String expressionPrefix = DEFAULT_EXPRESSION_PREFIX; + + /** + * The marked expression delimiter suffix. + */ + private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX; + + /** + * Should we allow delimited eval expressions like "${foo.bar}"? If not, evalutable expressions must not be enclosed + * in delimiters like ${foo.bar} else an exception is thrown. Only here for compatability reasons, as Web Flow 1.0 + * allows delimited eval expressions while 2.x does not. + */ + private boolean allowDelimitedEvalExpressions; + + /** + * Returns the configured expression delimiter prefix. Defaults to "${". + */ + public String getExpressionPrefix() { + return expressionPrefix; + } + + /** + * Sets the expression delimiter prefix. + */ + public void setExpressionPrefix(String expressionPrefix) { + this.expressionPrefix = expressionPrefix; + } + + /** + * Returns the expression delimiter suffix. Defaults to "}". + */ + public String getExpressionSuffix() { + return expressionSuffix; + } + + /** + * Sets the expression delimiter suffix. + */ + public void setExpressionSuffix(String expressionSuffix) { + this.expressionSuffix = expressionSuffix; + } + + /** + * Returns if this parser allows delimited eval expressions like ${foo.bar}. + */ + public boolean getAllowDelimitedEvalExpressions() { + return allowDelimitedEvalExpressions; + } + + /** + * Sets if this parser allows eval expressions like ${foo.bar}. + */ + public void setAllowDelimitedEvalExpressions(boolean allowDelmitedEvalExpressions) { + this.allowDelimitedEvalExpressions = allowDelmitedEvalExpressions; + } + + // expression parser + + public Expression parseExpression(String expressionString, ParserContext context) throws ParserException { + Assert.notNull(expressionString, "The expression string to parse is required"); + if (context == null) { + context = NullParserContext.INSTANCE; + } + if (context.isTemplate()) { + return parseTemplate(expressionString, context); + } else { + if (expressionString.startsWith(getExpressionPrefix()) && expressionString.endsWith(getExpressionSuffix())) { + if (!allowDelimitedEvalExpressions) { + throw new ParserException( + expressionString, + "The expression '" + + expressionString + + "' being parsed is expected be a standard OGNL expression. Do not attempt to enclose such expression strings in ${} delimiters--this is redundant. If you need to parse a template that mixes literal text with evaluatable blocks, set the 'template' parser context attribute to true.", + null); + } else { + int lastIndex = expressionString.length() - getExpressionSuffix().length(); + String ognlExpression = expressionString.substring(getExpressionPrefix().length(), lastIndex); + return doParseExpression(ognlExpression, context); + } + } else { + return doParseExpression(expressionString, context); + } + } + } + + private Expression parseTemplate(String expressionString, ParserContext context) throws ParserException { + Assert.notNull(expressionString, "The expression string to parse is required"); + if (expressionString.length() == 0) { + return parseEmptyExpressionString(context); + } + Expression[] expressions = parseExpressions(expressionString, context); + if (expressions.length == 1) { + return expressions[0]; + } else { + return new CompositeStringExpression(expressions); + } + } + + // helper methods + + /** + * Helper that handles a empty expression string. + */ + private Expression parseEmptyExpressionString(ParserContext context) { + if (allowDelimitedEvalExpressions) { + // let the parser handle it + return doParseExpression("", context); + } else { + // return a literal expression containing the empty string + return new LiteralExpression(""); + } + } + + /** + * Helper that parses given expression string using the configured parser. The expression string can contain any + * number of expressions all contained in "${...}" markers. For instance: "foo${expr0}bar${expr1}". The static + * pieces of text will also be returned as Expressions that just return that static piece of text. As a result, + * evaluating all returned expressions and concatenating the results produces the complete evaluated string. + * @param expressionString the expression string + * @return the parsed expressions + * @throws ParserException when the expressions cannot be parsed + */ + private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParserException { + List expressions = new LinkedList(); + int startIdx = 0; + while (startIdx < expressionString.length()) { + int prefixIndex = expressionString.indexOf(getExpressionPrefix(), startIdx); + if (prefixIndex >= startIdx) { + // a inner expression was found - this is a composite + if (prefixIndex > startIdx) { + expressions.add(new LiteralExpression(expressionString.substring(startIdx, prefixIndex))); + startIdx = prefixIndex; + } + int nextPrefixIndex = expressionString.indexOf(getExpressionPrefix(), prefixIndex + + getExpressionPrefix().length()); + int suffixIndex; + if (nextPrefixIndex == -1) { + // this is the last expression in the expression string + suffixIndex = expressionString.lastIndexOf(getExpressionSuffix()); + } else { + // another expression exists after this one in the expression string + suffixIndex = expressionString.lastIndexOf(getExpressionSuffix(), nextPrefixIndex); + } + if (suffixIndex < (prefixIndex + getExpressionPrefix().length())) { + throw new ParserException(expressionString, "No ending suffix '" + getExpressionSuffix() + + "' for expression starting at character " + prefixIndex + ": " + + expressionString.substring(prefixIndex), null); + } else if (suffixIndex == prefixIndex + getExpressionPrefix().length()) { + throw new ParserException(expressionString, "No expression defined within delimiter '" + + getExpressionPrefix() + getExpressionSuffix() + "' at character " + prefixIndex, null); + } else { + String expr = expressionString.substring(prefixIndex + getExpressionPrefix().length(), suffixIndex); + expressions.add(doParseExpression(expr, context)); + startIdx = suffixIndex + 1; + } + } else { + if (startIdx == 0) { + // treat the entire string as one expression + if (allowDelimitedEvalExpressions) { + expressions.add(doParseExpression(expressionString, context)); + } else { + // treat entire string as a literal + expressions.add(new LiteralExpression(expressionString)); + } + } else { + // no more ${expressions} found in string, add rest as static text + expressions.add(new LiteralExpression(expressionString.substring(startIdx))); + } + startIdx = expressionString.length(); + } + } + return (Expression[]) expressions.toArray(new Expression[expressions.size()]); + } + + protected Map parseVariableExpressions(ExpressionVariable[] variables) throws ParserException { + if (variables == null || variables.length == 0) { + return null; + } + Map variableExpressions = new HashMap(variables.length, 1); + for (int i = 0; i < variables.length; i++) { + ExpressionVariable var = variables[i]; + variableExpressions.put(var.getName(), parseExpression(var.getValueExpression(), var.getParserContext())); + } + return variableExpressions; + } + + protected abstract Expression doParseExpression(String expressionString, ParserContext context) + throws ParserException; + +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java index 96613c03..5b15be97 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java @@ -17,7 +17,6 @@ package org.springframework.binding.mapping.impl; import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.convert.ConversionExecutor; -import org.springframework.binding.convert.ConversionService; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.mapping.Mapping; @@ -106,7 +105,7 @@ public class DefaultMapping implements Mapping { * Execute this mapping. * @param context the mapping context */ - void map(DefaultMappingContext context) { + public void map(DefaultMappingContext context) { context.setCurrentMapping(this); Object sourceValue; try { @@ -128,27 +127,6 @@ public class DefaultMapping implements Mapping { context.setTypeConversionErrorResult(e); return; } - } else { - ConversionService conversionService = context.getConversionService(); - if (conversionService != null) { - Class targetType; - try { - targetType = targetExpression.getValueType(context.getTarget()); - } catch (EvaluationException e) { - context.setTargetAccessError(sourceValue, e); - return; - } - if (targetType != null && !targetType.isInstance(targetValue)) { - ConversionExecutor typeConverter = conversionService.getConversionExecutor(sourceValue - .getClass(), targetType); - try { - targetValue = typeConverter.execute(sourceValue); - } catch (ConversionExecutionException e) { - context.setTypeConversionErrorResult(e); - return; - } - } - } } } try { diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/SourceAccessError.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/SourceAccessError.java index 0727ed9d..9412b833 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/results/SourceAccessError.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/SourceAccessError.java @@ -15,6 +15,9 @@ */ package org.springframework.binding.mapping.results; +import java.io.PrintWriter; +import java.io.StringWriter; + import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.PropertyNotFoundException; import org.springframework.binding.mapping.Result; @@ -65,7 +68,11 @@ public class SourceAccessError extends Result { } public String toString() { - return new ToStringCreator(this).append("errorCode", getErrorCode()).append("details", error.getMessage()) - .toString(); + ToStringCreator creator = new ToStringCreator(this).append("errorCode", getErrorCode()); + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + error.printStackTrace(writer); + creator.append("exceptionStackTrace", stringWriter.toString()); + return creator.toString(); } } diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/TargetAccessError.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TargetAccessError.java index 4e0a2df4..e0685950 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/results/TargetAccessError.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TargetAccessError.java @@ -15,6 +15,9 @@ */ package org.springframework.binding.mapping.results; +import java.io.PrintWriter; +import java.io.StringWriter; + import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.PropertyNotFoundException; import org.springframework.binding.mapping.Result; @@ -69,7 +72,11 @@ public class TargetAccessError extends Result { } public String toString() { - return new ToStringCreator(this).append("errorCode", getErrorCode()).append("details", error.getMessage()) - .toString(); + ToStringCreator creator = new ToStringCreator(this).append("errorCode", getErrorCode()); + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + error.printStackTrace(writer); + creator.append("stackTrace", stringWriter.toString()); + return creator.toString(); } } diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java index 30100999..32593483 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java @@ -15,6 +15,9 @@ */ package org.springframework.binding.mapping.results; +import java.io.PrintWriter; +import java.io.StringWriter; + import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.mapping.Result; import org.springframework.core.style.ToStringCreator; @@ -73,7 +76,12 @@ public class TypeConversionError extends Result { } public String toString() { - return new ToStringCreator(this).append("originalValue", originalValue).append("targetType", targetType) - .append("details", exception.getMessage()).toString(); + ToStringCreator creator = new ToStringCreator(this).append("originalValue", originalValue).append("targetType", + targetType); + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + exception.printStackTrace(writer); + creator.append("exceptionStackTrace", stringWriter.toString()); + return creator.toString(); } } diff --git a/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java b/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java index 3101f48f..024a5db0 100644 --- a/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java @@ -208,87 +208,357 @@ public class DefaultConversionServiceTests extends TestCase { assertEquals("1,2,3", result); } - /* - * - * public void testArrayListConversionWithElementConversion() throws Exception { DefaultConversionService service = - * new DefaultConversionService(); ConversionExecutor executor = service.getConversionExecutor(String[].class, - * IntegerArrayList.class); List result = (List) executor.execute(new String[] { "1", "2", "3" }); assertEquals(new - * Integer(1), result.get(0)); assertEquals(new Integer(2), result.get(1)); assertEquals(new Integer(3), - * result.get(2)); } - * - * - * public static class IntegerArrayList implements List { - * - * private ArrayList realList = new ArrayList(); - * - * public IntegerArrayList() { } - * - * public void add(int index, Integer element) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public boolean add(Integer o) { return realList.add(o); } - * - * public boolean addAll(Collection c) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public boolean addAll(int index, Collection c) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public void clear() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Auto-generated - * method stub"); } - * - * public boolean contains(Object o) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public boolean containsAll(Collection c) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public Integer get(int index) { return (Integer) realList.get(index); } - * - * public int indexOf(Object o) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public boolean isEmpty() { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public Iterator iterator() { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public int lastIndexOf(Object o) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public ListIterator listIterator() { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public ListIterator listIterator(int index) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public Integer remove(int index) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public boolean remove(Object o) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public boolean removeAll(Collection c) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public boolean retainAll(Collection c) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public Integer set(int index, Integer element) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public int size() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Auto-generated - * method stub"); } - * - * public List subList(int fromIndex, int toIndex) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public Object[] toArray() { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } - * - * public T[] toArray(T[] a) { // TODO Auto-generated method stub throw new - * UnsupportedOperationException("Auto-generated method stub"); } } - */ + // public void testGenericTypeConversionOGNL() { + // DefaultConversionService service = new DefaultConversionService(); + // Map map = new HashMap(); + // map.put("stringArray", new String[] { "1", "2", "3" }); + // map.put("string", "1"); + // TestBean bean = new TestBean(); + // OgnlExpressionParser parser = new OgnlExpressionParser(); + // Expression stringArray = parser.parseExpression("stringArray", null); + // Expression integerList = parser.parseExpression("integerList", null); + // DefaultMappingContext context = new DefaultMappingContext(map, bean, service); + // DefaultMapping mapping = new DefaultMapping(stringArray, integerList); + // mapping.map(context); + // assertEquals(new Integer(1), bean.getIntegerList().get(0)); + // assertEquals(new Integer(2), bean.getIntegerList().get(1)); + // assertEquals(new Integer(3), bean.getIntegerList().get(2)); + // } + // + // public void testGenericTypeConversionBeanWrapper() { + // DefaultConversionService service = new DefaultConversionService(); + // Map map = new HashMap(); + // map.put("stringArray", new String[] { "1", "2", "3" }); + // map.put("string", "1"); + // CollectionWrapperBean wrapper = new CollectionWrapperBean(map); + // TestBean bean = new TestBean(); + // BeanWrapperExpressionParser parser = new BeanWrapperExpressionParser(); + // Expression stringArray = parser.parseExpression("map[stringArray]", null); + // Expression integerList = parser.parseExpression("integerList", null); + // DefaultMappingContext context = new DefaultMappingContext(wrapper, bean, service); + // DefaultMapping mapping = new DefaultMapping(stringArray, integerList); + // mapping.map(context); + // assertEquals(new Integer(1), bean.getIntegerList().get(0)); + // assertEquals(new Integer(2), bean.getIntegerList().get(1)); + // assertEquals(new Integer(3), bean.getIntegerList().get(2)); + // } + // + // public void testGenericTypeConversionBeanWrapperWithCustomConversion() { + // DefaultConversionService service = new DefaultConversionService(); + // service.addConverter(new StringToAuthority()); + // Map map = new HashMap(); + // map.put("authorityArray", new String[] { "keith", "keri", "annabelle" }); + // CollectionWrapperBean wrapper = new CollectionWrapperBean(map); + // TestBean bean = new TestBean(); + // BeanWrapperExpressionParser parser = new BeanWrapperExpressionParser(); + // parser.setConversionService(service); + // Expression authorityArray = parser.parseExpression("map[authorityArray]", null); + // Expression authorityList = parser.parseExpression("authorityList", null); + // DefaultMappingContext context = new DefaultMappingContext(wrapper, bean, service); + // DefaultMapping mapping = new DefaultMapping(authorityArray, authorityList); + // mapping.map(context); + // assertEquals(new Authority("keith"), bean.getAuthorityList().get(0)); + // assertEquals(new Authority("keri"), bean.getAuthorityList().get(1)); + // assertEquals(new Authority("annabelle"), bean.getAuthorityList().get(2)); + // } + // + // public void testGenericTypeConversionBeanWrapperWithNestedCustomConversion() { + // DefaultConversionService service = new DefaultConversionService(); + // service.addConverter(new StringToAuthority()); + // Map map = new HashMap(); + // map.put("foo", "keith"); + // CollectionWrapperBean wrapper = new CollectionWrapperBean(map); + // TestBean bean = new TestBean(); + // BeanWrapperExpressionParser parser = new BeanWrapperExpressionParser(); + // parser.setConversionService(service); + // Expression keith = parser.parseExpression("map[foo]", null); + // Expression nestedMapEntry = parser.parseExpression("nested[0][0]", null); + // DefaultMappingContext context = new DefaultMappingContext(wrapper, bean, service); + // DefaultMapping mapping = new DefaultMapping(keith, nestedMapEntry); + // mapping.map(context); + // assertEquals(new Authority("keith"), bean.getNested().get(0).get(0)); + // } + // + // public void testGenericTypeConversionEL() { + // DefaultConversionService service = new DefaultConversionService(); + // Map map = new HashMap(); + // map.put("stringArray", new String[] { "1", "2", "3" }); + // map.put("string", "1"); + // TestBean bean = new TestBean(); + // ELExpressionParser parser = new ELExpressionParser(new ExpressionFactoryImpl()); + // Expression stringArray = parser.parseExpression("stringArray", null); + // Expression integerList = parser.parseExpression("integerList", null); + // DefaultMappingContext context = new DefaultMappingContext(map, bean, service); + // DefaultMapping mapping = new DefaultMapping(stringArray, integerList); + // mapping.map(context); + // assertEquals(new Integer(1), bean.getIntegerList().get(0)); + // assertEquals(new Integer(2), bean.getIntegerList().get(1)); + // assertEquals(new Integer(3), bean.getIntegerList().get(2)); + // } + // + // public void testArrayConversionOGNL() { + // DefaultConversionService service = new DefaultConversionService(); + // Map map = new HashMap(); + // map.put("integerArray", new Integer[] { 1, 2, 3 }); + // map.put("string", "1"); + // TestBean bean = new TestBean(); + // OgnlExpressionParser parser = new OgnlExpressionParser(); + // Expression stringArray = parser.parseExpression("integerArray", null); + // Expression integerList = parser.parseExpression("primitiveArray", null); + // DefaultMappingContext context = new DefaultMappingContext(map, bean, service); + // DefaultMapping mapping = new DefaultMapping(stringArray, integerList); + // mapping.map(context); + // } + // + // public void testArrayConversionEL() { + // DefaultConversionService service = new DefaultConversionService(); + // Map map = new HashMap(); + // map.put("stringArray", new String[] { "1", "2", "3" }); + // map.put("string", "1"); + // TestBean bean = new TestBean(); + // ELExpressionParser parser = new ELExpressionParser(new ExpressionFactoryImpl()); + // Expression stringArray = parser.parseExpression("stringArray", null); + // Expression integerList = parser.parseExpression("primitiveArray", null); + // DefaultMappingContext context = new DefaultMappingContext(map, bean, service); + // DefaultMapping mapping = new DefaultMapping(stringArray, integerList); + // mapping.map(context); + // } + // + // public static class CollectionWrapperBean { + // + // private Collection collection; + // + // private Map map; + // + // public CollectionWrapperBean(Map map) { + // this.map = map; + // } + // + // public CollectionWrapperBean(Collection collection) { + // this.collection = collection; + // } + // + // public Collection getCollection() { + // return collection; + // } + // + // public Map getMap() { + // return map; + // } + // } + // + // public static class TestBean { + // + // private List integerList; + // + // private int primitive; + // + // private int[] primitiveArray; + // + // private List authorityList; + // + // private List> nested; + // + // public TestBean() { + // nested = new ArrayList>(); + // nested.add(new HashMap()); + // nested.get(0).put(0, new Authority("bubba")); + // } + // + // public List getIntegerList() { + // return integerList; + // } + // + // public void setIntegerList(List integerList) { + // this.integerList = integerList; + // } + // + // public int getPrimitive() { + // return primitive; + // } + // + // public void setPrimitive(int primitive) { + // this.primitive = primitive; + // } + // + // public int[] getPrimitiveArray() { + // return primitiveArray; + // } + // + // public void setPrimitiveArray(int[] primitiveArray) { + // this.primitiveArray = primitiveArray; + // } + // + // public List getAuthorityList() { + // return authorityList; + // } + // + // public void setAuthorityList(List authorityList) { + // this.authorityList = authorityList; + // } + // + // // nested[0][1] + // public List> getNested() { + // return nested; + // } + // + // public void setNested(List> nested) { + // this.nested = nested; + // } + // + // } + // + // public static class Authority { + // private String name; + // + // public Authority(String name) { + // this.name = name; + // } + // + // public String getName() { + // return name; + // } + // + // public boolean equals(Object o) { + // if (!(o instanceof Authority)) { + // return false; + // } + // Authority auth = (Authority) o; + // return name.equals(auth.name); + // } + // + // public int hashCode() { + // return name.hashCode(); + // } + // + // public String toString() { + // return name; + // } + // } + // + // public static class StringToAuthority extends StringToObject { + // + // public StringToAuthority() { + // super(Authority.class); + // } + // + // protected Object toObject(String string, Class targetClass) throws Exception { + // return new Authority(string); + // } + // + // protected String toString(Object object) throws Exception { + // return object.toString(); + // } + // + // } + + // public void testArrayListConversionWithElementConversion() throws Exception { + // DefaultConversionService service = new DefaultConversionService(); + // ConversionExecutor executor = service.getConversionExecutor(String[].class, IntegerArrayList.class); + // List result = (List) executor.execute(new String[] { "1", "2", "3" }); + // assertEquals(new Integer(1), result.get(0)); + // assertEquals(new Integer(2), result.get(1)); + // assertEquals(new Integer(3), result.get(2)); + // } + // + // public static class IntegerArrayList implements List { + // + // private ArrayList realList = new ArrayList(); + // + // public IntegerArrayList() { + // } + // + // public void add(int index, Integer element) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public boolean add(Integer o) { + // return realList.add(o); + // } + // + // public boolean addAll(Collection c) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public boolean addAll(int index, Collection c) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public void clear() { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public boolean contains(Object o) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public boolean containsAll(Collection c) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public Integer get(int index) { + // return (Integer) realList.get(index); + // } + // + // public int indexOf(Object o) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public boolean isEmpty() { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public Iterator iterator() { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public int lastIndexOf(Object o) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public ListIterator listIterator() { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public ListIterator listIterator(int index) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public Integer remove(int index) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public boolean remove(Object o) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public boolean removeAll(Collection c) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public boolean retainAll(Collection c) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public Integer set(int index, Integer element) { + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public int size() { // TODO Auto-generated method stu + // throw new UnsupportedOperationException("Auto-generatedmethod stub"); + // } + // + // public List subList(int fromIndex, int toIndex) { // TODO Auto-generated method stub throw new + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public Object[] toArray() { // TODO Auto-generated method stub throw new + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // + // public T[] toArray(T[] a) { // TODO Auto-generated method stub throw new + // throw new UnsupportedOperationException("Auto-generated method stub"); + // } + // } } \ No newline at end of file