Coverage Report - com.lexicalscope.jewel.cli.ConvertTypeOfObject
 
Classes in this File Line Coverage Branch Coverage Complexity
ConvertTypeOfObject
85%
55/64
78%
30/38
4.7
 
 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  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  
      * 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  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  
                 // special case for characters from string
 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  
 }