View Javadoc

1   /*
2    * Copyright 2006 Tim Wood
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  
15  package com.lexicalscope.jewel.cli;
16  
17  import java.util.ArrayList;
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.LinkedHashSet;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  import java.util.TreeMap;
25  import java.util.TreeSet;
26  
27  import com.lexicalscope.fluent.FluentDollar;
28  import com.lexicalscope.fluent.list.FluentList;
29  import com.lexicalscope.fluentreflection.FluentClass;
30  import com.lexicalscope.fluentreflection.FluentMethod;
31  import com.lexicalscope.jewel.cli.specification.CliSpecification;
32  import com.lexicalscope.jewel.cli.specification.OptionsSpecification;
33  import com.lexicalscope.jewel.cli.specification.ParsedOptionSpecification;
34  import com.lexicalscope.jewel.cli.specification.UnparsedOptionSpecification;
35  
36  class OptionsSpecificationImpl<O> implements OptionsSpecification<O>, CliSpecification {
37      private final FluentClass<O> klass;
38  
39      private final Set<ParsedOptionSpecification> options;
40      private final Map<String, ParsedOptionSpecification> optionsByName =
41              new TreeMap<String, ParsedOptionSpecification>();
42      private final Map<FluentMethod, ParsedOptionSpecification> optionsMethod =
43              new HashMap<FluentMethod, ParsedOptionSpecification>();
44      private final Map<FluentMethod, ParsedOptionSpecification> optionalOptionsMethod =
45              new HashMap<FluentMethod, ParsedOptionSpecification>();
46  
47      private final Map<FluentMethod, UnparsedOptionSpecification> unparsedOptionsMethod =
48              new HashMap<FluentMethod, UnparsedOptionSpecification>();
49      private final Map<FluentMethod, UnparsedOptionSpecification> unparsedOptionalOptionsMethod =
50              new HashMap<FluentMethod, UnparsedOptionSpecification>();
51  
52      OptionsSpecificationImpl(
53              final FluentClass<O> klass,
54              final List<ParsedOptionSpecification> optionSpecifications,
55              final List<UnparsedOptionSpecification> unparsedSpecifications) {
56          this.klass = klass;
57  
58          if(klass.annotatedWith(CommandLineInterface.class) &&
59             klass.annotation(CommandLineInterface.class).order().equals(OptionOrder.DEFINITION))
60          {
61              options = new LinkedHashSet<ParsedOptionSpecification>();
62          }
63          else
64          {
65              options = new TreeSet<ParsedOptionSpecification>();
66          }
67  
68          for (final ParsedOptionSpecification optionSpecification : optionSpecifications) {
69              addOption(optionSpecification);
70          }
71  
72          for (final UnparsedOptionSpecification optionSpecification : unparsedSpecifications) {
73              addUnparsedOption(optionSpecification);
74          }
75      }
76  
77      @Override public boolean isSpecified(final String key) {
78          return optionsByName.containsKey(key);
79      }
80  
81      @Override public ParsedOptionSpecification getSpecification(final String key) {
82          return optionsByName.get(key);
83      }
84  
85      @Override public ParsedOptionSpecification getSpecification(final FluentMethod method) {
86          if (optionsMethod.containsKey(method)) {
87              return optionsMethod.get(method);
88          }
89          return optionalOptionsMethod.get(method);
90      }
91  
92      @Override public FluentList<ParsedOptionSpecification> getMandatoryOptions() {
93          final FluentList<ParsedOptionSpecification> result = FluentDollar.$.list(ParsedOptionSpecification.class);
94          for (final ParsedOptionSpecification specification : options) {
95              if (!specification.isOptional() && !specification.hasDefaultValue()) {
96                  result.add(specification);
97              }
98          }
99  
100         return result;
101     }
102 
103     @Override public Iterator<ParsedOptionSpecification> iterator() {
104         return new ArrayList<ParsedOptionSpecification>(optionsByName.values()).iterator();
105     }
106 
107     @Override public UnparsedOptionSpecification getUnparsedSpecification() {
108         return unparsedOptionsMethod.values().iterator().next();
109     }
110 
111     @Override public boolean hasUnparsedSpecification() {
112         return !unparsedOptionsMethod.values().isEmpty();
113     }
114 
115     @Override public String getApplicationName() {
116         final String applicationName = applicationName();
117         if (applicationName != null)
118         {
119             return applicationName;
120         }
121         return klass.name();
122     }
123 
124     private String applicationName() {
125         if (klass.annotatedWith(CommandLineInterface.class))
126         {
127             final String applicationName = klass.annotation(CommandLineInterface.class).application();
128             if (applicationName != null && !applicationName.trim().equals("")) {
129                 return applicationName.trim();
130             }
131         }
132         return null;
133     }
134 
135     private void addOption(final ParsedOptionSpecification optionSpecification) {
136         for (final String name : optionSpecification.getNames()) {
137             optionsByName.put(name, optionSpecification);
138         }
139         options.add(optionSpecification);
140 
141         optionsMethod.put(optionSpecification.getMethod(), optionSpecification);
142 
143         if (optionSpecification.isOptional()) {
144             optionalOptionsMethod.put(optionSpecification.getOptionalityMethod(), optionSpecification);
145         }
146     }
147 
148     private void addUnparsedOption(final UnparsedOptionSpecification optionSpecification) {
149         unparsedOptionsMethod.put(optionSpecification.getMethod(), optionSpecification);
150 
151         if (optionSpecification.isOptional()) {
152             unparsedOptionalOptionsMethod.put(optionSpecification.getOptionalityMethod(), optionSpecification);
153         }
154     }
155 
156     @Override public void describeTo(final HelpMessage helpMessage) {
157         if (!hasCustomApplicationName() && (!hasUnparsedSpecification() || getUnparsedSpecification().isHidden())) {
158             helpMessage.noUsageInformation();
159         } else {
160             if (hasCustomApplicationName()) {
161                 helpMessage.hasUsageInformation(applicationName());
162             }
163             else
164             {
165                 helpMessage.hasUsageInformation();
166             }
167 
168             if (getMandatoryOptions().isEmpty()) {
169                 helpMessage.hasOnlyOptionalOptions();
170             }
171             else
172             {
173                 helpMessage.hasSomeMandatoryOptions();
174             }
175 
176             if (hasUnparsedSpecification() && !getUnparsedSpecification().isHidden()) {
177                 if (getUnparsedSpecification().isMultiValued()) {
178                     helpMessage.hasUnparsedMultiValuedOption(getUnparsedSpecification().getValueName());
179                 }
180                 else
181                 {
182                     helpMessage.hasUnparsedOption(getUnparsedSpecification().getValueName());
183                 }
184             }
185         }
186 
187         helpMessage.startOfOptions();
188 
189         for (final ParsedOptionSpecification specification : options) {
190             if(!specification.isHidden())
191             {
192                 new ParsedOptionSummary(specification).describeOptionTo(helpMessage.option());
193             }
194         }
195 
196         helpMessage.endOfOptions();
197     }
198 
199     private boolean hasCustomApplicationName() {
200         return !nullOrBlank(applicationName());
201     }
202 
203     static boolean nullOrBlank(final String string) {
204         return string == null || string.trim().equals("");
205     }
206 
207     @Override public String toString() {
208         final HelpMessageBuilderImpl helpMessage = new HelpMessageBuilderImpl();
209         describeTo(helpMessage);
210         return helpMessage.toString();
211     }
212 }