View Javadoc

1   /*
2    * Copyright 2004-2005, 2007-2008 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package net.sf.morph.transform.converters;
17  
18  import java.math.BigDecimal;
19  import java.util.Locale;
20  
21  import net.sf.morph.transform.DecoratedConverter;
22  import net.sf.morph.transform.ImpreciseTransformer;
23  import net.sf.morph.transform.TransformationException;
24  import net.sf.morph.transform.support.NumberRounder;
25  import net.sf.morph.transform.transformers.BaseTransformer;
26  import net.sf.morph.util.ClassUtils;
27  import net.sf.morph.util.NumberUtils;
28  
29  /**
30   * Converts a number from one number type to another.
31   * 
32   * @author Matt Sgarlata
33   * @since Dec 14, 2004
34   */
35  public class NumberConverter extends BaseTransformer implements DecoratedConverter, ImpreciseTransformer {
36  
37  	private static final Class[] SOURCE_AND_DESTINATION_TYPES = {
38  		Number.class, byte.class, short.class, int.class, long.class,
39  		float.class, double.class, null
40  	};
41  
42  	/** Default rounding method */
43  	public static final String DEFAULT_ROUNDING_METHOD = NumberRounder.ROUND_HALF_UP;
44  
45  //	/**
46  //	 * The source classes for number converters.
47  //	 */
48  //	public static final Class[] SOURCE_TYPES = new Class[] {
49  //		Number.class,
50  //		String.class,
51  //		StringBuffer.class,
52  //		Character.class
53  //	};
54  	
55  //	/**
56  //	 * Whether this converter should ensure that data remains consistent when
57  //	 * conversions are performed. The default behavior for Java allows for
58  //	 * rollover to occur when converting larger numbers into smaller numbers.
59  //	 * This causes numbers with a large absolute value to roll over to a random
60  //	 * value which might not even have the same sign as the original number.
61  //	 * (Actually usually the lower bits of the number are retained and the
62  //	 * higher bits are discarded, but I think most end users would tell you
63  //	 * that's pretty random to them!) Usually this behavior is problematic, so
64  //	 * an exception should be thrown. If this value is set to true, a
65  //	 * TransformationException is thrown when such a conversion is attempted.
66  //	 * By default, this value is initialized to <code>true</code> for all
67  //	 * transformers included in the Morph framework.
68  //	 */
69  //	private boolean ensureDataConsistency;
70  
71  	private String roundingMethod;
72  
73  	/**
74  	 * Creates a number converter that is ensuring data consistency.
75  	 */
76  	public NumberConverter() {
77  		super();
78  //		setEnsureDataConsistency(true);
79  		setRoundingMethod(DEFAULT_ROUNDING_METHOD);
80  	}
81  
82  	/**
83  	 * {@inheritDoc}
84  	 */
85  	protected Class[] getSourceClassesImpl() throws Exception {
86  		return SOURCE_AND_DESTINATION_TYPES;
87  	}
88  
89  	/**
90  	 * {@inheritDoc}
91  	 */
92  	protected Class[] getDestinationClassesImpl() throws Exception {
93  		return SOURCE_AND_DESTINATION_TYPES;
94  	}
95  
96  	/**
97  	 * Verify <code>number</code> is within the bounds of <code>destinationClass</code>.  
98  	 * @param destinationClass
99  	 * @param number
100 	 * @throws Exception if validation fails
101 	 */
102 	protected void checkNotOutOfBounds(Class destinationClass, Number number) throws Exception {
103 //		if (isEnsureDataConsistency() &&
104 			if (NumberUtils.isTooBigForType(number, destinationClass)) {
105 				throw new TransformationException(destinationClass, number,
106 					null, number + " is too large to be contained in a "
107 						+ destinationClass.getName());
108 			}
109 			if (NumberUtils.isTooSmallForType(number, destinationClass)) {
110 				throw new TransformationException(destinationClass, number,
111 					null, number + " is too small to be contained in a "
112 						+ destinationClass.getName());
113 			}
114 //		}
115 	}
116 
117 	/**
118 	 * {@inheritDoc}
119 	 */
120 	protected boolean isImpreciseTransformationImpl(Class destinationClass, Class sourceClass) {
121 		return super.isImpreciseTransformationImpl(destinationClass, sourceClass)
122 				|| NumberUtils.NARROWNESS_COMPARATOR.compare(destinationClass,
123 						sourceClass) < 0;
124 	}
125 
126 	/**
127 	 * {@inheritDoc}
128 	 */
129 	protected Object convertImpl(Class destinationClass, Object source,
130 		Locale locale) throws Exception {
131 		if (destinationClass == null) {
132 			return null;
133 		}
134 		if (destinationClass.isPrimitive() && source == null) {
135 			throw new TransformationException(destinationClass, source);
136 		}
137 
138 		// basically this check is to allow conversions of specific types like
139 		// java.util.BigDecimal to the more general java.lang.Number
140 		if (destinationClass.isAssignableFrom(ClassUtils.getClass(source))) {
141 			return source;
142 		}
143 		checkNotOutOfBounds(destinationClass, (Number) source);
144 
145 		String numberStr;
146 		if (isDecimal(destinationClass)) {
147 			numberStr = source.toString();
148 		}
149 		else {
150 			BigDecimal bigDecimal = new BigDecimal(source.toString());
151 			bigDecimal = bigDecimal.setScale(0,
152 				NumberRounder.getBigDecimalRoundMode(getRoundingMethod()));
153 			numberStr = bigDecimal.toString();
154 		}
155 		return NumberUtils.getNumber(destinationClass, numberStr);
156 	}
157 
158 	/**
159 	 * Learn whether <code>numberType</code> is a decimal type
160 	 * @param numberType
161 	 * @return boolean
162 	 */
163 	protected boolean isDecimal(Class numberType) {
164 		return numberType == double.class || numberType == Double.class
165 				|| numberType == float.class || numberType == Float.class
166 				|| BigDecimal.class.isAssignableFrom(numberType);
167 	}
168 
169 //	/**
170 //	 * Gets whether this converter should ensure that data remains consistent
171 //	 * when conversions are performed. The default behavior for Java allows for
172 //	 * rollover to occur when converting larger numbers into smaller numbers.
173 //	 * This causes numbers with a large absolute value to roll over to a random
174 //	 * value which might not even have the same sign as the original number.
175 //	 * (Actually usually the lower bits of the number are retained and the
176 //	 * higher bits are discarded, but I think most end users would tell you
177 //	 * that's pretty random to them!) Usually this behavior is problematic, so
178 //	 * an exception should be thrown. If this value is set to true, a
179 //	 * TransformationException is thrown when such a conversion is attempted.
180 //	 * By default, this value is initialized to <code>true</code> for all
181 //	 * transformers included in the Morph framework.
182 //	 * 
183 //	 * @return whether data consistency should be ensured
184 //	 */
185 //	public boolean isEnsureDataConsistency() {
186 //		return ensureDataConsistency;
187 //	}
188 //
189 //	/**
190 //	 * Sets whether this converter should ensure that data remains consistent
191 //	 * when conversions are performed. The default behavior for Java allows for
192 //	 * rollover to occur when converting larger numbers into smaller numbers.
193 //	 * This causes numbers with a large absolute value to roll over to a random
194 //	 * value which might not even have the same sign as the original number.
195 //	 * (Actually usually the lower bits of the number are retained and the
196 //	 * higher bits are discarded, but I think most end users would tell you
197 //	 * that's pretty random to them!) Usually this behavior is problematic, so
198 //	 * an exception should be thrown. If this value is set to true, a
199 //	 * TransformationException is thrown when such a conversion is attempted.
200 //	 * By default, this value is initialized to <code>true</code> for all
201 //	 * transformers included in the Morph framework.
202 //	 * 
203 //	 * @param ensureDataConsistency
204 //	 *            whether data consistency should be ensured
205 //	 */
206 //	public void setEnsureDataConsistency(boolean ensureDataConsistency) {
207 //		this.ensureDataConsistency = ensureDataConsistency;
208 //	}
209 
210 	/**
211 	 * Get the rounding method used by this NumberConverter.
212 	 * @return String
213 	 */
214 	public String getRoundingMethod() {
215 		if (roundingMethod == null) {
216 			setRoundingMethod(DEFAULT_ROUNDING_METHOD);
217 		}
218 		return roundingMethod;
219 	}
220 
221 	/**
222 	 * Set the rounding method used by this NumberConverter.
223 	 * @param roundingMethod
224 	 */
225 	public void setRoundingMethod(
226 		String roundingMethod) {
227 		this.roundingMethod = roundingMethod;
228 	}
229 
230 	/**
231 	 * {@inheritDoc}
232 	 */
233 	protected boolean isWrappingRuntimeExceptions() {
234 	    return true;
235     }
236 
237 }