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}