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