/** * Copyright 2014 Opower, Inc. * 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. **/ package com.opower.rest.client.generator.util; import com.google.common.base.Throwables; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A utility class that can convert a String value as a typed object. * * @author <a href="ryan@damnhandy.com>Ryan J. McDonough</a> * @version $Revision: $ */ public final class TypeConverter { private static final String VALUE_OF_METHOD = "valueOf"; private static final Logger logger = LoggerFactory.getLogger(TypeConverter.class); /** * A map of primitive to objects. */ private static final Map<Class<?>, Class<?>> PRIMITIVES; static { PRIMITIVES = new HashMap<Class<?>, Class<?>>(); PRIMITIVES.put(int.class, Integer.class); PRIMITIVES.put(double.class, Double.class); PRIMITIVES.put(float.class, Float.class); PRIMITIVES.put(short.class, Short.class); PRIMITIVES.put(byte.class, Byte.class); PRIMITIVES.put(long.class, Long.class); } private TypeConverter() { } /** * A generic method that returns the {@link String} as the specified Java type. * * @param <T> the type to return * @param source the string value to convert * @return the object instance */ public static <T> T getType(final Class<T> targetType, final String source) { // just return that source if it's a String if (String.class.equals(targetType)) { return targetType.cast(source); } /* * Dates are too complicated for this class. */ if (Date.class.isAssignableFrom(targetType)) { throw new IllegalArgumentException("Date instances are not supported by this class."); } T result; // boolean types need special handling if (Boolean.class.equals(targetType) || boolean.class.equals(targetType)) { return targetType.cast(getBooleanValue(source)); } try { result = getTypeViaValueOfMethod(source, targetType); } catch (NoSuchMethodException e) { logger.warn("No valueOf() method available for {0}, trying constructor...", targetType .getSimpleName()); result = getTypeViaStringConstructor(source, targetType); } return result; } /** * Tests if the class can safely be converted from a String to the * specified type. * * @param targetType the type to convert to * @return true if the class possesses either a "valueOf()" method or a constructor with a String * parameter. */ public static boolean isConvertable(final Class<?> targetType) { if (Boolean.class.equals(targetType)) { return true; } if (targetType.isPrimitive()) { return true; } try { targetType.getDeclaredMethod(VALUE_OF_METHOD, String.class); return true; } catch (NoSuchMethodException e) { try { targetType.getDeclaredConstructor(String.class); return true; } catch (NoSuchMethodException e1) { return false; } } } /** * <p> * Returns a Boolean value from a String. Unlike {@link Boolean.#valueOf(String)}, this * method takes more String options. The following String values will return true: * </p> * <ul> * <li>Yes</li> * <li>Y</li> * <li>T</li> * <li>1</li> * </ul> * <p> * While the following values will return false: * </p> * <ul> * <li>No</li> * <li>N</li> * <li>F</li> * <li>0</li> * </ul> */ public static Boolean getBooleanValue(final String source) { if ("Y".equalsIgnoreCase(source) || "T".equalsIgnoreCase(source) || "Yes".equalsIgnoreCase(source) || "1".equalsIgnoreCase(source)) { return Boolean.TRUE; } else if ("N".equals(source) || "F".equals(source) || "No".equals(source) || "0".equalsIgnoreCase(source)) { return Boolean.FALSE; } return Boolean.valueOf(source); } /** * @param <T> * @param source * @param targetType * @return * @throws NoSuchMethodException */ @SuppressWarnings("unchecked") public static <T> T getTypeViaValueOfMethod(final String source, final Class<T> targetType) throws NoSuchMethodException { Class<?> actualTarget = targetType; /* * if this is a primitive type, use the Object class's "valueOf()" * method. */ if (targetType.isPrimitive()) { actualTarget = PRIMITIVES.get(targetType); } T result = null; try { // if the type has a static "valueOf()" method, try and create the instance that way Method valueOf = actualTarget.getDeclaredMethod(VALUE_OF_METHOD, String.class); Object value = valueOf.invoke(null, source); if (actualTarget.equals(targetType) && targetType.isInstance(value)) { result = targetType.cast(value); } /* * handle the primitive case */ else if (!actualTarget.equals(targetType) && actualTarget.isInstance(value)) { // because you can't use targetType.cast() with primitives. result = (T) value; } } catch (IllegalAccessException e) { Throwables.propagate(e); } catch (InvocationTargetException e) { Throwables.propagate(e.getTargetException()); } return result; } /** * @param <T> * @param source * @param targetType * @return * @throws IllegalArgumentException * @throws InstantiationException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ private static <T> T getTypeViaStringConstructor(String source, Class<T> targetType) { T result = null; Constructor<T> c = null; try { c = targetType.getDeclaredConstructor(String.class); } catch (NoSuchMethodException e) { String msg = new StringBuilder().append(targetType.getName()).append( " has no String constructor").toString(); throw new IllegalArgumentException(msg, e); } try { result = c.newInstance(source); } catch (IllegalAccessException | InstantiationException | IllegalArgumentException e) { Throwables.propagate(e); } catch (InvocationTargetException e) { Throwables.propagate(e.getTargetException()); } return result; } }