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  public final <T> CommandLine processCommandLine(
129      final String[] args, final Class<? extends Name<?>>... argClasses)
130      throws IOException, BindException {
131
132    for (final Class<? extends Name<?>> c : argClasses) {
133      registerShortNameOfClass(c);
134    }
135
136    final Options o = getCommandLineOptions();
137    o.addOption(new Option("?", "help"));
138    final Parser g = new GnuParser();
139
140    final org.apache.commons.cli.CommandLine cl;
141    try {
142      cl = g.parse(o, args);
143    } catch (final ParseException e) {
144      throw new IOException("Could not parse config file", e);
145    }
146
147    if (cl.hasOption("?")) {
148      new HelpFormatter().printHelp("reef", o);
149      return null;
150    }
151
152    for (final Option option : cl.getOptions()) {
153
154      final String shortName = option.getOpt();
155      final String value = option.getValue();
156
157      if (applicationOptions.containsKey(option)) {
158        applicationOptions.get(option).process(option);
159      } else {
160        try {
161          conf.bind(shortNames.get(shortName), value);
162        } catch (final BindException e) {
163          throw new BindException("Could not bind shortName " + shortName + " to value " + value, e);
164        }
165      }
166    }
167
168    return this;
169  }
170
171  /**
172   * Utility method to quickly parse a command line to a Configuration.
173   * <p>
174   * This is equivalent to
175   * <code>parseToConfigurationBuilder(args, argClasses).build()</code>
176   *
177   * @param args       the command line parameters to parse.
178   * @param argClasses the named parameters to look for.
179   * @return a Configuration with the parsed parameters
180   * @throws ParseException if the parsing  of the commandline fails.
181   */
182  public static Configuration parseToConfiguration(final String[] args,
183                                                   final Class<? extends Name<?>>... argClasses)
184      throws ParseException {
185    return parseToConfigurationBuilder(args, argClasses).build();
186  }
187
188  /**
189   * Utility method to quickly parse a command line to a ConfigurationBuilder.
190   * <p>
191   * This is equivalent to
192   * <code>new CommandLine().processCommandLine(args, argClasses).getBuilder()</code>, but with additional checks.
193   *
194   * @param args       the command line parameters to parse.
195   * @param argClasses the named parameters to look for.
196   * @return a ConfigurationBuilder with the parsed parameters
197   * @throws ParseException if the parsing  of the commandline fails.
198   */
199
200  // ParseException constructor does not accept a cause Exception, hence
201  @SuppressWarnings("checkstyle:avoidhidingcauseexception")
202  public static ConfigurationBuilder parseToConfigurationBuilder(final String[] args,
203                                                                 final Class<? extends Name<?>>... argClasses)
204      throws ParseException {
205    final CommandLine commandLine;
206    try {
207      commandLine = new CommandLine().processCommandLine(args, argClasses);
208    } catch (final IOException e) {
209      // processCommandLine() converts ParseException into IOException. This reverts that to make exception handling
210      // more straight forward.
211      throw new ParseException(e.getMessage());
212    }
213
214    // processCommandLine() indicates that it might return null. We need to guard users of this one from that
215    if (commandLine == null) {
216      throw new ParseException("Unable to parse the command line and the parser returned null.");
217    } else {
218      return commandLine.getBuilder();
219    }
220  }
221
222  public interface CommandLineCallback {
223    void process(final Option option);
224  }
225}