1 | |
package com.lexicalscope.jewel.cli; |
2 | |
|
3 | |
import static com.lexicalscope.fluentreflection.FluentReflection.type; |
4 | |
import static com.lexicalscope.fluentreflection.ReflectionMatchers.*; |
5 | |
|
6 | |
import java.lang.reflect.Type; |
7 | |
import java.util.Collection; |
8 | |
import java.util.LinkedHashSet; |
9 | |
import java.util.List; |
10 | |
import java.util.Set; |
11 | |
|
12 | |
import ch.lambdaj.Lambda; |
13 | |
import ch.lambdaj.function.convert.Converter; |
14 | |
|
15 | |
import com.lexicalscope.fluentreflection.FluentClass; |
16 | |
import com.lexicalscope.fluentreflection.FluentConstructor; |
17 | |
import com.lexicalscope.fluentreflection.FluentMethod; |
18 | |
import com.lexicalscope.fluentreflection.InvocationTargetRuntimeException; |
19 | |
import com.lexicalscope.fluentreflection.ReflectionRuntimeException; |
20 | |
import com.lexicalscope.fluentreflection.TypeToken; |
21 | |
import com.lexicalscope.jewel.cli.specification.OptionSpecification; |
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | |
|
29 | |
|
30 | |
|
31 | |
|
32 | |
|
33 | |
|
34 | |
|
35 | |
|
36 | |
|
37 | |
|
38 | |
|
39 | |
class ConvertTypeOfObject<T> implements Converter<Object, T> { |
40 | |
private final FluentClass<T> reflectedKlass; |
41 | |
private final Class<?> klass; |
42 | |
private final OptionSpecification specification; |
43 | |
private final ValidationErrorBuilder validationErrorBuilder; |
44 | |
|
45 | |
public ConvertTypeOfObject( |
46 | |
final ValidationErrorBuilder validationErrorBuilder, |
47 | |
final OptionSpecification specification, |
48 | 390 | final FluentClass<T> reflectedKlass) { |
49 | 390 | this.validationErrorBuilder = validationErrorBuilder; |
50 | 390 | this.specification = specification; |
51 | 390 | this.reflectedKlass = reflectedKlass; |
52 | 390 | klass = reflectedKlass.classUnderReflection(); |
53 | |
|
54 | 390 | if(klass.equals(void.class)) |
55 | |
{ |
56 | 0 | throw new AssertionError("cannot convert to void"); |
57 | |
} |
58 | 390 | } |
59 | |
|
60 | |
|
61 | |
|
62 | |
|
63 | |
|
64 | |
|
65 | |
|
66 | |
|
67 | |
|
68 | |
|
69 | |
|
70 | |
|
71 | |
|
72 | |
|
73 | |
|
74 | |
|
75 | |
|
76 | |
|
77 | |
|
78 | |
@Override public T convert(final Object value) { |
79 | 310 | if (value == null) { |
80 | 24 | return null; |
81 | 286 | } else if (isIterable() && Iterable.class.isAssignableFrom(value.getClass())) { |
82 | 68 | return convertIterable(value); |
83 | 218 | } else if (reflectedKlass.assignableFromObject(value)) { |
84 | 68 | return (T) value; |
85 | 150 | } else if (reflectedKlass.canBeUnboxed(value.getClass())) { |
86 | 0 | return (T) value; |
87 | 150 | } else if (reflectedKlass.canBeBoxed(value.getClass())) { |
88 | 0 | return (T) value; |
89 | |
} |
90 | |
|
91 | |
FluentClass<?> klassToCreate; |
92 | 150 | if (reflectedKlass.isPrimitive()) { |
93 | 40 | klassToCreate = reflectedKlass.boxedType(); |
94 | |
} else { |
95 | 110 | klassToCreate = reflectedKlass; |
96 | |
} |
97 | |
|
98 | 150 | return (T) convertValueTo(value, klassToCreate); |
99 | |
} |
100 | |
|
101 | |
private <S> S convertValueTo(final Object value, final FluentClass<S> klassToCreate) { |
102 | |
try |
103 | |
{ |
104 | 150 | final List<FluentMethod> valueOfMethods = |
105 | |
klassToCreate.methods(hasName("valueOf").and(hasArguments(value.getClass())).and( |
106 | |
hasType(klass))); |
107 | |
|
108 | 150 | if (!valueOfMethods.isEmpty()) { |
109 | 90 | return (S) valueOfMethods.get(0).call(value).value(); |
110 | |
} |
111 | |
|
112 | 60 | final List<FluentConstructor<S>> singleArgumentConstructors = |
113 | |
klassToCreate.constructors(hasArguments(value.getClass())); |
114 | |
|
115 | 60 | if (!singleArgumentConstructors.isEmpty()) { |
116 | 46 | return singleArgumentConstructors.get(0).call(value).value(); |
117 | |
} |
118 | |
|
119 | 14 | final List<FluentConstructor<S>> typeTokenConstructors = |
120 | |
klassToCreate.constructors(hasArguments(value.getClass(), Type.class)); |
121 | |
|
122 | 14 | if (!typeTokenConstructors.isEmpty()) { |
123 | 2 | return typeTokenConstructors.get(0).call(value, klassToCreate.type()).value(); |
124 | |
} |
125 | |
|
126 | 12 | if (klassToCreate.classUnderReflection().equals(Character.class) && value.getClass().equals(String.class)) { |
127 | |
|
128 | 12 | final String stringValue = (String) value; |
129 | 12 | if (stringValue.length() == 1) { |
130 | 8 | return (S) Character.valueOf(stringValue.charAt(0)); |
131 | |
} |
132 | |
else |
133 | |
{ |
134 | 4 | validationErrorBuilder.invalidValueForType( |
135 | |
specification, |
136 | |
String.format("value is not a character (%s)", value)); |
137 | 4 | return null; |
138 | |
} |
139 | |
} |
140 | 6 | } catch (final InvocationTargetRuntimeException e) { |
141 | 6 | final Throwable cause = e.getExceptionThrownByInvocationTarget(); |
142 | 6 | if (cause instanceof NumberFormatException) |
143 | |
{ |
144 | 6 | validationErrorBuilder.invalidValueForType( |
145 | |
specification, |
146 | |
unsupportedNumberFormatMessage((NumberFormatException) cause)); |
147 | |
} |
148 | |
else |
149 | |
{ |
150 | 0 | validationErrorBuilder.invalidValueForType(specification, cause.getMessage()); |
151 | |
} |
152 | 6 | return null; |
153 | 0 | } catch (final ReflectionRuntimeException e) |
154 | |
{ |
155 | 0 | validationErrorBuilder.unableToConstructType(specification, e.getMessage()); |
156 | 0 | return null; |
157 | 0 | } |
158 | |
|
159 | 0 | throw new ClassCastException(String.format("cannot convert %s to %s", value.getClass(), klass)); |
160 | |
} |
161 | |
|
162 | |
private T convertIterable(final Object value) { |
163 | 68 | final FluentClass<?> desiredCollectionReflectedType = |
164 | |
reflectedKlass.asType(reflectingOn(Iterable.class)).typeArgument(0); |
165 | |
|
166 | 68 | final List<Object> convertedTypes = Lambda.convert(value, |
167 | |
new ConvertTypeOfObject<Object>( |
168 | |
validationErrorBuilder, |
169 | |
specification, |
170 | |
(FluentClass<Object>) desiredCollectionReflectedType)); |
171 | |
|
172 | 68 | if (List.class.isAssignableFrom(klass) && Collection.class.isAssignableFrom(klass)) { |
173 | 62 | return (T) convertedTypes; |
174 | 6 | } else if (Set.class.isAssignableFrom(klass)) { |
175 | 2 | return (T) new LinkedHashSet<Object>(convertedTypes); |
176 | |
} |
177 | |
|
178 | 4 | return (T) convertedTypes; |
179 | |
} |
180 | |
|
181 | |
private boolean isIterable() { |
182 | 286 | return Iterable.class.isAssignableFrom(klass); |
183 | |
} |
184 | |
|
185 | |
static <T> ConvertTypeOfObject<T> converterTo( |
186 | |
final ValidationErrorBuilder validationErrorBuilder, |
187 | |
final OptionSpecification specification, |
188 | |
final FluentMethod method) { |
189 | |
FluentClass<T> methodType; |
190 | 306 | if (isMutator().matches(method)) |
191 | |
{ |
192 | 42 | methodType = (FluentClass<T>) method.args().get(0); |
193 | |
} |
194 | |
else |
195 | |
{ |
196 | 264 | methodType = (FluentClass<T>) method.type(); |
197 | |
} |
198 | 306 | return converterTo(validationErrorBuilder, specification, methodType); |
199 | |
} |
200 | |
|
201 | |
private static <T> ConvertTypeOfObject<T> converterTo( |
202 | |
final ValidationErrorBuilder validationErrorBuilder, |
203 | |
final OptionSpecification specification, |
204 | |
final FluentClass<T> type) { |
205 | 322 | return new ConvertTypeOfObject<T>(validationErrorBuilder, specification, type); |
206 | |
} |
207 | |
|
208 | |
static <T> ConvertTypeOfObject<T> converterTo( |
209 | |
final ValidationErrorBuilder validationErrorBuilder, |
210 | |
final OptionSpecification specification, |
211 | |
final Class<T> klass) { |
212 | 8 | return converterTo(validationErrorBuilder, specification, type(klass)); |
213 | |
} |
214 | |
|
215 | |
static <T> ConvertTypeOfObject<T> converterTo( |
216 | |
final ValidationErrorBuilder validationErrorBuilder, |
217 | |
final OptionSpecification specification, |
218 | |
final TypeToken<T> token) { |
219 | 8 | return converterTo(validationErrorBuilder, specification, type(token)); |
220 | |
} |
221 | |
|
222 | |
private String unsupportedNumberFormatMessage(final NumberFormatException e1) |
223 | |
{ |
224 | 6 | return "Unsupported number format: " + e1.getMessage(); |
225 | |
} |
226 | |
} |