1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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 }