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}