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}