All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.carrotsearch.console.jcommander.ParameterDescription Maven / Gradle / Ivy

/*
 * console-tools
 *
 * Copyright (C) 2019, Carrot Search s.c.
 * All rights reserved.
 */
package com.carrotsearch.console.jcommander;

import com.carrotsearch.console.jcommander.validators.NoValidator;
import com.carrotsearch.console.jcommander.validators.NoValueValidator;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

@SuppressWarnings("all")
public class ParameterDescription {
  private Object m_object;

  private WrappedParameter m_wrappedParameter;
  private Parameter m_parameterAnnotation;
  private DynamicParameter m_dynamicParameterAnnotation;

  /** The field/method */
  private Parameterized m_parameterized;
  /** Keep track of whether a value was added to flag an error */
  private boolean m_assigned = false;

  private ResourceBundle m_bundle;
  private String m_description;
  private JCommander m_jCommander;
  private Object m_default;
  /** Longest of the names(), used to present usage() alphabetically */
  private String m_longestName = "";

  public ParameterDescription(
      Object object,
      DynamicParameter annotation,
      Parameterized parameterized,
      ResourceBundle bundle,
      JCommander jc) {
    if (!Map.class.isAssignableFrom(parameterized.getType())) {
      throw new ParameterException(
          "@DynamicParameter "
              + parameterized.getName()
              + " should be of type "
              + "Map but is "
              + parameterized.getType().getName());
    }

    m_dynamicParameterAnnotation = annotation;
    m_wrappedParameter = new WrappedParameter(m_dynamicParameterAnnotation);
    init(object, parameterized, bundle, jc);
  }

  public ParameterDescription(
      Object object,
      Parameter annotation,
      Parameterized parameterized,
      ResourceBundle bundle,
      JCommander jc) {
    m_parameterAnnotation = annotation;
    m_wrappedParameter = new WrappedParameter(m_parameterAnnotation);
    init(object, parameterized, bundle, jc);
  }

  /**
   * Find the resource bundle in the annotations.
   *
   * @return
   */
  @SuppressWarnings("deprecation")
  private ResourceBundle findResourceBundle(Object o) {
    ResourceBundle result = null;

    Parameters p = o.getClass().getAnnotation(Parameters.class);
    if (p != null && !isEmpty(p.resourceBundle())) {
      result = ResourceBundle.getBundle(p.resourceBundle(), Locale.getDefault());
    }

    return result;
  }

  private boolean isEmpty(String s) {
    return s == null || "".equals(s);
  }

  private void initDescription(String description, String descriptionKey, String[] names) {
    m_description = description;
    if (!"".equals(descriptionKey)) {
      if (m_bundle != null) {
        m_description = m_bundle.getString(descriptionKey);
      } else {
        //        JCommander.getConsole().println("Warning: field " + object.getClass() + "." +
        // field.getName()
        //            + " has a descriptionKey but no bundle was defined with @ResourceBundle, using
        // " +
        //            "default description:'" + m_description + "'");
      }
    }

    for (String name : names) {
      if (name.length() > m_longestName.length()) m_longestName = name;
    }
  }

  @SuppressWarnings("unchecked")
  private void init(
      Object object, Parameterized parameterized, ResourceBundle bundle, JCommander jCommander) {
    m_object = object;
    m_parameterized = parameterized;
    m_bundle = bundle;
    if (m_bundle == null) {
      m_bundle = findResourceBundle(object);
    }
    m_jCommander = jCommander;

    if (m_parameterAnnotation != null) {
      String description;
      if (Enum.class.isAssignableFrom(parameterized.getType())
          && m_parameterAnnotation.description().isEmpty()) {
        description = "Options: " + EnumSet.allOf((Class) parameterized.getType());
      } else {
        description = m_parameterAnnotation.description();
      }
      initDescription(
          description, m_parameterAnnotation.descriptionKey(), m_parameterAnnotation.names());
    } else if (m_dynamicParameterAnnotation != null) {
      initDescription(
          m_dynamicParameterAnnotation.description(),
          m_dynamicParameterAnnotation.descriptionKey(),
          m_dynamicParameterAnnotation.names());
    } else {
      throw new AssertionError("Shound never happen");
    }

    try {
      m_default = parameterized.get(object);
    } catch (Exception e) {
    }

    //
    // Validate default values, if any and if applicable
    //
    if (m_default != null) {
      if (m_parameterAnnotation != null) {
        validateDefaultValues(m_parameterAnnotation.names());
      }
    }
  }

  private void validateDefaultValues(String[] names) {
    String name = names.length > 0 ? names[0] : "";
    validateValueParameter(name, m_default);
  }

  public String getLongestName() {
    return m_longestName;
  }

  public Object getDefault() {
    return m_default;
  }

  public String getDescription() {
    return m_description;
  }

  public Object getObject() {
    return m_object;
  }

  public String getNames() {
    StringBuilder sb = new StringBuilder();
    String[] names = m_wrappedParameter.names();
    for (int i = 0; i < names.length; i++) {
      if (i > 0) sb.append(", ");
      // L4G-499: don't indent sole long options
      // if (names.length == 1 && names[i].startsWith("--")) sb.append("    ");
      sb.append(names[i]);
    }
    return sb.toString();
  }

  public WrappedParameter getParameter() {
    return m_wrappedParameter;
  }

  public Parameterized getParameterized() {
    return m_parameterized;
  }

