001package org.unix4j.unix.echo; 002 003import java.util.List; 004import java.util.Map; 005import java.util.Arrays; 006 007import org.unix4j.command.Arguments; 008import org.unix4j.context.ExecutionContext; 009import org.unix4j.convert.ValueConverter; 010import org.unix4j.option.DefaultOptionSet; 011import org.unix4j.util.ArgsUtil; 012import org.unix4j.util.ArrayUtil; 013import org.unix4j.variable.Arg; 014import org.unix4j.variable.VariableContext; 015 016import org.unix4j.unix.Echo; 017 018/** 019 * Arguments and options for the {@link Echo echo} command. 020 */ 021public final class EchoArguments implements Arguments<EchoArguments> { 022 023 private final EchoOptions options; 024 025 026 // operand: <string> 027 private String string; 028 private boolean stringIsSet = false; 029 030 // operand: <strings> 031 private String[] strings; 032 private boolean stringsIsSet = false; 033 034 // operand: <args> 035 private String[] args; 036 private boolean argsIsSet = false; 037 038 /** 039 * Constructor to use if no options are specified. 040 */ 041 public EchoArguments() { 042 this.options = EchoOptions.EMPTY; 043 } 044 045 /** 046 * Constructor with option set containing the selected command options. 047 * 048 * @param options the selected options 049 * @throws NullPointerException if the argument is null 050 */ 051 public EchoArguments(EchoOptions options) { 052 if (options == null) { 053 throw new NullPointerException("options argument cannot be null"); 054 } 055 this.options = options; 056 } 057 058 /** 059 * Returns the options set containing the selected command options. Returns 060 * an empty options set if no option has been selected. 061 * 062 * @return set with the selected options 063 */ 064 public EchoOptions getOptions() { 065 return options; 066 } 067 068 /** 069 * Constructor string arguments encoding options and arguments, possibly 070 * also containing variable expressions. 071 * 072 * @param args string arguments for the command 073 * @throws NullPointerException if args is null 074 */ 075 public EchoArguments(String... args) { 076 this(); 077 this.args = args; 078 this.argsIsSet = true; 079 } 080 private Object[] resolveVariables(VariableContext context, String... unresolved) { 081 final Object[] resolved = new Object[unresolved.length]; 082 for (int i = 0; i < resolved.length; i++) { 083 final String expression = unresolved[i]; 084 if (Arg.isVariable(expression)) { 085 resolved[i] = resolveVariable(context, expression); 086 } else { 087 resolved[i] = expression; 088 } 089 } 090 return resolved; 091 } 092 private <V> V convertList(ExecutionContext context, String operandName, Class<V> operandType, List<Object> values) { 093 if (values.size() == 1) { 094 final Object value = values.get(0); 095 return convert(context, operandName, operandType, value); 096 } 097 return convert(context, operandName, operandType, values); 098 } 099 100 private Object resolveVariable(VariableContext context, String variable) { 101 final Object value = context.getValue(variable); 102 if (value != null) { 103 return value; 104 } 105 throw new IllegalArgumentException("cannot resolve variable " + variable + 106 " in command: echo " + this); 107 } 108 private <V> V convert(ExecutionContext context, String operandName, Class<V> operandType, Object value) { 109 final ValueConverter<V> converter = context.getValueConverterFor(operandType); 110 final V convertedValue; 111 if (converter != null) { 112 convertedValue = converter.convert(value); 113 } else { 114 if (EchoOptions.class.equals(operandType)) { 115 convertedValue = operandType.cast(EchoOptions.CONVERTER.convert(value)); 116 } else { 117 convertedValue = null; 118 } 119 } 120 if (convertedValue != null) { 121 return convertedValue; 122 } 123 throw new IllegalArgumentException("cannot convert --" + operandName + 124 " value '" + value + "' into the type " + operandType.getName() + 125 " for echo command"); 126 } 127 128 @Override 129 public EchoArguments getForContext(ExecutionContext context) { 130 if (context == null) { 131 throw new NullPointerException("context cannot be null"); 132 } 133 if (!argsIsSet || args.length == 0) { 134 //nothing to resolve 135 return this; 136 } 137 138 //check if there is at least one variable 139 boolean hasVariable = false; 140 for (final String arg : args) { 141 if (arg != null && arg.startsWith("$")) { 142 hasVariable = true; 143 break; 144 } 145 } 146 //resolve variables 147 final Object[] resolvedArgs = hasVariable ? resolveVariables(context.getVariableContext(), this.args) : this.args; 148 149 //convert now 150 final List<String> defaultOperands = Arrays.asList("strings"); 151 final Map<String, List<Object>> map = ArgsUtil.parseArgs("options", defaultOperands, resolvedArgs); 152 final EchoOptions.Default options = new EchoOptions.Default(); 153 final EchoArguments argsForContext = new EchoArguments(options); 154 for (final Map.Entry<String, List<Object>> e : map.entrySet()) { 155 if ("string".equals(e.getKey())) { 156 157 final String value = convertList(context, "string", String.class, e.getValue()); 158 argsForContext.setString(value); 159 } else if ("strings".equals(e.getKey())) { 160 161 final String[] value = convertList(context, "strings", String[].class, e.getValue()); 162 argsForContext.setStrings(value); 163 } else if ("args".equals(e.getKey())) { 164 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in echo command args: " + Arrays.toString(args)); 165 } else if ("options".equals(e.getKey())) { 166 167 final EchoOptions value = convertList(context, "options", EchoOptions.class, e.getValue()); 168 options.setAll(value); 169 } else { 170 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in echo command args: " + Arrays.toString(args)); 171 } 172 } 173 return argsForContext; 174 } 175 176 /** 177 * Returns the {@code <string>} operand value (variables are NOT resolved): A string to be written to standard output. 178 * 179 * @return the {@code <string>} operand value (variables are not resolved) 180 * @throws IllegalStateException if this operand has never been set 181 * @see #getString(ExecutionContext) 182 */ 183 public String getString() { 184 if (stringIsSet) { 185 return string; 186 } 187 throw new IllegalStateException("operand has not been set: " + string); 188 } 189 /** 190 * Returns the {@code <string>} (variables are resolved): A string to be written to standard output. 191 * 192 * @param context the execution context used to resolve variables 193 * @return the {@code <string>} operand value after resolving variables 194 * @throws IllegalStateException if this operand has never been set 195 * @see #getString() 196 */ 197 public String getString(ExecutionContext context) { 198 final String value = getString(); 199 if (Arg.isVariable(value)) { 200 final Object resolved = resolveVariable(context.getVariableContext(), value); 201 final String converted = convert(context, "string", String.class, resolved); 202 return converted; 203 } 204 return value; 205 } 206 207 /** 208 * Returns true if the {@code <string>} operand has been set. 209 * <p> 210 * Note that this method returns true even if {@code null} was passed to the 211 * {@link #setString(String)} method. 212 * 213 * @return true if the setter for the {@code <string>} operand has 214 * been called at least once 215 */ 216 public boolean isStringSet() { 217 return stringIsSet; 218 } 219 /** 220 * Sets {@code <string>}: A string to be written to standard output. 221 * 222 * @param string the value for the {@code <string>} operand 223 */ 224 public void setString(String string) { 225 this.string = string; 226 this.stringIsSet = true; 227 } 228 /** 229 * Returns the {@code <strings>} operand value: Strings to be written to standard output, separated by single blank 230 characters. 231 * 232 * @return the {@code <strings>} operand value (variables are not resolved) 233 * @throws IllegalStateException if this operand has never been set 234 * 235 */ 236 public String[] getStrings() { 237 if (stringsIsSet) { 238 return strings; 239 } 240 throw new IllegalStateException("operand has not been set: " + strings); 241 } 242 243 /** 244 * Returns true if the {@code <strings>} operand has been set. 245 * <p> 246 * Note that this method returns true even if {@code null} was passed to the 247 * {@link #setStrings(String[])} method. 248 * 249 * @return true if the setter for the {@code <strings>} operand has 250 * been called at least once 251 */ 252 public boolean isStringsSet() { 253 return stringsIsSet; 254 } 255 /** 256 * Sets {@code <strings>}: Strings to be written to standard output, separated by single blank 257 characters. 258 * 259 * @param strings the value for the {@code <strings>} operand 260 */ 261 public void setStrings(String... strings) { 262 this.strings = strings; 263 this.stringsIsSet = true; 264 } 265 /** 266 * Returns the {@code <args>} operand value: String arguments defining the options for the command and the 267 strings to be written to the output. Options can be specified by 268 acronym (with a leading dash "-") or by long name (with two leading 269 dashes "--"). 270 * 271 * @return the {@code <args>} operand value (variables are not resolved) 272 * @throws IllegalStateException if this operand has never been set 273 * 274 */ 275 public String[] getArgs() { 276 if (argsIsSet) { 277 return args; 278 } 279 throw new IllegalStateException("operand has not been set: " + args); 280 } 281 282 /** 283 * Returns true if the {@code <args>} operand has been set. 284 * 285 * @return true if the setter for the {@code <args>} operand has 286 * been called at least once 287 */ 288 public boolean isArgsSet() { 289 return argsIsSet; 290 } 291 292 /** 293 * Returns true if the {@code --}{@link EchoOption#noNewline noNewline} option 294 * is set. The option is also known as {@code -}n option. 295 * <p> 296 * Description: Do not print the trailing newline character(s). 297 * 298 * @return true if the {@code --noNewline} or {@code -n} option is set 299 */ 300 public boolean isNoNewline() { 301 return getOptions().isSet(EchoOption.noNewline); 302 } 303 304 @Override 305 public String toString() { 306 // ok, we have options or arguments or both 307 final StringBuilder sb = new StringBuilder(); 308 309 if (argsIsSet) { 310 for (String arg : args) { 311 if (sb.length() > 0) sb.append(' '); 312 sb.append(arg); 313 } 314 } else { 315 316 // first the options 317 if (options.size() > 0) { 318 sb.append(DefaultOptionSet.toString(options)); 319 } 320 // operand: <string> 321 if (stringIsSet) { 322 if (sb.length() > 0) sb.append(' '); 323 sb.append("--").append("string"); 324 sb.append(" ").append(toString(getString())); 325 } 326 // operand: <strings> 327 if (stringsIsSet) { 328 if (sb.length() > 0) sb.append(' '); 329 sb.append("--").append("strings"); 330 sb.append(" ").append(toString(getStrings())); 331 } 332 // operand: <args> 333 if (argsIsSet) { 334 if (sb.length() > 0) sb.append(' '); 335 sb.append("--").append("args"); 336 sb.append(" ").append(toString(getArgs())); 337 } 338 } 339 340 return sb.toString(); 341 } 342 private static String toString(Object value) { 343 if (value != null && value.getClass().isArray()) { 344 return ArrayUtil.toString(value); 345 } 346 return String.valueOf(value); 347 } 348}