package org.dcache.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigInteger; public class OptionParser { private static final Logger LOGGER = LoggerFactory.getLogger(OptionParser.class); private final Args args; public OptionParser(Args args) { this.args = args; } /** * Injects the options of {@code args} and default values into {@code object} using the * Option annotation of fields of {@code <T>}. * * @param args Arguments providing option values. * @param object Target object to inject options into. * @param <T> Type of the target object. * @return Returns {@code object}. */ public static <T> T inject(Args args, T object) { return new OptionParser(args).inject(object); } /** * Injects the options default values for options into {@code object} using the * Option annotation of fields of {@code <T>}. * * @param object Target object to inject options into. * @param <T> Type of the target object. * @return Returns {@code object}. */ public static <T> T injectDefaults(T object) { return inject(new Args(new String[0]), object); } /** * Convert an instance to a specific type (kind of intelligent * casting). Note: you can set primitive types as input * <i>type</i> but the return type will be the corresponding * wrapper type (e.g. Integer.TYPE will result in Integer.class) * with the difference that instead of a result 'null' a numeric 0 * (or boolean false) will be returned because primitive types * can't be null. * <p> * <p> * Supported simple destination types are: * <ul> * <li>java.lang.Boolean, Boolean.TYPE (= boolean.class) * <li>java.lang.Byte, Byte.TYPE (= byte.class) * <li>java.lang.Character, Character.TYPE (= char.class) * <li>java.lang.Double, Double.TYPE (= double.class) * <li>java.lang.Float, Float.TYPE (= float.class) * <li>java.lang.Integer, Integer.TYPE (= int.class) * <li>java.lang.Long, Long.TYPE (= long.class) * <li>java.lang.Short, Short.TYPE (= short.class) * <li>java.lang.String * <li>java.math.BigDecimal * <li>java.math.BigInteger * <li>java.lang.Class * </ul> * * @param object Instance to convert. * @param type Destination type (e.g. Boolean.class). * @return Converted instance/datatype/collection or null if * input object is null. * @throws ClassCastException if <i>object</i> can't be converted to * <i>type</i>. * @author MartinHilpert at SUN's Java Forum */ @SuppressWarnings("unchecked") public static <T> T toType(final Object object, final Class<T> type) { if (object == null) { // initialize primitive types: if (type == Boolean.TYPE) { return ((Class<T>) Boolean.class).cast(false); } else if (type == Byte.TYPE) { return ((Class<T>) Byte.class).cast(0); } else if (type == Character.TYPE) { return ((Class<T>) Character.class).cast(0); } else if (type == Double.TYPE) { return ((Class<T>) Double.class).cast(0.0); } else if (type == Float.TYPE) { return ((Class<T>) Float.class).cast(0.0); } else if (type == Integer.TYPE) { return ((Class<T>) Integer.class).cast(0); } else if (type == Long.TYPE) { return ((Class<T>) Long.class).cast(0); } else if (type == Short.TYPE) { return ((Class<T>) Short.class).cast(0); } return null; } else { final String so = object.toString(); // custom type conversions: if (type == BigInteger.class) { return type.cast(new BigInteger(so)); } else if (type == Boolean.class || type == Boolean.TYPE) { Boolean r; if ("1".equals(so) || "true".equalsIgnoreCase(so) || "yes".equalsIgnoreCase( so) || "on".equalsIgnoreCase(so) || "enabled".equalsIgnoreCase(so)) { r = Boolean.TRUE; } else if ("0".equals(object) || "false".equalsIgnoreCase(so) || "no".equalsIgnoreCase( so) || "off".equalsIgnoreCase(so) || "disabled".equalsIgnoreCase(so)) { r = Boolean.FALSE; } else { r = Boolean.valueOf(so); } if (type == Boolean.TYPE) { return ((Class<T>) Boolean.class).cast(r); //avoid ClassCastException through autoboxing } else { return type.cast(r); } } else if (type == Byte.class || type == Byte.TYPE) { Byte i = Byte.valueOf(so); if (type == Byte.TYPE) { return ((Class<T>) Byte.class).cast(i); //avoid ClassCastException through autoboxing } else { return type.cast(i); } } else if (type == Character.class || type == Character.TYPE) { Character i = so.charAt(0); if (type == Character.TYPE) { return ((Class<T>) Character.class).cast(i); //avoid ClassCastException through autoboxing } else { return type.cast(i); } } else if (type == Double.class || type == Double.TYPE) { Double i = Double.valueOf(so); if (type == Double.TYPE) { return ((Class<T>) Double.class).cast(i); //avoid ClassCastException through autoboxing } else { return type.cast(i); } } else if (type == Float.class || type == Float.TYPE) { Float i = Float.valueOf(so); if (type == Float.TYPE) { return ((Class<T>) Float.class).cast(i); //avoid ClassCastException through autoboxing } else { return type.cast(i); } } else if (type == Integer.class || type == Integer.TYPE) { Integer i = Integer.valueOf(so); if (type == Integer.TYPE) { return ((Class<T>) Integer.class).cast(i); //avoid ClassCastException through autoboxing } else { return type.cast(i); } } else if (type == Long.class || type == Long.TYPE) { Long i = Long.valueOf(so); if (type == Long.TYPE) { return ((Class<T>) Long.class).cast(i); //avoid ClassCastException through autoboxing } else { return type.cast(i); } } else if (type == Short.class || type == Short.TYPE) { Short i = Short.valueOf(so); if (type == Short.TYPE) { return ((Class<T>) Short.class).cast(i); //avoid ClassCastException through autoboxing } else { return type.cast(i); } } else if (Enum.class.isAssignableFrom(type)) { return (T) Enum.valueOf((Class<Enum>) type, so); } else if (Class.class.isAssignableFrom(type)) { try { return type.cast(Class.forName(so)); } catch (ClassNotFoundException e) { return type.cast(object); } } else { try { Method valueOf = type.getMethod("valueOf", object.getClass()); if (Modifier.isStatic(valueOf.getModifiers()) && type.isAssignableFrom(valueOf.getReturnType())) { return type.cast(valueOf.invoke(null, object)); } } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) { } try { return type.getConstructor(String.class).newInstance(object); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InstantiationException | InvocationTargetException ignored) { } return type.cast(object); } } } /** * Returns the value of an option. * * @param option the option * @return the value of the option, or null if the option is * not defined * @throws IllegalArgumentException if <code>required</code> is true * and the option is not defined. */ protected String getOption(Option option) { String s; s = args.getOpt(option.name()); if (s != null && (!s.isEmpty() || !option.required())) { return s; } if (option.required()) { throw new IllegalArgumentException(option.name() + " is a required argument"); } return option.defaultValue(); } /** * Injects options into an object. * <p> * Option parsing is based on <code>Option</code> annotation of * fields. These fields must not be class private. * <p> * Values are logger at the INFO level. */ public <T> T inject(T obj) { for (Class<?> c = obj.getClass(); c != null; c = c.getSuperclass()) { for (Field field : c.getDeclaredFields()) { Option option = field.getAnnotation(Option.class); try { if (option != null) { field.setAccessible(true); String s = getOption(option); Object value; // this filters empty strings with the result that they // become null if (s != null && !s.isEmpty()) { try { value = toType(s, field.getType()); field.set(obj, value); } catch (NumberFormatException e) { throw new IllegalArgumentException( "Invalid value for option " + option.name() + ": " + e.toString(), e); } catch (ClassCastException e) { throw new IllegalArgumentException( "Invalid value for option " + option.name() + ": Cannot convert '" + s + "' to " + field.getType() + '.', e); } } else { value = field.get(obj); } if (option.log()) { String description = option.description(); String unit = option.unit(); if (description.isEmpty()) { description = option.name(); } if (!unit.isEmpty()) { LOGGER.info("{} set to {} {}", description, value, unit); } else { LOGGER.info("{} set to {}", description, value); } } } } catch (SecurityException | IllegalAccessException e) { throw new RuntimeException("Bug detected while processing option " + option.name(), e); } } } return obj; } }