package com.lexicalscope.jewel.cli; import static com.lexicalscope.fluentreflection.FluentReflection.type; import static com.lexicalscope.fluentreflection.ReflectionMatchers.*; import java.lang.reflect.Type; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import ch.lambdaj.Lambda; import ch.lambdaj.function.convert.Converter; import com.lexicalscope.fluentreflection.FluentClass; import com.lexicalscope.fluentreflection.FluentConstructor; import com.lexicalscope.fluentreflection.FluentMethod; import com.lexicalscope.fluentreflection.InvocationTargetRuntimeException; import com.lexicalscope.fluentreflection.ReflectionRuntimeException; import com.lexicalscope.fluentreflection.TypeToken; import com.lexicalscope.jewel.cli.specification.OptionSpecification; /* * Copyright 2011 Tim Wood * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class ConvertTypeOfObject<T> implements Converter<Object, T> { private final FluentClass<T> reflectedKlass; private final Class<?> klass; private final OptionSpecification specification; private final ValidationErrorBuilder validationErrorBuilder; public ConvertTypeOfObject( final ValidationErrorBuilder validationErrorBuilder, final OptionSpecification specification, final FluentClass<T> reflectedKlass) { this.validationErrorBuilder = validationErrorBuilder; this.specification = specification; this.reflectedKlass = reflectedKlass; klass = reflectedKlass.classUnderReflection(); if(klass.equals(void.class)) { throw new AssertionError("cannot convert to void"); } } /** * If T is assignable from value, then return the value. Otherwise tries to * create an instance of this type using the provided argument. * * Will first attempt to find a static method called "valueOf" which returns * this type and takes a single argument compatible with the type of the * value given. If that is not found, tries to find a constructor which * takes an argument of the type of the given value. Otherwise throws a * ClassCastException * * @param value * the value to be converted * * @throws ClassCastException * if the types cannot be converted * * @return the converted value */ @Override public T convert(final Object value) { if (value == null) { return null; } else if (isIterable() && Iterable.class.isAssignableFrom(value.getClass())) { return convertIterable(value); } else if (reflectedKlass.assignableFromObject(value)) { return (T) value; } else if (reflectedKlass.canBeUnboxed(value.getClass())) { return (T) value; } else if (reflectedKlass.canBeBoxed(value.getClass())) { return (T) value; } FluentClass<?> klassToCreate; if (reflectedKlass.isPrimitive()) { klassToCreate = reflectedKlass.boxedType(); } else { klassToCreate = reflectedKlass; } return (T) convertValueTo(value, klassToCreate); } private <S> S convertValueTo(final Object value, final FluentClass<S> klassToCreate) { try { final List<FluentMethod> valueOfMethods = klassToCreate.methods(hasName("valueOf").and(hasArguments(value.getClass())).and( hasType(klass))); if (!valueOfMethods.isEmpty()) { return (S) valueOfMethods.get(0).call(value).value(); } final List<FluentConstructor<S>> singleArgumentConstructors = klassToCreate.constructors(hasArguments(value.getClass())); if (!singleArgumentConstructors.isEmpty()) { return singleArgumentConstructors.get(0).call(value).value(); } final List<FluentConstructor<S>> typeTokenConstructors = klassToCreate.constructors(hasArguments(value.getClass(), Type.class)); if (!typeTokenConstructors.isEmpty()) { return typeTokenConstructors.get(0).call(value, klassToCreate.type()).value(); } if (klassToCreate.classUnderReflection().equals(Character.class) && value.getClass().equals(String.class)) { // special case for characters from string final String stringValue = (String) value; if (stringValue.length() == 1) { return (S) Character.valueOf(stringValue.charAt(0)); } else { validationErrorBuilder.invalidValueForType( specification, String.format("value is not a character (%s)", value)); return null; } } } catch (final InvocationTargetRuntimeException e) { final Throwable cause = e.getExceptionThrownByInvocationTarget(); if (cause instanceof NumberFormatException) { validationErrorBuilder.invalidValueForType( specification, unsupportedNumberFormatMessage((NumberFormatException) cause)); } else { validationErrorBuilder.invalidValueForType(specification, cause.getMessage()); } return null; } catch (final ReflectionRuntimeException e) { validationErrorBuilder.unableToConstructType(specification, e.getMessage()); return null; } throw new ClassCastException(String.format("cannot convert %s to %s", value.getClass(), klass)); } private T convertIterable(final Object value) { final FluentClass<?> desiredCollectionReflectedType = reflectedKlass.asType(reflectingOn(Iterable.class)).typeArgument(0); final List<Object> convertedTypes = Lambda.convert(value, new ConvertTypeOfObject<Object>( validationErrorBuilder, specification, (FluentClass<Object>) desiredCollectionReflectedType)); if (List.class.isAssignableFrom(klass) && Collection.class.isAssignableFrom(klass)) { return (T) convertedTypes; } else if (Set.class.isAssignableFrom(klass)) { return (T) new LinkedHashSet<Object>(convertedTypes); } return (T) convertedTypes; } private boolean isIterable() { return Iterable.class.isAssignableFrom(klass); } static <T> ConvertTypeOfObject<T> converterTo( final ValidationErrorBuilder validationErrorBuilder, final OptionSpecification specification, final FluentMethod method) { FluentClass<T> methodType; if (isMutator().matches(method)) { methodType = (FluentClass<T>) method.args().get(0); } else { methodType = (FluentClass<T>) method.type(); } return converterTo(validationErrorBuilder, specification, methodType); } private static <T> ConvertTypeOfObject<T> converterTo( final ValidationErrorBuilder validationErrorBuilder, final OptionSpecification specification, final FluentClass<T> type) { return new ConvertTypeOfObject<T>(validationErrorBuilder, specification, type); } static <T> ConvertTypeOfObject<T> converterTo( final ValidationErrorBuilder validationErrorBuilder, final OptionSpecification specification, final Class<T> klass) { return converterTo(validationErrorBuilder, specification, type(klass)); } static <T> ConvertTypeOfObject<T> converterTo( final ValidationErrorBuilder validationErrorBuilder, final OptionSpecification specification, final TypeToken<T> token) { return converterTo(validationErrorBuilder, specification, type(token)); } private String unsupportedNumberFormatMessage(final NumberFormatException e1) { return "Unsupported number format: " + e1.getMessage(); } }