  private boolean isMultiOption() {
    Class fieldType = m_parameterized.getType();
    return fieldType.equals(List.class)
        || fieldType.equals(Set.class)
        || m_parameterized.isDynamicParameter();
  }

  public void addValue(String value) {
    addValue(value, false /* not default */);
  }

  /**
   * @return true if this parameter received a value during the parsing phase.
   */
  public boolean isAssigned() {
    return m_assigned;
  }

  public void setAssigned(boolean b) {
    m_assigned = b;
  }

  /**
   * Add the specified value to the field. First, validate the value if a validator was specified.
   * Then look up any field converter, then any type converter, and if we can't find any, throw an
   * exception.
   */
  public void addValue(String value, boolean isDefault) {
    p(
        "Adding "
            + (isDefault ? "default " : "")
            + "value:"
            + value
            + " to parameter:"
            + m_parameterized.getName());
    String name = m_wrappedParameter.names()[0];
    if (m_assigned && !isMultiOption() && !m_jCommander.isParameterOverwritingAllowed()
        || isNonOverwritableForced()) {
      throw new ParameterException("Can only specify option " + name + " once.");
    }

    validateParameter(name, value);

    Class type = m_parameterized.getType();

    Object convertedValue = m_jCommander.convertValue(this, value);
    validateValueParameter(name, convertedValue);
    boolean isCollection = Collection.class.isAssignableFrom(type);

    if (isCollection) {
      @SuppressWarnings("unchecked")
      Collection l = (Collection) m_parameterized.get(m_object);
      if (l == null || fieldIsSetForTheFirstTime(isDefault)) {
        l = newCollection(type);
        m_parameterized.set(m_object, l);
      }
      if (convertedValue instanceof Collection) {
        l.addAll((Collection) convertedValue);
      } else { // if (isMainParameter || m_parameterAnnotation.arity() > 1) {
        l.add(convertedValue);
        //        } else {
        //          l.
      }
    } else {
      m_wrappedParameter.addValue(m_parameterized, m_object, convertedValue);
    }
    if (!isDefault) m_assigned = true;
  }

  private void validateParameter(String name, String value) {
    Class validator = m_wrappedParameter.validateWith();
    if (validator != null) {
      validateParameter(this, validator, name, value);
    }
  }

  private void validateValueParameter(String name, Object value) {
    Class validator = m_wrappedParameter.validateValueWith();
    if (validator != null) {
      validateValueParameter(validator, name, value);
    }
  }

  public static void validateValueParameter(
      Class validator, String name, Object value) {
    try {
      if (validator != NoValueValidator.class) {
        p("Validating value parameter:" + name + " value:" + value + " validator:" + validator);
      }
      validator.getConstructor().newInstance().validate(name, value);
    } catch (IllegalAccessException | NoSuchMethodException | InstantiationException e) {
      throw new ParameterException("Can't instantiate validator:" + e);
    } catch (InvocationTargetException e) {
      throw new ParameterException("Can't instantiate validator:" + e.getTargetException());
    }
  }

  public static void validateParameter(
      ParameterDescription pd,
      Class validator,
      String name,
      String value) {
    try {
      if (validator != NoValidator.class) {
        p("Validating parameter:" + name + " value:" + value + " validator:" + validator);
      }
      validator.getConstructor().newInstance().validate(name, value);
      if (IParameterValidator2.class.isAssignableFrom(validator)) {
        IParameterValidator2 instance =
            (IParameterValidator2) validator.getConstructor().newInstance();
        instance.validate(name, value, pd);
      }
    } catch (IllegalAccessException | NoSuchMethodException | InstantiationException e) {
      throw new ParameterException("Can't instantiate validator:" + e);
    } catch (InvocationTargetException e) {
      throw new ParameterException("Can't instantiate validator:" + e.getTargetException());
    } catch (ParameterException ex) {
      throw ex;
    } catch (Exception ex) {
      throw new ParameterException(ex);
    }
  }

  /*
   * Creates a new collection for the field's type.
   *
   * Currently only List and Set are supported. Support for
   * Queues and Stacks could be useful.
   */
  @SuppressWarnings("unchecked")
  private Collection newCollection(Class type) {
    if (SortedSet.class.isAssignableFrom(type)) return new TreeSet();
    else if (LinkedHashSet.class.isAssignableFrom(type)) return new LinkedHashSet();
    else if (Set.class.isAssignableFrom(type)) return new HashSet();
    else if (List.class.isAssignableFrom(type)) return new ArrayList();
    else {
      throw new ParameterException(
          "Parameters of Collection type '"
              + type.getSimpleName()
              + "' are not supported. Please use List or Set instead.");
    }
  }

  /*
   * Tests if its the first time a non-default value is
   * being added to the field.
   */
  private boolean fieldIsSetForTheFirstTime(boolean isDefault) {
    return (!isDefault && !m_assigned);
  }

  private static void p(String string) {
    if (System.getProperty(JCommander.DEBUG_PROPERTY) != null) {
      JCommander.getConsole().println("[ParameterDescription] " + string);
    }
  }

  @Override
  public String toString() {
    return "[ParameterDescription " + m_parameterized.getName() + "]";
  }

  public boolean isDynamicParameter() {
    return m_dynamicParameterAnnotation != null;
  }

  public boolean isHelp() {
    return m_wrappedParameter.isHelp();
  }

  public boolean isNonOverwritableForced() {
    return m_wrappedParameter.isNonOverwritableForced();
  }
}