001package org.unix4j.unix.uniq; 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.Uniq; 017 018/** 019 * Arguments and options for the {@link Uniq uniq} command. 020 */ 021public final class UniqArguments implements Arguments<UniqArguments> { 022 023 private final UniqOptions options; 024 025 026 // operand: <file> 027 private java.io.File file; 028 private boolean fileIsSet = false; 029 030 // operand: <path> 031 private String path; 032 private boolean pathIsSet = 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 UniqArguments() { 042 this.options = UniqOptions.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 UniqArguments(UniqOptions 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 UniqOptions 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 UniqArguments(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: uniq " + 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 (UniqOptions.class.equals(operandType)) { 115 convertedValue = operandType.cast(UniqOptions.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 uniq command"); 126 } 127 128 @Override 129 public UniqArguments 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("path"); 151 final Map<String, List<Object>> map = ArgsUtil.parseArgs("options", defaultOperands, resolvedArgs); 152 final UniqOptions.Default options = new UniqOptions.Default(); 153 final UniqArguments argsForContext = new UniqArguments(options); 154 for (final Map.Entry<String, List<Object>> e : map.entrySet()) { 155 if ("file".equals(e.getKey())) { 156 157 final java.io.File value = convertList(context, "file", java.io.File.class, e.getValue()); 158 argsForContext.setFile(value); 159 } else if ("path".equals(e.getKey())) { 160 161 final String value = convertList(context, "path", String.class, e.getValue()); 162 argsForContext.setPath(value); 163 } else if ("args".equals(e.getKey())) { 164 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in uniq command args: " + Arrays.toString(args)); 165 } else if ("options".equals(e.getKey())) { 166 167 final UniqOptions value = convertList(context, "options", UniqOptions.class, e.getValue()); 168 options.setAll(value); 169 } else { 170 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in uniq command args: " + Arrays.toString(args)); 171 } 172 } 173 return argsForContext; 174 } 175 176 /** 177 * Returns the {@code <file>} operand value: The files or directories used as starting point for the listing; 178 relative paths are not resolved (use the string path argument to 179 enable relative path resolving based on the current working 180 directory). 181 * 182 * @return the {@code <file>} operand value (variables are not resolved) 183 * @throws IllegalStateException if this operand has never been set 184 * 185 */ 186 public java.io.File getFile() { 187 if (fileIsSet) { 188 return file; 189 } 190 throw new IllegalStateException("operand has not been set: " + file); 191 } 192 193 /** 194 * Returns true if the {@code <file>} operand has been set. 195 * <p> 196 * Note that this method returns true even if {@code null} was passed to the 197 * {@link #setFile(java.io.File)} method. 198 * 199 * @return true if the setter for the {@code <file>} operand has 200 * been called at least once 201 */ 202 public boolean isFileSet() { 203 return fileIsSet; 204 } 205 /** 206 * Sets {@code <file>}: The files or directories used as starting point for the listing; 207 relative paths are not resolved (use the string path argument to 208 enable relative path resolving based on the current working 209 directory). 210 * 211 * @param file the value for the {@code <file>} operand 212 */ 213 public void setFile(java.io.File file) { 214 this.file = file; 215 this.fileIsSet = true; 216 } 217 /** 218 * Returns the {@code <path>} operand value (variables are NOT resolved): The files or directories used as starting point for the listing; 219 wildcards * and ? are supported; relative paths are resolved on the 220 basis of the current working directory. 221 * 222 * @return the {@code <path>} operand value (variables are not resolved) 223 * @throws IllegalStateException if this operand has never been set 224 * @see #getPath(ExecutionContext) 225 */ 226 public String getPath() { 227 if (pathIsSet) { 228 return path; 229 } 230 throw new IllegalStateException("operand has not been set: " + path); 231 } 232 /** 233 * Returns the {@code <path>} (variables are resolved): The files or directories used as starting point for the listing; 234 wildcards * and ? are supported; relative paths are resolved on the 235 basis of the current working directory. 236 * 237 * @param context the execution context used to resolve variables 238 * @return the {@code <path>} operand value after resolving variables 239 * @throws IllegalStateException if this operand has never been set 240 * @see #getPath() 241 */ 242 public String getPath(ExecutionContext context) { 243 final String value = getPath(); 244 if (Arg.isVariable(value)) { 245 final Object resolved = resolveVariable(context.getVariableContext(), value); 246 final String converted = convert(context, "path", String.class, resolved); 247 return converted; 248 } 249 return value; 250 } 251 252 /** 253 * Returns true if the {@code <path>} operand has been set. 254 * <p> 255 * Note that this method returns true even if {@code null} was passed to the 256 * {@link #setPath(String)} method. 257 * 258 * @return true if the setter for the {@code <path>} operand has 259 * been called at least once 260 */ 261 public boolean isPathSet() { 262 return pathIsSet; 263 } 264 /** 265 * Sets {@code <path>}: The files or directories used as starting point for the listing; 266 wildcards * and ? are supported; relative paths are resolved on the 267 basis of the current working directory. 268 * 269 * @param path the value for the {@code <path>} operand 270 */ 271 public void setPath(String path) { 272 this.path = path; 273 this.pathIsSet = true; 274 } 275 /** 276 * Returns the {@code <args>} operand value: String arguments defining the options and operands for the command. 277 Options can be specified by acronym (with a leading dash "-") or by 278 long name (with two leading dashes "--"). Operands other than the 279 default "--path" operand have to be prefixed with the operand 280 name. 281 * 282 * @return the {@code <args>} operand value (variables are not resolved) 283 * @throws IllegalStateException if this operand has never been set 284 * 285 */ 286 public String[] getArgs() { 287 if (argsIsSet) { 288 return args; 289 } 290 throw new IllegalStateException("operand has not been set: " + args); 291 } 292 293 /** 294 * Returns true if the {@code <args>} operand has been set. 295 * 296 * @return true if the setter for the {@code <args>} operand has 297 * been called at least once 298 */ 299 public boolean isArgsSet() { 300 return argsIsSet; 301 } 302 303 /** 304 * Returns true if the {@code --}{@link UniqOption#count count} option 305 * is set. The option is also known as {@code -}c option. 306 * <p> 307 * Description: Precedes each output line with a count of the number of times the 308 line occurred in the input. 309 * 310 * @return true if the {@code --count} or {@code -c} option is set 311 */ 312 public boolean isCount() { 313 return getOptions().isSet(UniqOption.count); 314 } 315 /** 316 * Returns true if the {@code --}{@link UniqOption#duplicatedOnly duplicatedOnly} option 317 * is set. The option is also known as {@code -}d option. 318 * <p> 319 * Description: Suppresses the writing of lines that are not repeated in the input. 320 * 321 * @return true if the {@code --duplicatedOnly} or {@code -d} option is set 322 */ 323 public boolean isDuplicatedOnly() { 324 return getOptions().isSet(UniqOption.duplicatedOnly); 325 } 326 /** 327 * Returns true if the {@code --}{@link UniqOption#uniqueOnly uniqueOnly} option 328 * is set. The option is also known as {@code -}u option. 329 * <p> 330 * Description: Suppresses the writing of lines that are repeated in the input. 331 * 332 * @return true if the {@code --uniqueOnly} or {@code -u} option is set 333 */ 334 public boolean isUniqueOnly() { 335 return getOptions().isSet(UniqOption.uniqueOnly); 336 } 337 /** 338 * Returns true if the {@code --}{@link UniqOption#global global} option 339 * is set. The option is also known as {@code -}g option. 340 * <p> 341 * Description: Suppresses repeated lines globally, that is, if lines are 342 non-adjacent. This option guarantees unique output lines even if the 343 input lines are not sorted. 344 * 345 * @return true if the {@code --global} or {@code -g} option is set 346 */ 347 public boolean isGlobal() { 348 return getOptions().isSet(UniqOption.global); 349 } 350 351 @Override 352 public String toString() { 353 // ok, we have options or arguments or both 354 final StringBuilder sb = new StringBuilder(); 355 356 if (argsIsSet) { 357 for (String arg : args) { 358 if (sb.length() > 0) sb.append(' '); 359 sb.append(arg); 360 } 361 } else { 362 363 // first the options 364 if (options.size() > 0) { 365 sb.append(DefaultOptionSet.toString(options)); 366 } 367 // operand: <file> 368 if (fileIsSet) { 369 if (sb.length() > 0) sb.append(' '); 370 sb.append("--").append("file"); 371 sb.append(" ").append(toString(getFile())); 372 } 373 // operand: <path> 374 if (pathIsSet) { 375 if (sb.length() > 0) sb.append(' '); 376 sb.append("--").append("path"); 377 sb.append(" ").append(toString(getPath())); 378 } 379 // operand: <args> 380 if (argsIsSet) { 381 if (sb.length() > 0) sb.append(' '); 382 sb.append("--").append("args"); 383 sb.append(" ").append(toString(getArgs())); 384 } 385 } 386 387 return sb.toString(); 388 } 389 private static String toString(Object value) { 390 if (value != null && value.getClass().isArray()) { 391 return ArrayUtil.toString(value); 392 } 393 return String.valueOf(value); 394 } 395}