001package org.unix4j.unix.grep; 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.Grep; 017 018/** 019 * Arguments and options for the {@link Grep grep} command. 020 */ 021public final class GrepArguments implements Arguments<GrepArguments> { 022 023 private final GrepOptions options; 024 025 026 // operand: <regexp> 027 private String regexp; 028 private boolean regexpIsSet = false; 029 030 // operand: <pattern> 031 private java.util.regex.Pattern pattern; 032 private boolean patternIsSet = false; 033 034 // operand: <paths> 035 private String[] paths; 036 private boolean pathsIsSet = false; 037 038 // operand: <files> 039 private java.io.File[] files; 040 private boolean filesIsSet = false; 041 042 // operand: <inputs> 043 private org.unix4j.io.Input[] inputs; 044 private boolean inputsIsSet = false; 045 046 // operand: <args> 047 private String[] args; 048 private boolean argsIsSet = false; 049 050 /** 051 * Constructor to use if no options are specified. 052 */ 053 public GrepArguments() { 054 this.options = GrepOptions.EMPTY; 055 } 056 057 /** 058 * Constructor with option set containing the selected command options. 059 * 060 * @param options the selected options 061 * @throws NullPointerException if the argument is null 062 */ 063 public GrepArguments(GrepOptions options) { 064 if (options == null) { 065 throw new NullPointerException("options argument cannot be null"); 066 } 067 this.options = options; 068 } 069 070 /** 071 * Returns the options set containing the selected command options. Returns 072 * an empty options set if no option has been selected. 073 * 074 * @return set with the selected options 075 */ 076 public GrepOptions getOptions() { 077 return options; 078 } 079 080 /** 081 * Constructor string arguments encoding options and arguments, possibly 082 * also containing variable expressions. 083 * 084 * @param args string arguments for the command 085 * @throws NullPointerException if args is null 086 */ 087 public GrepArguments(String... args) { 088 this(); 089 this.args = args; 090 this.argsIsSet = true; 091 } 092 private Object[] resolveVariables(VariableContext context, String... unresolved) { 093 final Object[] resolved = new Object[unresolved.length]; 094 for (int i = 0; i < resolved.length; i++) { 095 final String expression = unresolved[i]; 096 if (Arg.isVariable(expression)) { 097 resolved[i] = resolveVariable(context, expression); 098 } else { 099 resolved[i] = expression; 100 } 101 } 102 return resolved; 103 } 104 private <V> V convertList(ExecutionContext context, String operandName, Class<V> operandType, List<Object> values) { 105 if (values.size() == 1) { 106 final Object value = values.get(0); 107 return convert(context, operandName, operandType, value); 108 } 109 return convert(context, operandName, operandType, values); 110 } 111 112 private Object resolveVariable(VariableContext context, String variable) { 113 final Object value = context.getValue(variable); 114 if (value != null) { 115 return value; 116 } 117 throw new IllegalArgumentException("cannot resolve variable " + variable + 118 " in command: grep " + this); 119 } 120 private <V> V convert(ExecutionContext context, String operandName, Class<V> operandType, Object value) { 121 final ValueConverter<V> converter = context.getValueConverterFor(operandType); 122 final V convertedValue; 123 if (converter != null) { 124 convertedValue = converter.convert(value); 125 } else { 126 if (GrepOptions.class.equals(operandType)) { 127 convertedValue = operandType.cast(GrepOptions.CONVERTER.convert(value)); 128 } else { 129 convertedValue = null; 130 } 131 } 132 if (convertedValue != null) { 133 return convertedValue; 134 } 135 throw new IllegalArgumentException("cannot convert --" + operandName + 136 " value '" + value + "' into the type " + operandType.getName() + 137 " for grep command"); 138 } 139 140 @Override 141 public GrepArguments getForContext(ExecutionContext context) { 142 if (context == null) { 143 throw new NullPointerException("context cannot be null"); 144 } 145 if (!argsIsSet || args.length == 0) { 146 //nothing to resolve 147 return this; 148 } 149 150 //check if there is at least one variable 151 boolean hasVariable = false; 152 for (final String arg : args) { 153 if (arg != null && arg.startsWith("$")) { 154 hasVariable = true; 155 break; 156 } 157 } 158 //resolve variables 159 final Object[] resolvedArgs = hasVariable ? resolveVariables(context.getVariableContext(), this.args) : this.args; 160 161 //convert now 162 final List<String> defaultOperands = Arrays.asList("regexp", "paths"); 163 final Map<String, List<Object>> map = ArgsUtil.parseArgs("options", defaultOperands, resolvedArgs); 164 final GrepOptions.Default options = new GrepOptions.Default(); 165 final GrepArguments argsForContext = new GrepArguments(options); 166 for (final Map.Entry<String, List<Object>> e : map.entrySet()) { 167 if ("regexp".equals(e.getKey())) { 168 169 final String value = convertList(context, "regexp", String.class, e.getValue()); 170 argsForContext.setRegexp(value); 171 } else if ("pattern".equals(e.getKey())) { 172 173 final java.util.regex.Pattern value = convertList(context, "pattern", java.util.regex.Pattern.class, e.getValue()); 174 argsForContext.setPattern(value); 175 } else if ("paths".equals(e.getKey())) { 176 177 final String[] value = convertList(context, "paths", String[].class, e.getValue()); 178 argsForContext.setPaths(value); 179 } else if ("files".equals(e.getKey())) { 180 181 final java.io.File[] value = convertList(context, "files", java.io.File[].class, e.getValue()); 182 argsForContext.setFiles(value); 183 } else if ("inputs".equals(e.getKey())) { 184 185 final org.unix4j.io.Input[] value = convertList(context, "inputs", org.unix4j.io.Input[].class, e.getValue()); 186 argsForContext.setInputs(value); 187 } else if ("args".equals(e.getKey())) { 188 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in grep command args: " + Arrays.toString(args)); 189 } else if ("options".equals(e.getKey())) { 190 191 final GrepOptions value = convertList(context, "options", GrepOptions.class, e.getValue()); 192 options.setAll(value); 193 } else { 194 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in grep command args: " + Arrays.toString(args)); 195 } 196 } 197 return argsForContext; 198 } 199 200 /** 201 * Returns the {@code <regexp>} operand value (variables are NOT resolved): Lines will be printed which match the given regular expression. The 202 {@code regexp} string is surrounded with ".*" on both sides unless 203 the {@code --wholeLine} option is specified. If the 204 {@code --fixedStrings} option is used, plain string comparison is 205 used instead of regular expression matching. 206 * 207 * @return the {@code <regexp>} operand value (variables are not resolved) 208 * @throws IllegalStateException if this operand has never been set 209 * @see #getRegexp(ExecutionContext) 210 */ 211 public String getRegexp() { 212 if (regexpIsSet) { 213 return regexp; 214 } 215 throw new IllegalStateException("operand has not been set: " + regexp); 216 } 217 /** 218 * Returns the {@code <regexp>} (variables are resolved): Lines will be printed which match the given regular expression. The 219 {@code regexp} string is surrounded with ".*" on both sides unless 220 the {@code --wholeLine} option is specified. If the 221 {@code --fixedStrings} option is used, plain string comparison is 222 used instead of regular expression matching. 223 * 224 * @param context the execution context used to resolve variables 225 * @return the {@code <regexp>} operand value after resolving variables 226 * @throws IllegalStateException if this operand has never been set 227 * @see #getRegexp() 228 */ 229 public String getRegexp(ExecutionContext context) { 230 final String value = getRegexp(); 231 if (Arg.isVariable(value)) { 232 final Object resolved = resolveVariable(context.getVariableContext(), value); 233 final String converted = convert(context, "regexp", String.class, resolved); 234 return converted; 235 } 236 return value; 237 } 238 239 /** 240 * Returns true if the {@code <regexp>} operand has been set. 241 * <p> 242 * Note that this method returns true even if {@code null} was passed to the 243 * {@link #setRegexp(String)} method. 244 * 245 * @return true if the setter for the {@code <regexp>} operand has 246 * been called at least once 247 */ 248 public boolean isRegexpSet() { 249 return regexpIsSet; 250 } 251 /** 252 * Sets {@code <regexp>}: Lines will be printed which match the given regular expression. The 253 {@code regexp} string is surrounded with ".*" on both sides unless 254 the {@code --wholeLine} option is specified. If the 255 {@code --fixedStrings} option is used, plain string comparison is 256 used instead of regular expression matching. 257 * 258 * @param regexp the value for the {@code <regexp>} operand 259 */ 260 public void setRegexp(String regexp) { 261 this.regexp = regexp; 262 this.regexpIsSet = true; 263 } 264 /** 265 * Returns the {@code <pattern>} operand value: Lines will be printed which match the given pattern. 266 * 267 * @return the {@code <pattern>} operand value (variables are not resolved) 268 * @throws IllegalStateException if this operand has never been set 269 * 270 */ 271 public java.util.regex.Pattern getPattern() { 272 if (patternIsSet) { 273 return pattern; 274 } 275 throw new IllegalStateException("operand has not been set: " + pattern); 276 } 277 278 /** 279 * Returns true if the {@code <pattern>} operand has been set. 280 * <p> 281 * Note that this method returns true even if {@code null} was passed to the 282 * {@link #setPattern(java.util.regex.Pattern)} method. 283 * 284 * @return true if the setter for the {@code <pattern>} operand has 285 * been called at least once 286 */ 287 public boolean isPatternSet() { 288 return patternIsSet; 289 } 290 /** 291 * Sets {@code <pattern>}: Lines will be printed which match the given pattern. 292 * 293 * @param pattern the value for the {@code <pattern>} operand 294 */ 295 public void setPattern(java.util.regex.Pattern pattern) { 296 this.pattern = pattern; 297 this.patternIsSet = true; 298 } 299 /** 300 * Returns the {@code <paths>} operand value: Path names of the input files to be searched for the pattern; 301 wildcards * and ? are supported; relative paths are resolved on the 302 basis of the current working directory. 303 * 304 * @return the {@code <paths>} operand value (variables are not resolved) 305 * @throws IllegalStateException if this operand has never been set 306 * 307 */ 308 public String[] getPaths() { 309 if (pathsIsSet) { 310 return paths; 311 } 312 throw new IllegalStateException("operand has not been set: " + paths); 313 } 314 315 /** 316 * Returns true if the {@code <paths>} operand has been set. 317 * <p> 318 * Note that this method returns true even if {@code null} was passed to the 319 * {@link #setPaths(String[])} method. 320 * 321 * @return true if the setter for the {@code <paths>} operand has 322 * been called at least once 323 */ 324 public boolean isPathsSet() { 325 return pathsIsSet; 326 } 327 /** 328 * Sets {@code <paths>}: Path names of the input files to be searched for the pattern; 329 wildcards * and ? are supported; relative paths are resolved on the 330 basis of the current working directory. 331 * 332 * @param paths the value for the {@code <paths>} operand 333 */ 334 public void setPaths(String... paths) { 335 this.paths = paths; 336 this.pathsIsSet = true; 337 } 338 /** 339 * Returns the {@code <files>} operand value: The input files to be searched for the pattern; relative paths are 340 not resolved (use the string paths argument to enable relative path 341 resolving based on the current working directory). 342 * 343 * @return the {@code <files>} operand value (variables are not resolved) 344 * @throws IllegalStateException if this operand has never been set 345 * 346 */ 347 public java.io.File[] getFiles() { 348 if (filesIsSet) { 349 return files; 350 } 351 throw new IllegalStateException("operand has not been set: " + files); 352 } 353 354 /** 355 * Returns true if the {@code <files>} operand has been set. 356 * <p> 357 * Note that this method returns true even if {@code null} was passed to the 358 * {@link #setFiles(java.io.File[])} method. 359 * 360 * @return true if the setter for the {@code <files>} operand has 361 * been called at least once 362 */ 363 public boolean isFilesSet() { 364 return filesIsSet; 365 } 366 /** 367 * Sets {@code <files>}: The input files to be searched for the pattern; relative paths are 368 not resolved (use the string paths argument to enable relative path 369 resolving based on the current working directory). 370 * 371 * @param files the value for the {@code <files>} operand 372 */ 373 public void setFiles(java.io.File... files) { 374 this.files = files; 375 this.filesIsSet = true; 376 } 377 /** 378 * Returns the {@code <inputs>} operand value: The inputs to be searched for the pattern. 379 * 380 * @return the {@code <inputs>} operand value (variables are not resolved) 381 * @throws IllegalStateException if this operand has never been set 382 * 383 */ 384 public org.unix4j.io.Input[] getInputs() { 385 if (inputsIsSet) { 386 return inputs; 387 } 388 throw new IllegalStateException("operand has not been set: " + inputs); 389 } 390 391 /** 392 * Returns true if the {@code <inputs>} operand has been set. 393 * <p> 394 * Note that this method returns true even if {@code null} was passed to the 395 * {@link #setInputs(org.unix4j.io.Input[])} method. 396 * 397 * @return true if the setter for the {@code <inputs>} operand has 398 * been called at least once 399 */ 400 public boolean isInputsSet() { 401 return inputsIsSet; 402 } 403 /** 404 * Sets {@code <inputs>}: The inputs to be searched for the pattern. 405 * 406 * @param inputs the value for the {@code <inputs>} operand 407 */ 408 public void setInputs(org.unix4j.io.Input... inputs) { 409 this.inputs = inputs; 410 this.inputsIsSet = true; 411 } 412 /** 413 * Returns the {@code <args>} operand value: String arguments defining the options and operands for the command. 414 Options can be specified by acronym (with a leading dash "-") or by 415 long name (with two leading dashes "--"). Operands other than the 416 default "--pattern" and "--paths" operands have to be prefixed with 417 the operand name (e.g. "--files" for subsequent file operand values). 418 * 419 * @return the {@code <args>} operand value (variables are not resolved) 420 * @throws IllegalStateException if this operand has never been set 421 * 422 */ 423 public String[] getArgs() { 424 if (argsIsSet) { 425 return args; 426 } 427 throw new IllegalStateException("operand has not been set: " + args); 428 } 429 430 /** 431 * Returns true if the {@code <args>} operand has been set. 432 * 433 * @return true if the setter for the {@code <args>} operand has 434 * been called at least once 435 */ 436 public boolean isArgsSet() { 437 return argsIsSet; 438 } 439 440 /** 441 * Returns true if the {@code --}{@link GrepOption#ignoreCase ignoreCase} option 442 * is set. The option is also known as {@code -}i option. 443 * <p> 444 * Description: Match lines ignoring the case when comparing the strings, also known 445 from Unix with its acronym 'i'. 446 * 447 * @return true if the {@code --ignoreCase} or {@code -i} option is set 448 */ 449 public boolean isIgnoreCase() { 450 return getOptions().isSet(GrepOption.ignoreCase); 451 } 452 /** 453 * Returns true if the {@code --}{@link GrepOption#invertMatch invertMatch} option 454 * is set. The option is also known as {@code -}v option. 455 * <p> 456 * Description: Invert the match result, that is, a non-matching line is written to 457 the output and a matching line is not. This option is also known 458 from Unix with its acronym 'v'. 459 * 460 * @return true if the {@code --invertMatch} or {@code -v} option is set 461 */ 462 public boolean isInvertMatch() { 463 return getOptions().isSet(GrepOption.invertMatch); 464 } 465 /** 466 * Returns true if the {@code --}{@link GrepOption#fixedStrings fixedStrings} option 467 * is set. The option is also known as {@code -}F option. 468 * <p> 469 * Description: Use fixed-strings matching instead of regular expressions. This is 470 usually faster than the standard regexp version. 471 <p> 472 (This option is ignored if a {@code pattern} operand is specified 473 instead of the {@code regexp} string). 474 * 475 * @return true if the {@code --fixedStrings} or {@code -F} option is set 476 */ 477 public boolean isFixedStrings() { 478 return getOptions().isSet(GrepOption.fixedStrings); 479 } 480 /** 481 * Returns true if the {@code --}{@link GrepOption#lineNumber lineNumber} option 482 * is set. The option is also known as {@code -}n option. 483 * <p> 484 * Description: Prefix each line of output with the line number within its input 485 file. 486 * 487 * @return true if the {@code --lineNumber} or {@code -n} option is set 488 */ 489 public boolean isLineNumber() { 490 return getOptions().isSet(GrepOption.lineNumber); 491 } 492 /** 493 * Returns true if the {@code --}{@link GrepOption#count count} option 494 * is set. The option is also known as {@code -}c option. 495 * <p> 496 * Description: Suppress normal output; instead print a count of matching lines for 497 each input file. With the {@code -v}, {@code --invertMatch} option, 498 count non-matching lines. 499 * 500 * @return true if the {@code --count} or {@code -c} option is set 501 */ 502 public boolean isCount() { 503 return getOptions().isSet(GrepOption.count); 504 } 505 /** 506 * Returns true if the {@code --}{@link GrepOption#matchingFiles matchingFiles} option 507 * is set. The option is also known as {@code -}l option. 508 * <p> 509 * Description: Suppress normal output; instead print the name of each input file 510 from which output would normally have been printed. The scanning 511 will stop on the first match. 512 * 513 * @return true if the {@code --matchingFiles} or {@code -l} option is set 514 */ 515 public boolean isMatchingFiles() { 516 return getOptions().isSet(GrepOption.matchingFiles); 517 } 518 /** 519 * Returns true if the {@code --}{@link GrepOption#wholeLine wholeLine} option 520 * is set. The option is also known as {@code -}x option. 521 * <p> 522 * Description: Select only those matches that exactly match the whole line 523 excluding the terminating line ending. 524 <p> 525 (This option is ignored if a {@code pattern} operand is specified 526 instead of the {@code regexp} string). 527 * 528 * @return true if the {@code --wholeLine} or {@code -x} option is set 529 */ 530 public boolean isWholeLine() { 531 return getOptions().isSet(GrepOption.wholeLine); 532 } 533 534 @Override 535 public String toString() { 536 // ok, we have options or arguments or both 537 final StringBuilder sb = new StringBuilder(); 538 539 if (argsIsSet) { 540 for (String arg : args) { 541 if (sb.length() > 0) sb.append(' '); 542 sb.append(arg); 543 } 544 } else { 545 546 // first the options 547 if (options.size() > 0) { 548 sb.append(DefaultOptionSet.toString(options)); 549 } 550 // operand: <regexp> 551 if (regexpIsSet) { 552 if (sb.length() > 0) sb.append(' '); 553 sb.append("--").append("regexp"); 554 sb.append(" ").append(toString(getRegexp())); 555 } 556 // operand: <pattern> 557 if (patternIsSet) { 558 if (sb.length() > 0) sb.append(' '); 559 sb.append("--").append("pattern"); 560 sb.append(" ").append(toString(getPattern())); 561 } 562 // operand: <paths> 563 if (pathsIsSet) { 564 if (sb.length() > 0) sb.append(' '); 565 sb.append("--").append("paths"); 566 sb.append(" ").append(toString(getPaths())); 567 } 568 // operand: <files> 569 if (filesIsSet) { 570 if (sb.length() > 0) sb.append(' '); 571 sb.append("--").append("files"); 572 sb.append(" ").append(toString(getFiles())); 573 } 574 // operand: <inputs> 575 if (inputsIsSet) { 576 if (sb.length() > 0) sb.append(' '); 577 sb.append("--").append("inputs"); 578 sb.append(" ").append(toString(getInputs())); 579 } 580 // operand: <args> 581 if (argsIsSet) { 582 if (sb.length() > 0) sb.append(' '); 583 sb.append("--").append("args"); 584 sb.append(" ").append(toString(getArgs())); 585 } 586 } 587 588 return sb.toString(); 589 } 590 private static String toString(Object value) { 591 if (value != null && value.getClass().isArray()) { 592 return ArrayUtil.toString(value); 593 } 594 return String.valueOf(value); 595 } 596}