001package org.unix4j.unix.cat; 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.Cat; 017 018/** 019 * Arguments and options for the {@link Cat cat} command. 020 */ 021public final class CatArguments implements Arguments<CatArguments> { 022 023 private final CatOptions options; 024 025 026 // operand: <files> 027 private java.io.File[] files; 028 private boolean filesIsSet = false; 029 030 // operand: <paths> 031 private String[] paths; 032 private boolean pathsIsSet = false; 033 034 // operand: <inputs> 035 private org.unix4j.io.Input[] inputs; 036 private boolean inputsIsSet = false; 037 038 // operand: <args> 039 private String[] args; 040 private boolean argsIsSet = false; 041 042 /** 043 * Constructor to use if no options are specified. 044 */ 045 public CatArguments() { 046 this.options = CatOptions.EMPTY; 047 } 048 049 /** 050 * Constructor with option set containing the selected command options. 051 * 052 * @param options the selected options 053 * @throws NullPointerException if the argument is null 054 */ 055 public CatArguments(CatOptions options) { 056 if (options == null) { 057 throw new NullPointerException("options argument cannot be null"); 058 } 059 this.options = options; 060 } 061 062 /** 063 * Returns the options set containing the selected command options. Returns 064 * an empty options set if no option has been selected. 065 * 066 * @return set with the selected options 067 */ 068 public CatOptions getOptions() { 069 return options; 070 } 071 072 /** 073 * Constructor string arguments encoding options and arguments, possibly 074 * also containing variable expressions. 075 * 076 * @param args string arguments for the command 077 * @throws NullPointerException if args is null 078 */ 079 public CatArguments(String... args) { 080 this(); 081 this.args = args; 082 this.argsIsSet = true; 083 } 084 private Object[] resolveVariables(VariableContext context, String... unresolved) { 085 final Object[] resolved = new Object[unresolved.length]; 086 for (int i = 0; i < resolved.length; i++) { 087 final String expression = unresolved[i]; 088 if (Arg.isVariable(expression)) { 089 resolved[i] = resolveVariable(context, expression); 090 } else { 091 resolved[i] = expression; 092 } 093 } 094 return resolved; 095 } 096 private <V> V convertList(ExecutionContext context, String operandName, Class<V> operandType, List<Object> values) { 097 if (values.size() == 1) { 098 final Object value = values.get(0); 099 return convert(context, operandName, operandType, value); 100 } 101 return convert(context, operandName, operandType, values); 102 } 103 104 private Object resolveVariable(VariableContext context, String variable) { 105 final Object value = context.getValue(variable); 106 if (value != null) { 107 return value; 108 } 109 throw new IllegalArgumentException("cannot resolve variable " + variable + 110 " in command: cat " + this); 111 } 112 private <V> V convert(ExecutionContext context, String operandName, Class<V> operandType, Object value) { 113 final ValueConverter<V> converter = context.getValueConverterFor(operandType); 114 final V convertedValue; 115 if (converter != null) { 116 convertedValue = converter.convert(value); 117 } else { 118 if (CatOptions.class.equals(operandType)) { 119 convertedValue = operandType.cast(CatOptions.CONVERTER.convert(value)); 120 } else { 121 convertedValue = null; 122 } 123 } 124 if (convertedValue != null) { 125 return convertedValue; 126 } 127 throw new IllegalArgumentException("cannot convert --" + operandName + 128 " value '" + value + "' into the type " + operandType.getName() + 129 " for cat command"); 130 } 131 132 @Override 133 public CatArguments getForContext(ExecutionContext context) { 134 if (context == null) { 135 throw new NullPointerException("context cannot be null"); 136 } 137 if (!argsIsSet || args.length == 0) { 138 //nothing to resolve 139 return this; 140 } 141 142 //check if there is at least one variable 143 boolean hasVariable = false; 144 for (final String arg : args) { 145 if (arg != null && arg.startsWith("$")) { 146 hasVariable = true; 147 break; 148 } 149 } 150 //resolve variables 151 final Object[] resolvedArgs = hasVariable ? resolveVariables(context.getVariableContext(), this.args) : this.args; 152 153 //convert now 154 final List<String> defaultOperands = Arrays.asList("paths"); 155 final Map<String, List<Object>> map = ArgsUtil.parseArgs("options", defaultOperands, resolvedArgs); 156 final CatOptions.Default options = new CatOptions.Default(); 157 final CatArguments argsForContext = new CatArguments(options); 158 for (final Map.Entry<String, List<Object>> e : map.entrySet()) { 159 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 ("paths".equals(e.getKey())) { 164 165 final String[] value = convertList(context, "paths", String[].class, e.getValue()); 166 argsForContext.setPaths(value); 167 } else if ("inputs".equals(e.getKey())) { 168 169 final org.unix4j.io.Input[] value = convertList(context, "inputs", org.unix4j.io.Input[].class, e.getValue()); 170 argsForContext.setInputs(value); 171 } else if ("args".equals(e.getKey())) { 172 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in cat command args: " + Arrays.toString(args)); 173 } else if ("options".equals(e.getKey())) { 174 175 final CatOptions value = convertList(context, "options", CatOptions.class, e.getValue()); 176 options.setAll(value); 177 } else { 178 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in cat command args: " + Arrays.toString(args)); 179 } 180 } 181 return argsForContext; 182 } 183 184 /** 185 * Returns the {@code <files>} operand value: The input files to be printed; relative paths are not resolved (use 186 the string path argument to enable relative path resolving based on 187 the current working directory). 188 * 189 * @return the {@code <files>} operand value (variables are not resolved) 190 * @throws IllegalStateException if this operand has never been set 191 * 192 */ 193 public java.io.File[] getFiles() { 194 if (filesIsSet) { 195 return files; 196 } 197 throw new IllegalStateException("operand has not been set: " + files); 198 } 199 200 /** 201 * Returns true if the {@code <files>} operand has been set. 202 * <p> 203 * Note that this method returns true even if {@code null} was passed to the 204 * {@link #setFiles(java.io.File[])} method. 205 * 206 * @return true if the setter for the {@code <files>} operand has 207 * been called at least once 208 */ 209 public boolean isFilesSet() { 210 return filesIsSet; 211 } 212 /** 213 * Sets {@code <files>}: The input files to be printed; relative paths are not resolved (use 214 the string path argument to enable relative path resolving based on 215 the current working directory). 216 * 217 * @param files the value for the {@code <files>} operand 218 */ 219 public void setFiles(java.io.File... files) { 220 this.files = files; 221 this.filesIsSet = true; 222 } 223 /** 224 * Returns the {@code <paths>} operand value: Path names of the input files to be printed; wildcards * and ? are 225 supported; relative paths are resolved on the basis of the current 226 working directory. 227 * 228 * @return the {@code <paths>} operand value (variables are not resolved) 229 * @throws IllegalStateException if this operand has never been set 230 * 231 */ 232 public String[] getPaths() { 233 if (pathsIsSet) { 234 return paths; 235 } 236 throw new IllegalStateException("operand has not been set: " + paths); 237 } 238 239 /** 240 * Returns true if the {@code <paths>} operand has been set. 241 * <p> 242 * Note that this method returns true even if {@code null} was passed to the 243 * {@link #setPaths(String[])} method. 244 * 245 * @return true if the setter for the {@code <paths>} operand has 246 * been called at least once 247 */ 248 public boolean isPathsSet() { 249 return pathsIsSet; 250 } 251 /** 252 * Sets {@code <paths>}: Path names of the input files to be printed; wildcards * and ? are 253 supported; relative paths are resolved on the basis of the current 254 working directory. 255 * 256 * @param paths the value for the {@code <paths>} operand 257 */ 258 public void setPaths(String... paths) { 259 this.paths = paths; 260 this.pathsIsSet = true; 261 } 262 /** 263 * Returns the {@code <inputs>} operand value: The inputs to be printed. 264 * 265 * @return the {@code <inputs>} operand value (variables are not resolved) 266 * @throws IllegalStateException if this operand has never been set 267 * 268 */ 269 public org.unix4j.io.Input[] getInputs() { 270 if (inputsIsSet) { 271 return inputs; 272 } 273 throw new IllegalStateException("operand has not been set: " + inputs); 274 } 275 276 /** 277 * Returns true if the {@code <inputs>} operand has been set. 278 * <p> 279 * Note that this method returns true even if {@code null} was passed to the 280 * {@link #setInputs(org.unix4j.io.Input[])} method. 281 * 282 * @return true if the setter for the {@code <inputs>} operand has 283 * been called at least once 284 */ 285 public boolean isInputsSet() { 286 return inputsIsSet; 287 } 288 /** 289 * Sets {@code <inputs>}: The inputs to be printed. 290 * 291 * @param inputs the value for the {@code <inputs>} operand 292 */ 293 public void setInputs(org.unix4j.io.Input... inputs) { 294 this.inputs = inputs; 295 this.inputsIsSet = true; 296 } 297 /** 298 * Returns the {@code <args>} operand value: String arguments defining the options and file operands for the 299 command. Options can be specified by acronym (with a leading dash 300 "-") or by long name (with two leading dashes "--"). File arguments 301 are expanded if wildcards are used. 302 * 303 * @return the {@code <args>} operand value (variables are not resolved) 304 * @throws IllegalStateException if this operand has never been set 305 * 306 */ 307 public String[] getArgs() { 308 if (argsIsSet) { 309 return args; 310 } 311 throw new IllegalStateException("operand has not been set: " + args); 312 } 313 314 /** 315 * Returns true if the {@code <args>} operand has been set. 316 * 317 * @return true if the setter for the {@code <args>} operand has 318 * been called at least once 319 */ 320 public boolean isArgsSet() { 321 return argsIsSet; 322 } 323 324 /** 325 * Returns true if the {@code --}{@link CatOption#numberNonBlankLines numberNonBlankLines} option 326 * is set. The option is also known as {@code -}b option. 327 * <p> 328 * Description: Number the non-blank output lines, starting at 1. 329 * 330 * @return true if the {@code --numberNonBlankLines} or {@code -b} option is set 331 */ 332 public boolean isNumberNonBlankLines() { 333 return getOptions().isSet(CatOption.numberNonBlankLines); 334 } 335 /** 336 * Returns true if the {@code --}{@link CatOption#numberLines numberLines} option 337 * is set. The option is also known as {@code -}n option. 338 * <p> 339 * Description: Number the output lines, starting at 1. 340 * 341 * @return true if the {@code --numberLines} or {@code -n} option is set 342 */ 343 public boolean isNumberLines() { 344 return getOptions().isSet(CatOption.numberLines); 345 } 346 /** 347 * Returns true if the {@code --}{@link CatOption#squeezeEmptyLines squeezeEmptyLines} option 348 * is set. The option is also known as {@code -}s option. 349 * <p> 350 * Description: Squeeze multiple adjacent empty lines, causing the output to be 351 single spaced. 352 * 353 * @return true if the {@code --squeezeEmptyLines} or {@code -s} option is set 354 */ 355 public boolean isSqueezeEmptyLines() { 356 return getOptions().isSet(CatOption.squeezeEmptyLines); 357 } 358 359 @Override 360 public String toString() { 361 // ok, we have options or arguments or both 362 final StringBuilder sb = new StringBuilder(); 363 364 if (argsIsSet) { 365 for (String arg : args) { 366 if (sb.length() > 0) sb.append(' '); 367 sb.append(arg); 368 } 369 } else { 370 371 // first the options 372 if (options.size() > 0) { 373 sb.append(DefaultOptionSet.toString(options)); 374 } 375 // operand: <files> 376 if (filesIsSet) { 377 if (sb.length() > 0) sb.append(' '); 378 sb.append("--").append("files"); 379 sb.append(" ").append(toString(getFiles())); 380 } 381 // operand: <paths> 382 if (pathsIsSet) { 383 if (sb.length() > 0) sb.append(' '); 384 sb.append("--").append("paths"); 385 sb.append(" ").append(toString(getPaths())); 386 } 387 // operand: <inputs> 388 if (inputsIsSet) { 389 if (sb.length() > 0) sb.append(' '); 390 sb.append("--").append("inputs"); 391 sb.append(" ").append(toString(getInputs())); 392 } 393 // operand: <args> 394 if (argsIsSet) { 395 if (sb.length() > 0) sb.append(' '); 396 sb.append("--").append("args"); 397 sb.append(" ").append(toString(getArgs())); 398 } 399 } 400 401 return sb.toString(); 402 } 403 private static String toString(Object value) { 404 if (value != null && value.getClass().isArray()) { 405 return ArrayUtil.toString(value); 406 } 407 return String.valueOf(value); 408 } 409}