001: /*
002: * Copyright 2004-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.springframework.binding.mapping;
017:
018: import org.springframework.binding.convert.ConversionExecutor;
019: import org.springframework.binding.convert.ConversionService;
020: import org.springframework.binding.convert.support.DefaultConversionService;
021: import org.springframework.binding.expression.Expression;
022: import org.springframework.binding.expression.ExpressionParser;
023: import org.springframework.binding.expression.SettableExpression;
024: import org.springframework.binding.expression.support.CollectionAddingExpression;
025: import org.springframework.util.Assert;
026:
027: /**
028: * A stateful builder that builds {@link Mapping} objects. Designed for
029: * convenience to build mappings in a clear, readable manner.
030: * <p>
031: * Example usage:
032: *
033: * <pre>
034: * MappingBuilder mapping = new MappingBuilder();
035: * Mapping result = mapping.source("foo").target("bar").from(String.class).to(Long.class).value();
036: * </pre>
037: *
038: * Calling the {@link #value()} result method clears out this builder's state so
039: * it can be reused to build another mapping.
040: *
041: * @author Keith Donald
042: * @author Erwin Vervaet
043: */
044: public class MappingBuilder {
045:
046: /**
047: * The expression string parser.
048: */
049: private ExpressionParser expressionParser;
050:
051: /**
052: * The conversion service for applying type conversions.
053: */
054: private ConversionService conversionService = new DefaultConversionService();
055:
056: /**
057: * The source mapping expression.
058: */
059: private Expression sourceExpression;
060:
061: /**
062: * The target mapping settable expression.
063: */
064: private SettableExpression targetExpression;
065:
066: /**
067: * The type of the object returned by evaluating the source expression.
068: */
069: private Class sourceType;
070:
071: /**
072: * The type of the property settable by the target expression.
073: */
074: private Class targetType;
075:
076: /**
077: * Whether or not the built mapping is a required mapping.
078: */
079: private boolean required;
080:
081: /**
082: * Creates a mapping builder that uses the expression parser to parse
083: * attribute mapping expressions.
084: * @param expressionParser the expression parser
085: */
086: public MappingBuilder(ExpressionParser expressionParser) {
087: Assert.notNull(expressionParser,
088: "The expression parser is required");
089: this .expressionParser = expressionParser;
090: }
091:
092: /**
093: * Sets the conversion service that will convert the object returned by
094: * evaluating the source expression to the {@link #to(Class)} type if
095: * necessary.
096: * @param conversionService the conversion service
097: */
098: public void setConversionService(ConversionService conversionService) {
099: this .conversionService = conversionService;
100: }
101:
102: /**
103: * Sets the source expression of the mapping built by this builder.
104: * @param expressionString the expression string
105: * @return this, to support call-chaining
106: */
107: public MappingBuilder source(String expressionString) {
108: sourceExpression = expressionParser
109: .parseExpression(expressionString);
110: return this ;
111: }
112:
113: /**
114: * Sets the target property expression of the mapping built by this builder.
115: * @param expressionString the expression string
116: * @return this, to support call-chaining
117: */
118: public MappingBuilder target(String expressionString) {
119: targetExpression = (SettableExpression) expressionParser
120: .parseExpression(expressionString);
121: return this ;
122: }
123:
124: /**
125: * Sets the target collection of the mapping built by this builder.
126: * @param expressionString the expression string, resolving a collection
127: * @return this, to support call-chaining
128: */
129: public MappingBuilder targetCollection(String expressionString) {
130: targetExpression = new CollectionAddingExpression(
131: expressionParser
132: .parseSettableExpression(expressionString));
133: return this ;
134: }
135:
136: /**
137: * Sets the expected type of the object returned by evaluating the source
138: * expression. Used in conjunction with {@link #to(Class)} to perform a type
139: * conversion during the mapping process.
140: * @param sourceType the source type
141: * @return this, to support call-chaining
142: */
143: public MappingBuilder from(Class sourceType) {
144: this .sourceType = sourceType;
145: return this ;
146: }
147:
148: /**
149: * Sets the target type of the property writeable by the target expression.
150: * @param targetType the target type
151: * @return this, to support call-chaining
152: */
153: public MappingBuilder to(Class targetType) {
154: this .targetType = targetType;
155: return this ;
156: }
157:
158: /**
159: * Marks the mapping to be built a "required" mapping.
160: * @return this, to support call-chaining
161: */
162: public MappingBuilder required() {
163: this .required = true;
164: return this ;
165: }
166:
167: /**
168: * The logical GoF builder getResult method, returning a fully constructed
169: * Mapping from the configured pieces. Once called, the state of this
170: * builder is nulled out to support building a new mapping object again.
171: * @return the mapping result
172: */
173: public Mapping value() {
174: Assert.notNull(sourceExpression,
175: "The source expression must be set at a minimum");
176: if (targetExpression == null) {
177: targetExpression = (SettableExpression) sourceExpression;
178: }
179: ConversionExecutor typeConverter = null;
180: if (sourceType != null) {
181: Assert
182: .notNull(targetType,
183: "The target type is required when the source type is specified");
184: typeConverter = conversionService.getConversionExecutor(
185: sourceType, targetType);
186: }
187: Mapping result;
188: if (required) {
189: result = new RequiredMapping(sourceExpression,
190: targetExpression, typeConverter);
191: } else {
192: result = new Mapping(sourceExpression, targetExpression,
193: typeConverter);
194: }
195: reset();
196: return result;
197: }
198:
199: /**
200: * Reset this mapping builder.
201: */
202: public void reset() {
203: sourceExpression = null;
204: targetExpression = null;
205: sourceType = null;
206: targetType = null;
207: required = false;
208: }
209: }
|