This project has retired. For details please refer to its Attic page.
Source code
001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.reef.tang.formats;
020
021import org.apache.commons.cli.*;
022import org.apache.reef.tang.Configuration;
023import org.apache.reef.tang.ConfigurationBuilder;
024import org.apache.reef.tang.Tang;
025import org.apache.reef.tang.annotations.Name;
026import org.apache.reef.tang.exceptions.BindException;
027import org.apache.reef.tang.exceptions.NameResolutionException;
028import org.apache.reef.tang.types.NamedParameterNode;
029import org.apache.reef.tang.types.Node;
030import org.apache.reef.tang.util.MonotonicTreeMap;
031import org.apache.reef.tang.util.ReflectionUtilities;
032
033import java.io.IOException;
034import java.util.HashMap;
035import java.util.Map;
036import java.util.Map.Entry;
037
038public final class CommandLine {
039
040  private final Map<Option, CommandLineCallback> applicationOptions = new HashMap<>();
041  private final ConfigurationBuilder conf;
042  private final Map<String, String> shortNames = new MonotonicTreeMap<>();
043
044  public CommandLine() {
045    this.conf = Tang.Factory.getTang().newConfigurationBuilder();
046  }
047
048  public CommandLine(final ConfigurationBuilder conf) {
049    this.conf = conf;
050  }
051
052  public ConfigurationBuilder getBuilder() {
053    return this.conf;
054  }
055
056  public CommandLine registerShortNameOfClass(final String s) throws BindException {
057
058    final Node n;
059    try {
060      n = conf.getClassHierarchy().getNode(s);
061    } catch (final NameResolutionException e) {
062      throw new BindException("Problem loading class " + s, e);
063    }
064
065    if (n instanceof NamedParameterNode) {
066      final NamedParameterNode<?> np = (NamedParameterNode<?>) n;
067      final String shortName = np.getShortName();
068      final String longName = np.getFullName();
069      if (shortName == null) {
070        throw new BindException(
071            "Can't register non-existent short name of named parameter: " + longName);
072      }
073      shortNames.put(shortName, longName);
074    } else {
075      throw new BindException("Can't register short name for non-NamedParameterNode: " + n);
076    }
077
078    return this;
079  }
080
081  public CommandLine registerShortNameOfClass(
082      final Class<? extends Name<?>> c) throws BindException {
083    return registerShortNameOfClass(ReflectionUtilities.getFullName(c));
084  }
085
086  @SuppressWarnings("static-access")
087  private Options getCommandLineOptions() {
088
089    final Options opts = new Options();
090    for (final Entry<String, String> entry : shortNames.entrySet()) {
091      final String shortName = entry.getKey();
092      final String longName = entry.getValue();
093      try {
094        opts.addOption(OptionBuilder
095            .withArgName(conf.classPrettyDefaultString(longName)).hasArg()
096            .withDescription(conf.classPrettyDescriptionString(longName))
097            .create(shortName));
098      } catch (final BindException e) {
099        throw new IllegalStateException(
100            "Could not process " + shortName + " which is the short name of " + longName, e);
101      }
102    }
103
104    for (final Option o : applicationOptions.keySet()) {
105      opts.addOption(o);
106    }
107
108    return opts;
109  }
110
111  public CommandLine addCommandLineOption(final Option option, final CommandLineCallback cb) {
112    // TODO: Check for conflicting options.
113    applicationOptions.put(option, cb);
114    return this;
115  }
116
117  /**
118   * Process command line arguments.
119   *
120   * @param <T> a type
121   * @param args the command line arguments to be parsed
122   * @param argClasses the target named classes to be set
123   * @return Selfie if the command line parsing succeeded, null (or exception) otherwise.
124   * @throws IOException if parsing fails
125   * @throws BindException if a binding of short-named parameter fails
126   */
127  @SafeVarargs
128  @SuppressWarnings("checkstyle:redundantmodifier")
129  public final <T> CommandLine processCommandLine(
130      final String[] args, final Class<? extends Name<?>>... argClasses)
131      throws IOException, BindException {
132
133    for (final Class<? extends Name<?>> c : argClasses) {
134      registerShortNameOfClass(c);
135    }
136
137    final Options o = getCommandLineOptions();
138    o.addOption(new Option("?", "help"));
139    final Parser g = new GnuParser();
140
141    final org.apache.commons.cli.CommandLine cl;
142    try {
143      cl = g.parse(o, args);
144    } catch (final ParseException e) {
145      throw new IOException("Could not parse config file", e);
146    }
147
148    if (cl.hasOption("?")) {
149      new HelpFormatter().printHelp("reef", o);
150      return null;
151    }
152
153    for (final Option option : cl.getOptions()) {
154
155      final String shortName = option.getOpt();
156      final String value = option.getValue();
157
158      if (applicationOptions.containsKey(option)) {
159        applicationOptions.get(option).process(option);
160      } else {
161        try {
162          conf.bind(shortNames.get(shortName), value);
163        } catch (final BindException e) {
164          throw new BindException("Could not bind shortName " + shortName + " to value " + value, e);
165        }
166      }
167    }
168
169    return this;
170  }
171
172  /**
173   * Utility method to quickly parse a command line to a Configuration.
174   * <p>
175   * This is equivalent to
176   * <code>parseToConfigurationBuilder(args, argClasses).build()</code>
177   *
178   * @param args       the command line parameters to parse.
179   * @param argClasses the named parameters to look for.
180   * @return a Configuration with the parsed parameters
181   * @throws ParseException if the parsing  of the commandline fails.
182   */
183  public static Configuration parseToConfiguration(final String[] args,
184                                                   final Class<? extends Name<?>>... argClasses)
185      throws ParseException {
186    return parseToConfigurationBuilder(args, argClasses).build();
187  }
188
189  /**
190   * Utility method to quickly parse a command line to a ConfigurationBuilder.
191   * <p>
192   * This is equivalent to
193   * <code>new CommandLine().processCommandLine(args, argClasses).getBuilder()</code>, but with additional checks.
194   *
195   * @param args       the command line parameters to parse.
196   * @param argClasses the named parameters to look for.
197   * @return a ConfigurationBuilder with the parsed parameters
198   * @throws ParseException if the parsing  of the commandline fails.
199   */
200
201  // ParseException constructor does not accept a cause Exception, hence
202  @SuppressWarnings("checkstyle:avoidhidingcauseexception")
203  public static ConfigurationBuilder parseToConfigurationBuilder(final String[] args,
204                                                                 final Class<? extends Name<?>>... argClasses)
205      throws ParseException {
206    final CommandLine commandLine;
207    try {
208      commandLine = new CommandLine().processCommandLine(args, argClasses);
209    } catch (final IOException e) {
210      // processCommandLine() converts ParseException into IOException. This reverts that to make exception handling
211      // more straight forward.
212      throw new ParseException(e.getMessage());
213    }
214
215    // processCommandLine() indicates that it might return null. We need to guard users of this one from that
216    if (commandLine == null) {
217      throw new ParseException("Unable to parse the command line and the parser returned null.");
218    } else {
219      return commandLine.getBuilder();
220    }
221  }
222
223  public interface CommandLineCallback {
224    void process(final Option option);
225  }
226}