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