View Javadoc

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   * Copyright 2011 Tim Wood
25   *
26   * Licensed under the Apache License, Version 2.0 (the "License");
27   * you may not use this file except in compliance with the License.
28   * You may obtain a copy of the License at
29   *
30   * http://www.apache.org/licenses/LICENSE-2.0
31   *
32   * Unless required by applicable law or agreed to in writing, software
33   * distributed under the License is distributed on an "AS IS" BASIS,
34   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35   * See the License for the specific language governing permissions and
36   * limitations under the License.
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       * If T is assignable from value, then return the value. Otherwise tries to
62       * create an instance of this type using the provided argument.
63       *
64       * Will first attempt to find a static method called "valueOf" which returns
65       * this type and takes a single argument compatible with the type of the
66       * value given. If that is not found, tries to find a constructor which
67       * takes an argument of the type of the given value. Otherwise throws a
68       * ClassCastException
69       *
70       * @param value
71       *            the value to be converted
72       *
73       * @throws ClassCastException
74       *             if the types cannot be converted
75       *
76       * @return the converted value
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                 // special case for characters from string
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 }