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.JavaConfigurationBuilder;
025import org.apache.reef.tang.Tang;
026import org.apache.reef.tang.annotations.Name;
027import org.apache.reef.tang.exceptions.BindException;
028import org.apache.reef.tang.exceptions.NameResolutionException;
029import org.apache.reef.tang.types.NamedParameterNode;
030import org.apache.reef.tang.types.Node;
031import org.apache.reef.tang.util.MonotonicTreeMap;
032import org.apache.reef.tang.util.ReflectionUtilities;
033
034import java.io.IOException;
035import java.util.HashMap;
036import java.util.Map;
037
038public final class CommandLine {
039
040  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 String shortName : shortNames.keySet()) {
091      final String longName = shortNames.get(shortName);
092      try {
093        opts.addOption(OptionBuilder
094            .withArgName(conf.classPrettyDefaultString(longName)).hasArg()
095            .withDescription(conf.classPrettyDescriptionString(longName))
096            .create(shortName));
097      } catch (final BindException e) {
098        throw new IllegalStateException(
099            "Could not process " + shortName + " which is the short name of " + longName, e);
100      }
101    }
102
103    for (final Option o : applicationOptions.keySet()) {
104      opts.addOption(o);
105    }
106
107    return opts;
108  }
109
110  public CommandLine addCommandLineOption(final Option option, final CommandLineCallback cb) {
111    // TODO: Check for conflicting options.
112    applicationOptions.put(option, cb);
113    return this;
114  }
115
116  /**
117   * @param args
118   * @return Selfie if the command line parsing succeeded, null (or exception) otherwise.
119   * @throws IOException
120   * @throws NumberFormatException
121   * @throws ParseException
122   */
123  @SafeVarargs
124  final public <T> CommandLine processCommandLine(
125      final String[] args, Class<? extends Name<?>>... argClasses)
126      throws IOException, BindException {
127
128    for (final Class<? extends Name<?>> c : argClasses) {
129      registerShortNameOfClass(c);
130    }
131
132    final Options o = getCommandLineOptions();
133    o.addOption(new Option("?", "help"));
134    final Parser g = new GnuParser();
135
136    final org.apache.commons.cli.CommandLine cl;
137    try {
138      cl = g.parse(o, args);
139    } catch (final ParseException e) {
140      throw new IOException("Could not parse config file", e);
141    }
142
143    if (cl.hasOption("?")) {
144      new HelpFormatter().printHelp("reef", o);
145      return null;
146    }
147
148    for (final Option option : cl.getOptions()) {
149
150      final String shortName = option.getOpt();
151      final String value = option.getValue();
152
153      if (applicationOptions.containsKey(option)) {
154        applicationOptions.get(option).process(option);
155      } else {
156        try {
157          conf.bind(shortNames.get(shortName), value);
158        } catch (final BindException e) {
159          throw new BindException("Could not bind shortName " + shortName + " to value " + value, e);
160        }
161      }
162    }
163
164    return this;
165  }
166
167  /**
168   * Utility method to quickly parse a command line to a Configuration.
169   * <p/>
170   * This is equivalent to
171   * <code>parseToConfigurationBuilder(args, argClasses).build()</code>
172   *
173   * @param args       the command line parameters to parse.
174   * @param argClasses the named parameters to look for.
175   * @return a Configuration with the parsed parameters
176   * @throws ParseException if the parsing  of the commandline fails.
177   */
178  public static Configuration parseToConfiguration(final String[] args,
179                                                   final Class<? extends Name<?>>... argClasses)
180      throws ParseException {
181    return parseToConfigurationBuilder(args, argClasses).build();
182  }
183
184  /**
185   * Utility method to quickly parse a command line to a ConfigurationBuilder.
186   * <p/>
187   * This is equivalent to
188   * <code>new CommandLine().processCommandLine(args, argClasses).getBuilder()</code>, but with additional checks.
189   *
190   * @param args       the command line parameters to parse.
191   * @param argClasses the named parameters to look for.
192   * @return a ConfigurationBuilder with the parsed parameters
193   * @throws ParseException if the parsing  of the commandline fails.
194   */
195  public static ConfigurationBuilder parseToConfigurationBuilder(final String[] args,
196                                                                 final Class<? extends Name<?>>... argClasses)
197      throws ParseException {
198    final CommandLine commandLine;
199    try {
200      commandLine = new CommandLine().processCommandLine(args, argClasses);
201    } catch (final IOException e) {
202      // processCommandLine() converts ParseException into IOException. This reverts that to make exception handling
203      // more straight forward.
204      throw new ParseException(e.getMessage());
205    }
206
207    // processCommandLine() indicates that it might return null. We need to guard users of this one from that
208    if (commandLine == null) {
209      throw new ParseException("Unable to parse the command line and the parser returned null.");
210    } else {
211      return commandLine.getBuilder();
212    }
213  }
214
215  public interface CommandLineCallback {
216    public void process(final Option option);
217  }
218}