Home | History | Annotate | Download | only in jcommander
      1 /**
      2  * Copyright (C) 2010 the original author or authors.
      3  * See the notice.md file distributed with this work for additional
      4  * information regarding copyright ownership.
      5  *
      6  * Licensed under the Apache License, Version 2.0 (the "License");
      7  * you may not use this file except in compliance with the License.
      8  * You may obtain a copy of the License at
      9  *
     10  *     http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software
     13  * distributed under the License is distributed on an "AS IS" BASIS,
     14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  * See the License for the specific language governing permissions and
     16  * limitations under the License.
     17  */
     18 
     19 package com.beust.jcommander;
     20 
     21 import com.beust.jcommander.validators.NoValidator;
     22 import com.beust.jcommander.validators.NoValueValidator;
     23 
     24 import java.util.ArrayList;
     25 import java.util.Collection;
     26 import java.util.EnumSet;
     27 import java.util.HashSet;
     28 import java.util.LinkedHashSet;
     29 import java.util.List;
     30 import java.util.Locale;
     31 import java.util.Map;
     32 import java.util.ResourceBundle;
     33 import java.util.Set;
     34 import java.util.SortedSet;
     35 import java.util.TreeSet;
     36 
     37 public class ParameterDescription {
     38   private Object m_object;
     39 
     40   private WrappedParameter m_wrappedParameter;
     41   private Parameter m_parameterAnnotation;
     42   private DynamicParameter m_dynamicParameterAnnotation;
     43 
     44   /** The field/method */
     45   private Parameterized m_parameterized;
     46   /** Keep track of whether a value was added to flag an error */
     47   private boolean m_assigned = false;
     48   private ResourceBundle m_bundle;
     49   private String m_description;
     50   private JCommander m_jCommander;
     51   private Object m_default;
     52   /** Longest of the names(), used to present usage() alphabetically */
     53   private String m_longestName = "";
     54 
     55   public ParameterDescription(Object object, DynamicParameter annotation,
     56       Parameterized parameterized,
     57       ResourceBundle bundle, JCommander jc) {
     58     if (! Map.class.isAssignableFrom(parameterized.getType())) {
     59       throw new ParameterException("@DynamicParameter " + parameterized.getName()
     60           + " should be of type "
     61           + "Map but is " + parameterized.getType().getName());
     62     }
     63 
     64     m_dynamicParameterAnnotation = annotation;
     65     m_wrappedParameter = new WrappedParameter(m_dynamicParameterAnnotation);
     66     init(object, parameterized, bundle, jc);
     67   }
     68 
     69   public ParameterDescription(Object object, Parameter annotation, Parameterized parameterized,
     70       ResourceBundle bundle, JCommander jc) {
     71     m_parameterAnnotation = annotation;
     72     m_wrappedParameter = new WrappedParameter(m_parameterAnnotation);
     73     init(object, parameterized, bundle, jc);
     74   }
     75 
     76   /**
     77    * Find the resource bundle in the annotations.
     78    * @return
     79    */
     80   @SuppressWarnings("deprecation")
     81   private ResourceBundle findResourceBundle(Object o) {
     82     ResourceBundle result = null;
     83 
     84     Parameters p = o.getClass().getAnnotation(Parameters.class);
     85     if (p != null && ! isEmpty(p.resourceBundle())) {
     86       result = ResourceBundle.getBundle(p.resourceBundle(), Locale.getDefault());
     87     } else {
     88       com.beust.jcommander.ResourceBundle a = o.getClass().getAnnotation(
     89           com.beust.jcommander.ResourceBundle.class);
     90       if (a != null && ! isEmpty(a.value())) {
     91         result = ResourceBundle.getBundle(a.value(), Locale.getDefault());
     92       }
     93     }
     94 
     95     return result;
     96   }
     97 
     98   private boolean isEmpty(String s) {
     99     return s == null || "".equals(s);
    100   }
    101 
    102   private void initDescription(String description, String descriptionKey, String[] names) {
    103     m_description = description;
    104     if (! "".equals(descriptionKey)) {
    105       if (m_bundle != null) {
    106         m_description = m_bundle.getString(descriptionKey);
    107       } else {
    108 //        JCommander.getConsole().println("Warning: field " + object.getClass() + "." + field.getName()
    109 //            + " has a descriptionKey but no bundle was defined with @ResourceBundle, using " +
    110 //            "default description:'" + m_description + "'");
    111       }
    112     }
    113 
    114     for (String name : names) {
    115       if (name.length() > m_longestName.length()) m_longestName = name;
    116     }
    117   }
    118 
    119   @SuppressWarnings("unchecked")
    120   private void init(Object object, Parameterized parameterized, ResourceBundle bundle,
    121       JCommander jCommander) {
    122     m_object = object;
    123     m_parameterized = parameterized;
    124     m_bundle = bundle;
    125     if (m_bundle == null) {
    126       m_bundle = findResourceBundle(object);
    127     }
    128     m_jCommander = jCommander;
    129 
    130     if (m_parameterAnnotation != null) {
    131       String description;
    132       if (Enum.class.isAssignableFrom(parameterized.getType())
    133           && m_parameterAnnotation.description().isEmpty()) {
    134         description = "Options: " + EnumSet.allOf((Class<? extends Enum>) parameterized.getType());
    135       }else {
    136         description = m_parameterAnnotation.description();
    137       }
    138       initDescription(description, m_parameterAnnotation.descriptionKey(),
    139           m_parameterAnnotation.names());
    140     } else if (m_dynamicParameterAnnotation != null) {
    141       initDescription(m_dynamicParameterAnnotation.description(),
    142           m_dynamicParameterAnnotation.descriptionKey(),
    143           m_dynamicParameterAnnotation.names());
    144     } else {
    145       throw new AssertionError("Shound never happen");
    146     }
    147 
    148     try {
    149       m_default = parameterized.get(object);
    150     } catch (Exception e) {
    151     }
    152 
    153     //
    154     // Validate default values, if any and if applicable
    155     //
    156     if (m_default != null) {
    157       if (m_parameterAnnotation != null) {
    158         validateDefaultValues(m_parameterAnnotation.names());
    159       }
    160     }
    161   }
    162 
    163   private void validateDefaultValues(String[] names) {
    164     String name = names.length > 0 ? names[0] : "";
    165     validateValueParameter(name, m_default);
    166   }
    167 
    168   public String getLongestName() {
    169     return m_longestName;
    170   }
    171 
    172   public Object getDefault() {
    173    return m_default;
    174   }
    175 
    176   public String getDescription() {
    177     return m_description;
    178   }
    179 
    180   public Object getObject() {
    181     return m_object;
    182   }
    183 
    184   public String getNames() {
    185     StringBuilder sb = new StringBuilder();
    186     String[] names = m_wrappedParameter.names();
    187     for (int i = 0; i < names.length; i++) {
    188       if (i > 0) sb.append(", ");
    189       sb.append(names[i]);
    190     }
    191     return sb.toString();
    192   }
    193 
    194   public WrappedParameter getParameter() {
    195     return m_wrappedParameter;
    196   }
    197 
    198   public Parameterized getParameterized() {
    199     return m_parameterized;
    200   }
    201 
    202   private boolean isMultiOption() {
    203     Class<?> fieldType = m_parameterized.getType();
    204     return fieldType.equals(List.class) || fieldType.equals(Set.class)
    205         || m_parameterized.isDynamicParameter();
    206   }
    207 
    208   public void addValue(String value) {
    209     addValue(value, false /* not default */);
    210   }
    211 
    212   /**
    213    * @return true if this parameter received a value during the parsing phase.
    214    */
    215   public boolean isAssigned() {
    216     return m_assigned;
    217   }
    218 
    219 
    220   public void setAssigned(boolean b) {
    221     m_assigned = b;
    222   }
    223 
    224   /**
    225    * Add the specified value to the field. First, validate the value if a
    226    * validator was specified. Then look up any field converter, then any type
    227    * converter, and if we can't find any, throw an exception.
    228    */
    229   public void addValue(String value, boolean isDefault) {
    230     p("Adding " + (isDefault ? "default " : "") + "value:" + value
    231         + " to parameter:" + m_parameterized.getName());
    232     String name = m_wrappedParameter.names()[0];
    233     if (m_assigned && ! isMultiOption() && !m_jCommander.isParameterOverwritingAllowed() || isNonOverwritableForced()) {
    234       throw new ParameterException("Can only specify option " + name + " once.");
    235     }
    236 
    237     validateParameter(name, value);
    238 
    239     Class<?> type = m_parameterized.getType();
    240 
    241     Object convertedValue = m_jCommander.convertValue(this, value);
    242     validateValueParameter(name, convertedValue);
    243     boolean isCollection = Collection.class.isAssignableFrom(type);
    244 
    245     if (isCollection) {
    246       @SuppressWarnings("unchecked")
    247       Collection<Object> l = (Collection<Object>) m_parameterized.get(m_object);
    248       if (l == null || fieldIsSetForTheFirstTime(isDefault)) {
    249         l = newCollection(type);
    250         m_parameterized.set(m_object, l);
    251       }
    252       if (convertedValue instanceof Collection) {
    253         l.addAll((Collection) convertedValue);
    254       } else { // if (isMainParameter || m_parameterAnnotation.arity() > 1) {
    255         l.add(convertedValue);
    256 //        } else {
    257 //          l.
    258       }
    259     } else {
    260       m_wrappedParameter.addValue(m_parameterized, m_object, convertedValue);
    261     }
    262     if (! isDefault) m_assigned = true;
    263   }
    264 
    265   private void validateParameter(String name, String value) {
    266     Class<? extends IParameterValidator> validator = m_wrappedParameter.validateWith();
    267     if (validator != null) {
    268       validateParameter(this, validator, name, value);
    269     }
    270   }
    271 
    272   private void validateValueParameter(String name, Object value) {
    273     Class<? extends IValueValidator> validator = m_wrappedParameter.validateValueWith();
    274     if (validator != null) {
    275       validateValueParameter(validator, name, value);
    276     }
    277   }
    278 
    279   public static void validateValueParameter(Class<? extends IValueValidator> validator,
    280       String name, Object value) {
    281     try {
    282       if (validator != NoValueValidator.class) {
    283         p("Validating value parameter:" + name + " value:" + value + " validator:" + validator);
    284       }
    285       validator.newInstance().validate(name, value);
    286     } catch (InstantiationException e) {
    287       throw new ParameterException("Can't instantiate validator:" + e);
    288     } catch (IllegalAccessException e) {
    289       throw new ParameterException("Can't instantiate validator:" + e);
    290     }
    291   }
    292 
    293   public static void validateParameter(ParameterDescription pd,
    294       Class<? extends IParameterValidator> validator,
    295       String name, String value) {
    296     try {
    297       if (validator != NoValidator.class) {
    298         p("Validating parameter:" + name + " value:" + value + " validator:" + validator);
    299       }
    300       validator.newInstance().validate(name, value);
    301       if (IParameterValidator2.class.isAssignableFrom(validator)) {
    302         IParameterValidator2 instance = (IParameterValidator2) validator.newInstance();
    303         instance.validate(name, value, pd);
    304       }
    305     } catch (InstantiationException e) {
    306       throw new ParameterException("Can't instantiate validator:" + e);
    307     } catch (IllegalAccessException e) {
    308       throw new ParameterException("Can't instantiate validator:" + e);
    309     } catch(ParameterException ex) {
    310       throw ex;
    311     } catch(Exception ex) {
    312       throw new ParameterException(ex);
    313     }
    314   }
    315 
    316   /*
    317    * Creates a new collection for the field's type.
    318    *
    319    * Currently only List and Set are supported. Support for
    320    * Queues and Stacks could be useful.
    321    */
    322   @SuppressWarnings("unchecked")
    323   private Collection<Object> newCollection(Class<?> type) {
    324     if (SortedSet.class.isAssignableFrom(type)) return new TreeSet();
    325     else if (LinkedHashSet.class.isAssignableFrom(type)) return new LinkedHashSet();
    326     else if (Set.class.isAssignableFrom(type)) return new HashSet();
    327     else if (List.class.isAssignableFrom(type)) return new ArrayList();
    328     else {
    329       throw new ParameterException("Parameters of Collection type '" + type.getSimpleName()
    330                                   + "' are not supported. Please use List or Set instead.");
    331     }
    332   }
    333 
    334   /*
    335    * Tests if its the first time a non-default value is
    336    * being added to the field.
    337    */
    338   private boolean fieldIsSetForTheFirstTime(boolean isDefault) {
    339     return (!isDefault && !m_assigned);
    340   }
    341 
    342   private static void p(String string) {
    343     if (System.getProperty(JCommander.DEBUG_PROPERTY) != null) {
    344       JCommander.getConsole().println("[ParameterDescription] " + string);
    345     }
    346   }
    347 
    348   @Override
    349   public String toString() {
    350     return "[ParameterDescription " + m_parameterized.getName() + "]";
    351   }
    352 
    353   public boolean isDynamicParameter() {
    354     return m_dynamicParameterAnnotation != null;
    355   }
    356 
    357   public boolean isHelp() {
    358     return m_wrappedParameter.isHelp();
    359   }
    360 
    361   public boolean isNonOverwritableForced() {
    362     return m_wrappedParameter.isNonOverwritableForced();
    363   }
    364 }
    365