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 final FluentClass<T> reflectedKlass) {
49 this.validationErrorBuilder = validationErrorBuilder;
50 this.specification = specification;
51 this.reflectedKlass = reflectedKlass;
52 klass = reflectedKlass.classUnderReflection();
53
54 if(klass.equals(void.class))
55 {
56 throw new AssertionError("cannot convert to void");
57 }
58 }
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 if (value == null) {
80 return null;
81 } else if (isIterable() && Iterable.class.isAssignableFrom(value.getClass())) {
82 return convertIterable(value);
83 } else if (reflectedKlass.assignableFromObject(value)) {
84 return (T) value;
85 } else if (reflectedKlass.canBeUnboxed(value.getClass())) {
86 return (T) value;
87 } else if (reflectedKlass.canBeBoxed(value.getClass())) {
88 return (T) value;
89 }
90
91 FluentClass<?> klassToCreate;
92 if (reflectedKlass.isPrimitive()) {
93 klassToCreate = reflectedKlass.boxedType();
94 } else {
95 klassToCreate = reflectedKlass;
96 }
97
98 return (T) convertValueTo(value, klassToCreate);
99 }
100
101 private <S> S convertValueTo(final Object value, final FluentClass<S> klassToCreate) {
102 try
103 {
104 final List<FluentMethod> valueOfMethods =
105 klassToCreate.methods(hasName("valueOf").and(hasArguments(value.getClass())).and(
106 hasType(klass)));
107
108 if (!valueOfMethods.isEmpty()) {
109 return (S) valueOfMethods.get(0).call(value).value();
110 }
111
112 final List<FluentConstructor<S>> singleArgumentConstructors =
113 klassToCreate.constructors(hasArguments(value.getClass()));
114
115 if (!singleArgumentConstructors.isEmpty()) {
116 return singleArgumentConstructors.get(0).call(value).value();
117 }
118
119 final List<FluentConstructor<S>> typeTokenConstructors =
120 klassToCreate.constructors(hasArguments(value.getClass(), Type.class));
121
122 if (!typeTokenConstructors.isEmpty()) {
123 return typeTokenConstructors.get(0).call(value, klassToCreate.type()).value();
124 }
125
126 if (klassToCreate.classUnderReflection().equals(Character.class) && value.getClass().equals(String.class)) {
127
128 final String stringValue = (String) value;
129 if (stringValue.length() == 1) {
130 return (S) Character.valueOf(stringValue.charAt(0));
131 }
132 else
133 {
134 validationErrorBuilder.invalidValueForType(
135 specification,
136 String.format("value is not a character (%s)", value));
137 return null;
138 }
139 }
140 } catch (final InvocationTargetRuntimeException e) {
141 final Throwable cause = e.getExceptionThrownByInvocationTarget();
142 if (cause instanceof NumberFormatException)
143 {
144 validationErrorBuilder.invalidValueForType(
145 specification,
146 unsupportedNumberFormatMessage((NumberFormatException) cause));
147 }
148 else
149 {
150 validationErrorBuilder.invalidValueForType(specification, cause.getMessage());
151 }
152 return null;
153 } catch (final ReflectionRuntimeException e)
154 {
155 validationErrorBuilder.unableToConstructType(specification, e.getMessage());
156 return null;
157 }
158
159 throw new ClassCastException(String.format("cannot convert %s to %s", value.getClass(), klass));
160 }
161
162 private T convertIterable(final Object value) {
163 final FluentClass<?> desiredCollectionReflectedType =
164 reflectedKlass.asType(reflectingOn(Iterable.class)).typeArgument(0);
165
166 final List<Object> convertedTypes = Lambda.convert(value,
167 new ConvertTypeOfObject<Object>(
168 validationErrorBuilder,
169 specification,
170 (FluentClass<Object>) desiredCollectionReflectedType));
171
172 if (List.class.isAssignableFrom(klass) && Collection.class.isAssignableFrom(klass)) {
173 return (T) convertedTypes;
174 } else if (Set.class.isAssignableFrom(klass)) {
175 return (T) new LinkedHashSet<Object>(convertedTypes);
176 }
177
178 return (T) convertedTypes;
179 }
180
181 private boolean isIterable() {
182 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 if (isMutator().matches(method))
191 {
192 methodType = (FluentClass<T>) method.args().get(0);
193 }
194 else
195 {
196 methodType = (FluentClass<T>) method.type();
197 }
198 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 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 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 return converterTo(validationErrorBuilder, specification, type(token));
220 }
221
222 private String unsupportedNumberFormatMessage(final NumberFormatException e1)
223 {
224 return "Unsupported number format: " + e1.getMessage();
225 }
226 }