001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * 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, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.application;
017
018 import java.math.BigDecimal;
019 import java.math.BigInteger;
020 import java.util.Date;
021
022 import org.springframework.beans.factory.FactoryBean;
023 import org.springframework.binding.convert.ConversionContext;
024 import org.springframework.binding.convert.ConversionService;
025 import org.springframework.binding.convert.support.AbstractConverter;
026 import org.springframework.binding.convert.support.AbstractFormattingConverter;
027 import org.springframework.binding.convert.support.DefaultConversionService;
028 import org.springframework.binding.format.FormatterFactory;
029 import org.springframework.binding.format.support.StrictNumberFormatterFactory;
030 import org.springframework.richclient.convert.support.CollectionConverter;
031 import org.springframework.richclient.convert.support.ListModelConverter;
032 import org.springframework.util.StringUtils;
033
034 /**
035 * A factory bean that produces a conversion service installed with most converters
036 * needed by Spring Rich. Subclasses may extend and customize. The factory approach
037 * here is superior to subclassing as it minimizes conversion service constructor logic.
038 *
039 * @author Oliver Hutchison
040 * @author Keith Donald
041 */
042 public class DefaultConversionServiceFactoryBean implements FactoryBean {
043
044 private ConversionService conversionService;
045
046 private FormatterFactory formatterFactory = new StrictNumberFormatterFactory();
047
048 public DefaultConversionServiceFactoryBean() {
049 }
050
051 protected FormatterFactory getFormatterFactory() {
052 return formatterFactory;
053 }
054
055 public void setFormatterFactory(FormatterFactory formatterFactory) {
056 this.formatterFactory = formatterFactory;
057 }
058
059 public Object getObject() throws Exception {
060 return getConversionService();
061 }
062
063 public Class getObjectType() {
064 return ConversionService.class;
065 }
066
067 public boolean isSingleton() {
068 return true;
069 }
070
071 public final ConversionService getConversionService() {
072 if (conversionService == null) {
073 conversionService = createConversionService();
074 }
075 return conversionService;
076 }
077
078 /**
079 * Creates the conversion service. Subclasses may override to customize creation.
080 * @return the configured conversion service
081 */
082 protected ConversionService createConversionService() {
083 DefaultConversionService service = new DefaultConversionService();
084 service.addConverter(new TextToDate(getFormatterFactory(), true));
085 service.addConverter(new DateToText(getFormatterFactory(), true));
086 service.addConverter(new TextToNumber(getFormatterFactory(), true));
087 service.addConverter(new NumberToText(getFormatterFactory(), true));
088 service.addConverter(new BooleanToText());
089 service.addConverter(new TextToBoolean());
090 service.addConverter(new CollectionConverter());
091 service.addConverter(new ListModelConverter());
092 return service;
093 }
094
095 static final class TextToDate extends AbstractFormattingConverter {
096
097 private final boolean allowEmpty;
098
099 protected TextToDate(FormatterFactory formatterFactory, boolean allowEmpty) {
100 super(formatterFactory);
101 this.allowEmpty = allowEmpty;
102 }
103
104 public Class[] getSourceClasses() {
105 return new Class[] { String.class };
106 }
107
108 public Class[] getTargetClasses() {
109 return new Class[] { Date.class };
110 }
111
112 protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
113 return (!allowEmpty || StringUtils.hasText((String) source)) ? getFormatterFactory().getDateTimeFormatter()
114 .parseValue((String) source, Date.class) : null;
115 }
116 }
117
118 static final class DateToText extends AbstractFormattingConverter {
119
120 private final boolean allowEmpty;
121
122 protected DateToText(FormatterFactory formatterLocator, boolean allowEmpty) {
123 super(formatterLocator);
124 this.allowEmpty = allowEmpty;
125 }
126
127 public Class[] getSourceClasses() {
128 return new Class[] { Date.class };
129 }
130
131 public Class[] getTargetClasses() {
132 return new Class[] { String.class };
133 }
134
135 protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
136 return (!allowEmpty || source != null) ? getFormatterFactory().getDateTimeFormatter().formatValue(source)
137 : "";
138 }
139 }
140
141 static final class TextToNumber extends AbstractFormattingConverter {
142
143 private final boolean allowEmpty;
144
145 protected TextToNumber(FormatterFactory formatterLocator, boolean allowEmpty) {
146 super(formatterLocator);
147 this.allowEmpty = allowEmpty;
148 }
149
150 public Class[] getSourceClasses() {
151 return new Class[] { String.class };
152 }
153
154 public Class[] getTargetClasses() {
155 return new Class[] { Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class,
156 BigInteger.class, BigDecimal.class, };
157 }
158
159 protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
160 return (!allowEmpty || StringUtils.hasText((String) source)) ? getFormatterFactory().getNumberFormatter(
161 targetClass).parseValue((String) source, targetClass) : null;
162 }
163 }
164
165 static final class NumberToText extends AbstractFormattingConverter {
166
167 private final boolean allowEmpty;
168
169 protected NumberToText(FormatterFactory formatterLocator, boolean allowEmpty) {
170 super(formatterLocator);
171 this.allowEmpty = allowEmpty;
172 }
173
174 public Class[] getSourceClasses() {
175 return new Class[] { Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class,
176 BigInteger.class, BigDecimal.class, };
177 }
178
179 public Class[] getTargetClasses() {
180 return new Class[] { String.class };
181 }
182
183 protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
184 return (!allowEmpty || source != null) ? getFormatterFactory().getNumberFormatter(source.getClass())
185 .formatValue(source) : "";
186 }
187 }
188
189 static final class TextToBoolean extends AbstractConverter {
190
191 public static final String VALUE_TRUE = "true";
192
193 public static final String VALUE_FALSE = "false";
194
195 public static final String VALUE_ON = "on";
196
197 public static final String VALUE_OFF = "off";
198
199 public static final String VALUE_YES = "yes";
200
201 public static final String VALUE_NO = "no";
202
203 public static final String VALUE_1 = "1";
204
205 public static final String VALUE_0 = "0";
206
207 private String trueString;
208
209 private String falseString;
210
211 public TextToBoolean() {
212 }
213
214 public TextToBoolean(String trueString, String falseString) {
215 this.trueString = trueString;
216 this.falseString = falseString;
217 }
218
219 public Class[] getSourceClasses() {
220 return new Class[] { String.class };
221 }
222
223 public Class[] getTargetClasses() {
224 return new Class[] { Boolean.class };
225 }
226
227 protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
228 String text = (String) source;
229 if (!StringUtils.hasText(text)) {
230 return null;
231 }
232 else if (this.trueString != null && text.equalsIgnoreCase(this.trueString)) {
233 return Boolean.TRUE;
234 }
235 else if (this.falseString != null && text.equalsIgnoreCase(this.falseString)) {
236 return Boolean.FALSE;
237 }
238 else if (this.trueString == null
239 && (text.equalsIgnoreCase(VALUE_TRUE) || text.equalsIgnoreCase(VALUE_ON)
240 || text.equalsIgnoreCase(VALUE_YES) || text.equals(VALUE_1))) {
241 return Boolean.TRUE;
242 }
243 else if (this.falseString == null
244 && (text.equalsIgnoreCase(VALUE_FALSE) || text.equalsIgnoreCase(VALUE_OFF)
245 || text.equalsIgnoreCase(VALUE_NO) || text.equals(VALUE_0))) {
246 return Boolean.FALSE;
247 }
248 else {
249 throw new IllegalArgumentException("Invalid boolean value [" + text + "]");
250 }
251 }
252 }
253
254 static final class BooleanToText extends AbstractConverter {
255
256 public static final String VALUE_YES = "yes";
257
258 public static final String VALUE_NO = "no";
259
260 private String trueString;
261
262 private String falseString;
263
264 public BooleanToText() {
265 }
266
267 public BooleanToText(String trueString, String falseString) {
268 this.trueString = trueString;
269 this.falseString = falseString;
270 }
271
272 public Class[] getSourceClasses() {
273 return new Class[] { Boolean.class };
274 }
275
276 public Class[] getTargetClasses() {
277 return new Class[] { String.class };
278 }
279
280 protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
281 Boolean bool = (Boolean) source;
282 if (this.trueString != null && bool.booleanValue()) {
283 return trueString;
284 }
285 else if (this.falseString != null && !bool.booleanValue()) {
286 return falseString;
287 }
288 else if (bool.booleanValue()) {
289 return VALUE_YES;
290 }
291 else {
292 return VALUE_NO;
293 }
294 }
295 }
296 }