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    }