001package org.unix4j.unix.ls; 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.Ls; 017 018/** 019 * Arguments and options for the {@link Ls ls} command. 020 */ 021public final class LsArguments implements Arguments<LsArguments> { 022 023 private final LsOptions 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: <args> 035 private String[] args; 036 private boolean argsIsSet = false; 037 038 /** 039 * Constructor to use if no options are specified. 040 */ 041 public LsArguments() { 042 this.options = LsOptions.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 LsArguments(LsOptions 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 LsOptions 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 LsArguments(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: ls " + 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 (LsOptions.class.equals(operandType)) { 115 convertedValue = operandType.cast(LsOptions.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 ls command"); 126 } 127 128 @Override 129 public LsArguments 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 LsOptions.Default options = new LsOptions.Default(); 153 final LsArguments argsForContext = new LsArguments(options); 154 for (final Map.Entry<String, List<Object>> e : map.entrySet()) { 155 if ("files".equals(e.getKey())) { 156 157 final java.io.File[] value = convertList(context, "files", java.io.File[].class, e.getValue()); 158 argsForContext.setFiles(value); 159 } else if ("paths".equals(e.getKey())) { 160 161 final String[] value = convertList(context, "paths", String[].class, e.getValue()); 162 argsForContext.setPaths(value); 163 } else if ("args".equals(e.getKey())) { 164 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in ls command args: " + Arrays.toString(args)); 165 } else if ("options".equals(e.getKey())) { 166 167 final LsOptions value = convertList(context, "options", LsOptions.class, e.getValue()); 168 options.setAll(value); 169 } else { 170 throw new IllegalStateException("invalid operand '" + e.getKey() + "' in ls command args: " + Arrays.toString(args)); 171 } 172 } 173 return argsForContext; 174 } 175 176 /** 177 * Returns the {@code <files>} 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 <files>} operand value (variables are not resolved) 183 * @throws IllegalStateException if this operand has never been set 184 * 185 */ 186 public java.io.File[] getFiles() { 187 if (filesIsSet) { 188 return files; 189 } 190 throw new IllegalStateException("operand has not been set: " + files); 191 } 192 193 /** 194 * Returns true if the {@code <files>} operand has been set. 195 * <p> 196 * Note that this method returns true even if {@code null} was passed to the 197 * {@link #setFiles(java.io.File[])} method. 198 * 199 * @return true if the setter for the {@code <files>} operand has 200 * been called at least once 201 */ 202 public boolean isFilesSet() { 203 return filesIsSet; 204 } 205 /** 206 * Sets {@code <files>}: 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 files the value for the {@code <files>} operand 212 */ 213 public void setFiles(java.io.File... files) { 214 this.files = files; 215 this.filesIsSet = true; 216 } 217 /** 218 * Returns the {@code <paths>} operand value: 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 <paths>} operand value (variables are not resolved) 223 * @throws IllegalStateException if this operand has never been set 224 * 225 */ 226 public String[] getPaths() { 227 if (pathsIsSet) { 228 return paths; 229 } 230 throw new IllegalStateException("operand has not been set: " + paths); 231 } 232 233 /** 234 * Returns true if the {@code <paths>} operand has been set. 235 * <p> 236 * Note that this method returns true even if {@code null} was passed to the 237 * {@link #setPaths(String[])} method. 238 * 239 * @return true if the setter for the {@code <paths>} operand has 240 * been called at least once 241 */ 242 public boolean isPathsSet() { 243 return pathsIsSet; 244 } 245 /** 246 * Sets {@code <paths>}: The files or directories used as starting point for the listing; 247 wildcards * and ? are supported; relative paths are resolved on the 248 basis of the current working directory. 249 * 250 * @param paths the value for the {@code <paths>} operand 251 */ 252 public void setPaths(String... paths) { 253 this.paths = paths; 254 this.pathsIsSet = true; 255 } 256 /** 257 * Returns the {@code <args>} operand value: String arguments defining the options and operands for the command. 258 Options can be specified by acronym (with a leading dash "-") or by 259 long name (with two leading dashes "--"). Operands other than the 260 default "--paths" operand have to be prefixed with the operand 261 name (e.g. "--count" for a subsequent count operand value). 262 * 263 * @return the {@code <args>} operand value (variables are not resolved) 264 * @throws IllegalStateException if this operand has never been set 265 * 266 */ 267 public String[] getArgs() { 268 if (argsIsSet) { 269 return args; 270 } 271 throw new IllegalStateException("operand has not been set: " + args); 272 } 273 274 /** 275 * Returns true if the {@code <args>} operand has been set. 276 * 277 * @return true if the setter for the {@code <args>} operand has 278 * been called at least once 279 */ 280 public boolean isArgsSet() { 281 return argsIsSet; 282 } 283 284 /** 285 * Returns true if the {@code --}{@link LsOption#allFiles allFiles} option 286 * is set. The option is also known as {@code -}a option. 287 * <p> 288 * Description: Lists all files in the given directory, including hidden files 289 (those whose names start with \".\" in Unix). By default, these 290 files are excluded from the list. 291 * 292 * @return true if the {@code --allFiles} or {@code -a} option is set 293 */ 294 public boolean isAllFiles() { 295 return getOptions().isSet(LsOption.allFiles); 296 } 297 /** 298 * Returns true if the {@code --}{@link LsOption#humanReadable humanReadable} option 299 * is set. The option is also known as {@code -}h option. 300 * <p> 301 * Description: Print sizes in human readable format. (e.g., 1K, 234M, 2G, etc.) 302 * 303 * @return true if the {@code --humanReadable} or {@code -h} option is set 304 */ 305 public boolean isHumanReadable() { 306 return getOptions().isSet(LsOption.humanReadable); 307 } 308 /** 309 * Returns true if the {@code --}{@link LsOption#longFormat longFormat} option 310 * is set. The option is also known as {@code -}l option. 311 * <p> 312 * Description: Long format, displaying file types, permissions, number of hard 313 links, owner, group, size, date, and filename. 314 * 315 * @return true if the {@code --longFormat} or {@code -l} option is set 316 */ 317 public boolean isLongFormat() { 318 return getOptions().isSet(LsOption.longFormat); 319 } 320 /** 321 * Returns true if the {@code --}{@link LsOption#recurseSubdirs recurseSubdirs} option 322 * is set. The option is also known as {@code -}R option. 323 * <p> 324 * Description: Recursively lists subdirectories encountered. 325 * 326 * @return true if the {@code --recurseSubdirs} or {@code -R} option is set 327 */ 328 public boolean isRecurseSubdirs() { 329 return getOptions().isSet(LsOption.recurseSubdirs); 330 } 331 /** 332 * Returns true if the {@code --}{@link LsOption#reverseOrder reverseOrder} option 333 * is set. The option is also known as {@code -}r option. 334 * <p> 335 * Description: Reverses the order of the sort to get reverse collating sequence or 336 oldest first. 337 * 338 * @return true if the {@code --reverseOrder} or {@code -r} option is set 339 */ 340 public boolean isReverseOrder() { 341 return getOptions().isSet(LsOption.reverseOrder); 342 } 343 /** 344 * Returns true if the {@code --}{@link LsOption#timeSorted timeSorted} option 345 * is set. The option is also known as {@code -}t option. 346 * <p> 347 * Description: Sorts with the primary key being time modified (most recently 348 modified first) and the secondary key being filename in the 349 collating sequence. 350 * 351 * @return true if the {@code --timeSorted} or {@code -t} option is set 352 */ 353 public boolean isTimeSorted() { 354 return getOptions().isSet(LsOption.timeSorted); 355 } 356 357 @Override 358 public String toString() { 359 // ok, we have options or arguments or both 360 final StringBuilder sb = new StringBuilder(); 361 362 if (argsIsSet) { 363 for (String arg : args) { 364 if (sb.length() > 0) sb.append(' '); 365 sb.append(arg); 366 } 367 } else { 368 369 // first the options 370 if (options.size() > 0) { 371 sb.append(DefaultOptionSet.toString(options)); 372 } 373 // operand: <files> 374 if (filesIsSet) { 375 if (sb.length() > 0) sb.append(' '); 376 sb.append("--").append("files"); 377 sb.append(" ").append(toString(getFiles())); 378 } 379 // operand: <paths> 380 if (pathsIsSet) { 381 if (sb.length() > 0) sb.append(' '); 382 sb.append("--").append("paths"); 383 sb.append(" ").append(toString(getPaths())); 384 } 385 // operand: <args> 386 if (argsIsSet) { 387 if (sb.length() > 0) sb.append(' '); 388 sb.append("--").append("args"); 389 sb.append(" ").append(toString(getArgs())); 390 } 391 } 392 393 return sb.toString(); 394 } 395 private static String toString(Object value) { 396 if (value != null && value.getClass().isArray()) { 397 return ArrayUtil.toString(value); 398 } 399 return String.valueOf(value); 400 } 401}