001package org.unix4j.unix.wc; 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.Wc; 017 018/** 019 * Arguments and options for the {@link Wc wc} command. 020 */ 021public final class WcArguments implements Arguments<WcArguments> { 022 023 private final WcOptions options; 024 025 026 // operand: <paths> 027 private String[] paths; 028 private boolean pathsIsSet = false; 029 030 // operand: <files> 031 private java.io.File[] files; 032 private boolean filesIsSet = 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 WcArguments() { 042 this.options = WcOptions.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 WcArguments(WcOptions 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 WcOptions 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 WcArguments(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: wc " + 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 (WcOptions.class.equals(operandType)) { 115 convertedValue = operandType.cast(WcOptions.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 wc command"); 126 } 127 128 @Override 129 public WcArguments 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("paths"); 151 final Map<String, List<Object>> map = ArgsUtil.parseArgs("options", defaultOperands, resolvedArgs); 152 final WcOptions.Default options = new WcOptions.Default(); 153 final WcArguments argsForContext = new WcArguments(options); 154 for (final Map.Entry<String, List<Object>> e : map.entrySet()) { 155 if ("paths".equals(e.getKey())) { 156 157 final String[] value = convertList(context, "paths", String[].class, e.getValue()); 158 argsForContext.setPaths(value); 159 } else if ("files".equals(e.getKey())) { 160 161 final java.io.File[] value = convertList(context, "files", java.io.File[].class, e.getValue()); 162 argsForContext.setFiles(value); 163 } else if ("args".equals(e.getKey())) { 164 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in wc command args: " + Arrays.toString(args)); 165 } else if ("options".equals(e.getKey())) { 166 167 final WcOptions value = convertList(context, "options", WcOptions.class, e.getValue()); 168 options.setAll(value); 169 } else { 170 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in wc command args: " + Arrays.toString(args)); 171 } 172 } 173 return argsForContext; 174 } 175 176 /** 177 * Returns the {@code <paths>} operand value: Pathnames of the input files; wildcards * and ? are supported; 178 relative paths are resolved on the basis of the current working 179 directory. 180 * 181 * @return the {@code <paths>} operand value (variables are not resolved) 182 * @throws IllegalStateException if this operand has never been set 183 * 184 */ 185 public String[] getPaths() { 186 if (pathsIsSet) { 187 return paths; 188 } 189 throw new IllegalStateException("operand has not been set: " + paths); 190 } 191 192 /** 193 * Returns true if the {@code <paths>} operand has been set. 194 * <p> 195 * Note that this method returns true even if {@code null} was passed to the 196 * {@link #setPaths(String[])} method. 197 * 198 * @return true if the setter for the {@code <paths>} operand has 199 * been called at least once 200 */ 201 public boolean isPathsSet() { 202 return pathsIsSet; 203 } 204 /** 205 * Sets {@code <paths>}: Pathnames of the input files; wildcards * and ? are supported; 206 relative paths are resolved on the basis of the current working 207 directory. 208 * 209 * @param paths the value for the {@code <paths>} operand 210 */ 211 public void setPaths(String[] paths) { 212 this.paths = paths; 213 this.pathsIsSet = true; 214 } 215 /** 216 * Returns the {@code <files>} operand value: The input files; relative paths are not resolved (use the string 217 paths argument to enable relative path resolving based on the 218 current working directory). 219 * 220 * @return the {@code <files>} operand value (variables are not resolved) 221 * @throws IllegalStateException if this operand has never been set 222 * 223 */ 224 public java.io.File[] getFiles() { 225 if (filesIsSet) { 226 return files; 227 } 228 throw new IllegalStateException("operand has not been set: " + files); 229 } 230 231 /** 232 * Returns true if the {@code <files>} operand has been set. 233 * <p> 234 * Note that this method returns true even if {@code null} was passed to the 235 * {@link #setFiles(java.io.File[])} method. 236 * 237 * @return true if the setter for the {@code <files>} operand has 238 * been called at least once 239 */ 240 public boolean isFilesSet() { 241 return filesIsSet; 242 } 243 /** 244 * Sets {@code <files>}: The input files; relative paths are not resolved (use the string 245 paths argument to enable relative path resolving based on the 246 current working directory). 247 * 248 * @param files the value for the {@code <files>} operand 249 */ 250 public void setFiles(java.io.File... files) { 251 this.files = files; 252 this.filesIsSet = true; 253 } 254 /** 255 * Returns the {@code <args>} operand value: String arguments defining the options and operands for the command. 256 Options can be specified by acronym (with a leading dash "-") or by 257 long name (with two leading dashes "--"). Operands other than the 258 default "--paths" operand have to be prefixed with the operand 259 name. 260 * 261 * @return the {@code <args>} operand value (variables are not resolved) 262 * @throws IllegalStateException if this operand has never been set 263 * 264 */ 265 public String[] getArgs() { 266 if (argsIsSet) { 267 return args; 268 } 269 throw new IllegalStateException("operand has not been set: " + args); 270 } 271 272 /** 273 * Returns true if the {@code <args>} operand has been set. 274 * 275 * @return true if the setter for the {@code <args>} operand has 276 * been called at least once 277 */ 278 public boolean isArgsSet() { 279 return argsIsSet; 280 } 281 282 /** 283 * Returns true if the {@code --}{@link WcOption#lines lines} option 284 * is set. The option is also known as {@code -}l option. 285 * <p> 286 * Description: Executes a count of lines and writes this count to the output. 287 * 288 * @return true if the {@code --lines} or {@code -l} option is set 289 */ 290 public boolean isLines() { 291 return getOptions().isSet(WcOption.lines); 292 } 293 /** 294 * Returns true if the {@code --}{@link WcOption#words words} option 295 * is set. The option is also known as {@code -}w option. 296 * <p> 297 * Description: Executes a count of words and writes this count to the output. A 298 word is a non-zero-length string of characters delimited by white 299 space as defined by {@link Character#isWhitespace(char)}. 300 * 301 * @return true if the {@code --words} or {@code -w} option is set 302 */ 303 public boolean isWords() { 304 return getOptions().isSet(WcOption.words); 305 } 306 /** 307 * Returns true if the {@code --}{@link WcOption#chars chars} option 308 * is set. The option is also known as {@code -}m option. 309 * <p> 310 * Description: Executes a count of chars and writes this count to the output. 311 * 312 * @return true if the {@code --chars} or {@code -m} option is set 313 */ 314 public boolean isChars() { 315 return getOptions().isSet(WcOption.chars); 316 } 317 318 @Override 319 public String toString() { 320 // ok, we have options or arguments or both 321 final StringBuilder sb = new StringBuilder(); 322 323 if (argsIsSet) { 324 for (String arg : args) { 325 if (sb.length() > 0) sb.append(' '); 326 sb.append(arg); 327 } 328 } else { 329 330 // first the options 331 if (options.size() > 0) { 332 sb.append(DefaultOptionSet.toString(options)); 333 } 334 // operand: <paths> 335 if (pathsIsSet) { 336 if (sb.length() > 0) sb.append(' '); 337 sb.append("--").append("paths"); 338 sb.append(" ").append(toString(getPaths())); 339 } 340 // operand: <files> 341 if (filesIsSet) { 342 if (sb.length() > 0) sb.append(' '); 343 sb.append("--").append("files"); 344 sb.append(" ").append(toString(getFiles())); 345 } 346 // operand: <args> 347 if (argsIsSet) { 348 if (sb.length() > 0) sb.append(' '); 349 sb.append("--").append("args"); 350 sb.append(" ").append(toString(getArgs())); 351 } 352 } 353 354 return sb.toString(); 355 } 356 private static String toString(Object value) { 357 if (value != null && value.getClass().isArray()) { 358 return ArrayUtil.toString(value); 359 } 360 return String.valueOf(value); 361 } 362}