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 }