1 /*
2  * Copyright 2011 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.modelmapper.internal.converter;
17
18 import java.math.BigDecimal;
19 import java.math.BigInteger;
20 import java.util.Calendar;
21 import java.util.Date;
22
23 import org.modelmapper.internal.Errors;
24 import org.modelmapper.internal.util.Primitives;
25 import org.modelmapper.spi.ConditionalConverter;
26 import org.modelmapper.spi.MappingContext;
27
28 import javax.xml.datatype.XMLGregorianCalendar;
29
30 /**
31  * Converts:
32  * 
33  * <ul>
34  * <li>Number</li>
35  * <li>Boolean</li>
36  * <li>Date</li>
37  * <li>Calendar</li>
38  * <li>String</li>
39  * <li>Object</li>
40  * </ul>
41  * 
42  * instances to instances of:
43  * 
44  * <ul>
45  * <li>Byte</li>
46  * <li>Short</li>
47  * <li>Integer</li>
48  * <li>Long</li>
49  * <li>Float</li>
50  * <li>Double</li>
51  * <li>BigDecimal</li>
52  * <li>BigInteger</li>
53  * </ul>
54  * 
55  * @author Jonathan Halterman
56  */

57 class NumberConverter implements ConditionalConverter<Object, Number> {
58   public Number convert(MappingContext<Object, Number> context) {
59     Object source = context.getSource();
60     if (source == null)
61       return null;
62
63     Class<?> destinationType = Primitives.wrapperFor(context.getDestinationType());
64
65     if (source instanceof Number)
66       return numberFor((Number) source, destinationType);
67     if (source instanceof Boolean)
68       return numberFor(((Boolean) source).booleanValue() ? 1 : 0, destinationType);
69     if (source instanceof Date && Long.class.equals(destinationType))
70       return Long.valueOf(((Date) source).getTime());
71     if (source instanceof Calendar && Long.class.equals(destinationType))
72       return Long.valueOf(((Calendar) source).getTime().getTime());
73     if (source instanceof XMLGregorianCalendar && Long.class.equals(destinationType))
74       return ((XMLGregorianCalendar) source).toGregorianCalendar().getTimeInMillis();
75     return numberFor(source.toString(), destinationType);
76   }
77
78   public MatchResult match(Class<?> sourceType, Class<?> destinationType) {
79     boolean destMatch = Number.class.isAssignableFrom(Primitives.wrapperFor(destinationType));
80     if (destMatch) {
81       return Number.class.isAssignableFrom(Primitives.wrapperFor(sourceType))
82           || sourceType == Boolean.class || sourceType == Boolean.TYPE
83           || sourceType == String.class || Date.class.isAssignableFrom(sourceType)
84           || Calendar.class.isAssignableFrom(sourceType)
85           || XMLGregorianCalendar.class.isAssignableFrom(sourceType) ? MatchResult.FULL : MatchResult.PARTIAL;
86     } else
87       return MatchResult.NONE;
88   }
89
90   /**
91    * Creates a Number for the {@code source} and {@code destinationType}.
92    */

93   Number numberFor(Number source, Class<?> destinationType) {
94     if (destinationType.equals(source.getClass()))
95       return source;
96
97     if (destinationType.equals(Byte.class)) {
98       long longValue = source.longValue();
99       if (longValue > Byte.MAX_VALUE)
100         throw new Errors().errorTooLarge(source, destinationType).toMappingException();
101       if (longValue < Byte.MIN_VALUE)
102         throw new Errors().errorTooSmall(source, destinationType).toMappingException();
103       return Byte.valueOf(source.byteValue());
104     }
105
106     if (destinationType.equals(Short.class)) {
107       long longValue = source.longValue();
108       if (longValue > Short.MAX_VALUE)
109         throw new Errors().errorTooLarge(source, destinationType).toMappingException();
110       if (longValue < Short.MIN_VALUE)
111         throw new Errors().errorTooSmall(source, destinationType).toMappingException();
112       return Short.valueOf(source.shortValue());
113     }
114
115     if (destinationType.equals(Integer.class)) {
116       long longValue = source.longValue();
117       if (longValue > Integer.MAX_VALUE)
118         throw new Errors().errorTooLarge(source, destinationType).toMappingException();
119       if (longValue < Integer.MIN_VALUE)
120         throw new Errors().errorTooSmall(source, destinationType).toMappingException();
121       return Integer.valueOf(source.intValue());
122     }
123
124     if (destinationType.equals(Long.class))
125       return Long.valueOf(source.longValue());
126
127     if (destinationType.equals(Float.class)) {
128       if (source.doubleValue() > Float.MAX_VALUE)
129         throw new Errors().errorTooLarge(source, destinationType).toMappingException();
130       return Float.valueOf(source.floatValue());
131     }
132
133     if (destinationType.equals(Double.class))
134       return Double.valueOf(source.doubleValue());
135
136     if (destinationType.equals(BigDecimal.class)) {
137       if (source instanceof Float || source instanceof Double)
138         return new BigDecimal(source.toString());
139       else if (source instanceof BigInteger)
140         return new BigDecimal((BigInteger) source);
141       else
142         return BigDecimal.valueOf(source.longValue());
143     }
144
145     if (destinationType.equals(BigInteger.class)) {
146       if (source instanceof BigDecimal)
147         return ((BigDecimal) source).toBigInteger();
148       else
149         return BigInteger.valueOf(source.longValue());
150     }
151
152     throw new Errors().errorMapping(source, destinationType).toMappingException();
153   }
154
155   /**
156    * Creates a Number for the {@code source} and {@code destinationType}.
157    */

158   Number numberFor(String source, Class<?> destinationType) {
159     String sourceString = source.trim();
160     if (sourceString.length() == 0)
161       return null;
162
163     try {
164       if (destinationType.equals(Byte.class))
165         return Byte.valueOf(source);
166       if (destinationType.equals(Short.class))
167         return Short.valueOf(source);
168       if (destinationType.equals(Integer.class))
169         return Integer.valueOf(source);
170       if (destinationType.equals(Long.class))
171         return Long.valueOf(source);
172       if (destinationType.equals(Float.class))
173         return Float.valueOf(source);
174       if (destinationType.equals(Double.class))
175         return Double.valueOf(source);
176       if (destinationType.equals(BigDecimal.class))
177         return new BigDecimal(source);
178       if (destinationType.equals(BigInteger.class))
179         return new BigInteger(source);
180     } catch (Exception e) {
181       throw new Errors().errorMapping(source, destinationType, e).toMappingException();
182     }
183
184     throw new Errors().errorMapping(source, destinationType).toMappingException();
185   }
186 }
187