001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket; 018 019import java.lang.ref.WeakReference; 020import java.math.BigDecimal; 021import java.math.BigInteger; 022import java.time.LocalDate; 023import java.time.LocalDateTime; 024import java.time.LocalTime; 025import java.time.ZonedDateTime; 026import java.util.Calendar; 027import java.util.Date; 028import java.util.HashMap; 029import java.util.Locale; 030import java.util.Map; 031 032import org.apache.wicket.util.convert.ConversionException; 033import org.apache.wicket.util.convert.IConverter; 034import org.apache.wicket.util.convert.converter.BigDecimalConverter; 035import org.apache.wicket.util.convert.converter.BigIntegerConverter; 036import org.apache.wicket.util.convert.converter.BooleanConverter; 037import org.apache.wicket.util.convert.converter.ByteConverter; 038import org.apache.wicket.util.convert.converter.CalendarConverter; 039import org.apache.wicket.util.convert.converter.CharacterConverter; 040import org.apache.wicket.util.convert.converter.DateConverter; 041import org.apache.wicket.util.convert.converter.DoubleConverter; 042import org.apache.wicket.util.convert.converter.FloatConverter; 043import org.apache.wicket.util.convert.converter.IntegerConverter; 044import org.apache.wicket.util.convert.converter.LocalDateConverter; 045import org.apache.wicket.util.convert.converter.LocalDateTimeConverter; 046import org.apache.wicket.util.convert.converter.LocalTimeConverter; 047import org.apache.wicket.util.convert.converter.LongConverter; 048import org.apache.wicket.util.convert.converter.ShortConverter; 049import org.apache.wicket.util.convert.converter.SqlDateConverter; 050import org.apache.wicket.util.convert.converter.SqlTimeConverter; 051import org.apache.wicket.util.convert.converter.SqlTimestampConverter; 052import org.apache.wicket.util.convert.converter.ZonedDateTimeConverter; 053import org.apache.wicket.util.lang.Args; 054import org.apache.wicket.util.lang.Objects; 055 056 057/** 058 * Implementation of {@link IConverterLocator} interface, which locates converters for a given type. 059 * It serves as a registry for {@link IConverter} instances stored by type, and is the default 060 * locator for Wicket. 061 * 062 * @see IConverterLocator 063 * @author Eelco Hillenius 064 * @author Jonathan Locke 065 * 066 */ 067public class ConverterLocator implements IConverterLocator 068{ 069 /** 070 * ConverterLocator that is to be used when no registered converter is found. 071 * 072 * @param <C> 073 * The object to convert from and to String 074 */ 075 private static class DefaultConverter<C> implements IConverter<C> 076 { 077 private static final long serialVersionUID = 1L; 078 079 private final transient WeakReference<Class<C>> type; 080 081 /** 082 * Construct. 083 * 084 * @param type 085 */ 086 private DefaultConverter(Class<C> type) 087 { 088 this.type = new WeakReference<>(type); 089 } 090 091 @Override 092 public C convertToObject(String value, Locale locale) 093 { 094 if (value == null) 095 { 096 return null; 097 } 098 Class<C> theType = type.get(); 099 if (value.isEmpty()) 100 { 101 if (String.class.equals(theType)) 102 { 103 return theType.cast(""); 104 } 105 return null; 106 } 107 108 try 109 { 110 C converted = Objects.convertValue(value, theType); 111 if (converted != null) 112 { 113 return converted; 114 } 115 116 if (theType != null && theType.isInstance(value)) 117 { 118 return theType.cast(value); 119 } 120 } 121 catch (Exception e) 122 { 123 throw new ConversionException(e.getMessage(), e).setSourceValue(value); 124 } 125 126 throw new ConversionException("Could not convert value: " + value + " to type: " + 127 theType.getName() + ". Could not find compatible converter.").setSourceValue(value); 128 } 129 130 @Override 131 public String convertToString(C value, Locale locale) 132 { 133 if (value == null || (value instanceof String && ((String) value).isEmpty())) 134 { 135 return ""; 136 } 137 138 try 139 { 140 return Objects.convertValue(value, String.class); 141 } 142 catch (RuntimeException e) 143 { 144 throw new ConversionException("Could not convert object of type: " + 145 value.getClass() + " to String. Possible its #toString() returned null. " + 146 "Either install a custom converter (see IConverterLocator) or " + 147 "override #toString() to return a non-null value.", e).setSourceValue(value) 148 .setConverter(this); 149 } 150 } 151 } 152 153 private static final long serialVersionUID = 1L; 154 155 /** Maps Classes to ITypeConverters. */ 156 private final Map<String, IConverter<?>> classToConverter = new HashMap<>(); 157 158 /** 159 * Constructor 160 */ 161 public ConverterLocator() 162 { 163 set(Boolean.TYPE, BooleanConverter.INSTANCE); 164 set(Boolean.class, BooleanConverter.INSTANCE); 165 set(Byte.TYPE, ByteConverter.INSTANCE); 166 set(Byte.class, ByteConverter.INSTANCE); 167 set(Character.TYPE, CharacterConverter.INSTANCE); 168 set(Character.class, CharacterConverter.INSTANCE); 169 set(Double.TYPE, DoubleConverter.INSTANCE); 170 set(Double.class, DoubleConverter.INSTANCE); 171 set(Float.TYPE, FloatConverter.INSTANCE); 172 set(Float.class, FloatConverter.INSTANCE); 173 set(Integer.TYPE, IntegerConverter.INSTANCE); 174 set(Integer.class, IntegerConverter.INSTANCE); 175 set(Long.TYPE, LongConverter.INSTANCE); 176 set(Long.class, LongConverter.INSTANCE); 177 set(Short.TYPE, ShortConverter.INSTANCE); 178 set(Short.class, ShortConverter.INSTANCE); 179 set(BigDecimal.class, new BigDecimalConverter()); 180 set(BigInteger.class, new BigIntegerConverter()); 181 set(Date.class, new DateConverter()); 182 set(java.sql.Date.class, new SqlDateConverter()); 183 set(java.sql.Time.class, new SqlTimeConverter()); 184 set(java.sql.Timestamp.class, new SqlTimestampConverter()); 185 set(Calendar.class, new CalendarConverter()); 186 set(LocalDate.class, new LocalDateConverter()); 187 set(LocalDateTime.class, new LocalDateTimeConverter()); 188 set(LocalTime.class, new LocalTimeConverter()); 189 set(ZonedDateTime.class, new ZonedDateTimeConverter()); 190 } 191 192 /** 193 * Gets the type converter that is registered for class c. 194 * 195 * @param <C> 196 * The object to convert from and to String 197 * @param c 198 * The class to get the type converter for 199 * @return The type converter that is registered for class c or null if no type converter was 200 * registered for class c 201 */ 202 public final <C> IConverter<C> get(Class<C> c) 203 { 204 return (IConverter<C>) classToConverter.get(c.getName()); 205 } 206 207 /** 208 * Converts the given value to class c. 209 * 210 * @param type 211 * Class to get the converter for. 212 * 213 * @return The converter for the given type 214 * 215 * @see org.apache.wicket.util.convert.IConverter#convertToObject(String, java.util.Locale) 216 */ 217 @Override 218 public final <C> IConverter<C> getConverter(Class<C> type) 219 { 220 // Null is always converted to null 221 if (type == null) 222 { 223 @SuppressWarnings("unchecked") 224 IConverter<C> converter = (IConverter<C>)new DefaultConverter<String>(String.class); 225 return converter; 226 } 227 228 // Get type converter for class 229 final IConverter<C> converter = get(type); 230 if (converter == null) 231 { 232 return new DefaultConverter<>(type); 233 } 234 return converter; 235 } 236 237 /** 238 * Removes the type converter currently registered for class c. 239 * 240 * @param c 241 * The class for which the converter registration should be removed 242 * @return The converter that was registered for class c before removal or null if none was 243 * registered 244 */ 245 public final IConverter<?> remove(Class<?> c) 246 { 247 return classToConverter.remove(c.getName()); 248 } 249 250 /** 251 * Registers a converter for use with class c. 252 * 253 * @param converter 254 * The converter to add 255 * @param c 256 * The class for which the converter should be used 257 * @return The previous registered converter for class c or null if none was registered yet for 258 * class c 259 */ 260 public final IConverter<?> set(final Class<?> c, final IConverter<?> converter) 261 { 262 Args.notNull(c, "Class"); 263 Args.notNull(converter, "converter"); 264 return classToConverter.put(c.getName(), converter); 265 } 266}