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.lang.reflect.Constructor;
19  import java.util.HashMap;
20  import java.util.Locale;
21  import java.util.Set;
22  
23  import net.sf.morph.transform.DecoratedConverter;
24  import net.sf.morph.transform.ExplicitTransformer;
25  import net.sf.morph.transform.ImpreciseTransformer;
26  import net.sf.morph.transform.TransformationException;
27  import net.sf.morph.transform.transformers.BaseTransformer;
28  import net.sf.morph.util.ClassUtils;
29  import net.sf.morph.util.ContainerUtils;
30  import net.sf.morph.util.TransformerUtils;
31  
32  /**
33   * Converts text types ({@link java.lang.String},
34   * {@link java.lang.StringBuffer} and {@link java.lang.Character},
35   * <code>char[]</code> and <code>byte[]</code> from one type to another.
36   * Empty Strings, StringBuffers with lengths of zero and empty character and
37   * byte arrays are converted to <code>null</code> Characters, and non-empty
38   * Strings and StringBuffers are converted to Characters by returning the first
39   * character in the String or StringBuffer. CharSequence is handled in this way:
40   * an explicit request for CharSequence yields a String. Other CharSequence
41   * implementations are handled if they have a public constructor that accepts
42   * a String argument.
43   *
44   * @author Matt Sgarlata
45   * @since Jan 2, 2005
46   */
47  public class TextConverter extends BaseTransformer implements DecoratedConverter,
48  		ExplicitTransformer, ImpreciseTransformer {
49  
50  	private static final Class CHAR_SEQUENCE = ClassUtils.isJdk14OrHigherPresent() ? ClassUtils
51  			.convertToClass("java.lang.CharSequence")
52  			: null;
53  
54  	private static final HashMap CONSTRUCTOR_CACHE = new HashMap();
55  	private static final Class[] SOURCE_AND_DESTINATION_TYPES;
56  
57  	static {
58  		Set s = ContainerUtils.createOrderedSet();
59  		
60  		s.add(StringBuffer.class);
61  		s.add(String.class);
62  		s.add(CHAR_SEQUENCE);
63  		s.add(byte[].class);
64  		s.add(char[].class);
65  		s.add(Character.class);
66  		s.add(char.class);
67  		s.add(null);
68  		SOURCE_AND_DESTINATION_TYPES = (Class[]) s.toArray(new Class[s.size()]);
69  	}
70  
71  	private boolean allowStringAsChar = true;
72  	private boolean emptyNull;
73  
74  	/**
75  	 * {@inheritDoc}
76  	 */
77  	protected Object convertImpl(Class destinationClass, Object source, Locale locale)
78  			throws Exception {
79  		Class sourceClass = ClassUtils.getClass(source);
80  		if (destinationClass == null
81  				&& ClassUtils.inheritanceContains(getSourceClasses(), sourceClass)) {
82  			return null;
83  		}
84  		if (isChar(destinationClass) && isChar(sourceClass)) {
85  			return source;
86  		}
87  		//if source == null, not automatically handling nulls, therefore null -> "":
88  		String string = source == null ? "" : source instanceof byte[] ? new String(
89  				(byte[]) source) : source instanceof char[] ? new String((char[]) source)
90  				: source.toString();
91  
92  		if (isChar(destinationClass)) {
93  			if (!isAllowStringAsChar()) {
94  				throw new TransformationException(destinationClass, source);
95  			}
96  			if ("".equals(string)) {
97  				if (destinationClass == char.class) {
98  					throw new TransformationException(destinationClass, source);
99  				}
100 				return null;
101 			}
102 			return new Character(string.charAt(0));
103 		}
104 		if (destinationClass == String.class
105 				|| (destinationClass == CHAR_SEQUENCE && CHAR_SEQUENCE != null)) {
106 			return string;
107 		}
108 		if (destinationClass == byte[].class) {
109 			return string.getBytes();
110 		}
111 		if (destinationClass == char[].class) {
112 			return string.toCharArray();
113 		}
114 		if (ClassUtils.inheritanceContains(getDestinationClasses(), destinationClass)
115 				&& canCreate(destinationClass)) {
116 			try {
117 				return getConstructor(destinationClass).newInstance(new Object[] { string });
118 			} catch (Exception e) {
119 				throw new TransformationException(destinationClass, source, e);
120 			}
121 		}
122 		throw new TransformationException(destinationClass, source);
123 	}
124 
125 	/**
126 	 * {@inheritDoc}
127 	 */
128 	protected boolean isTransformableImpl(Class destinationType, Class sourceType)
129 			throws Exception {
130 		if (!TransformerUtils.isImplicitlyTransformable(this, destinationType, sourceType)) {
131 			return false;
132 		}
133 		if (destinationType == null) {
134 			return true;
135 		}
136 		if (sourceType == null) {
137 			return !destinationType.isPrimitive();
138 		}
139 		if (isChar(destinationType)) {
140 			return isChar(sourceType) || isAllowStringAsChar();
141 		}
142 		if (isCharSequence(destinationType)) {
143 			return canCreate(destinationType);
144 		}
145 		return ClassUtils.inheritanceContains(getDestinationClasses(), destinationType);
146 	}
147 
148 	/**
149 	 * {@inheritDoc}
150 	 */
151 	protected boolean isImpreciseTransformationImpl(Class destinationClass, Class sourceClass) {
152 		if (super.isImpreciseTransformationImpl(destinationClass, sourceClass)) {
153 			return true;
154 		}
155 		return isChar(destinationClass) && !isChar(sourceClass);
156 	}
157 
158 	/**
159 	 * {@inheritDoc}
160 	 */
161 	protected boolean isAutomaticallyHandlingNulls() {
162 		return !isEmptyNull();
163 	}
164 
165 	/**
166 	 * {@inheritDoc}
167 	 */
168 	protected Class[] getSourceClassesImpl() throws Exception {
169 		return SOURCE_AND_DESTINATION_TYPES;
170 	}
171 
172 	/**
173 	 * {@inheritDoc}
174 	 */
175 	protected Class[] getDestinationClassesImpl() throws Exception {
176 		return SOURCE_AND_DESTINATION_TYPES;
177 	}
178 
179 	/**
180 	 * Learn whether <code>null</code> values return as empty strings.
181 	 * @return boolean
182 	 */
183 	public boolean isEmptyNull() {
184 		return emptyNull;
185 	}
186 
187 	/**
188 	 * Set whether <code>null</code> values return as empty strings.
189 	 * @param emptyNull boolean
190 	 */
191 	public void setEmptyNull(boolean emptyNull) {
192 		this.emptyNull = emptyNull;
193 	}
194 
195 	/**
196 	 * Learn whether string-to-char type conversions are allowed.
197 	 * @return boolean
198 	 */
199 	public boolean isAllowStringAsChar() {
200 		return allowStringAsChar;
201 	}
202 
203 	/**
204 	 * Set whether string-to-char type conversions are allowed. This might be
205 	 * undesirable for e.g. chained transformations or any operation where
206 	 * a loss of "precision" might be detrimental. Default <code>true</code>.
207 	 * @param allowStringAsChar the boolean to set
208 	 */
209 	public void setAllowStringAsChar(boolean allowStringAsChar) {
210 		this.allowStringAsChar = allowStringAsChar;
211 	}
212 
213 	private static boolean isChar(Class c) {
214 		return c == char.class || c == Character.class;
215 	}
216 
217 	private static boolean isCharSequence(Class c) {
218 		return CHAR_SEQUENCE != null && CHAR_SEQUENCE.isAssignableFrom(c);
219 	}
220 
221 	private static synchronized boolean canCreate(Class c) {
222 		if (CONSTRUCTOR_CACHE.containsKey(c)) {
223 			return true;
224 		}
225 		Constructor[] cs = c.getConstructors();
226 		for (int i = 0; i < cs.length; i++) {
227 			Class[] p = cs[i].getParameterTypes();
228 			if (p.length == 1 && p[0].isAssignableFrom(String.class)) {
229 				CONSTRUCTOR_CACHE.put(c, cs[i]);
230 				return true;
231 			}
232 		}
233 		return false;
234 	}
235 
236 	private static Constructor getConstructor(Class c) {
237 		return (Constructor) CONSTRUCTOR_CACHE.get(c);
238 	}
239 
240 }