001package org.unix4j.unix.from;
002
003
004import org.unix4j.command.Arguments;
005import org.unix4j.context.ExecutionContext;
006import org.unix4j.convert.ValueConverter;
007import org.unix4j.util.ArrayUtil;
008import org.unix4j.variable.Arg;
009import org.unix4j.variable.VariableContext;
010
011import org.unix4j.unix.From;
012
013/**
014 * Arguments and options for the {@link From from} command.
015 */
016public final class FromArguments implements Arguments<FromArguments> {
017        
018
019        
020        // operand: <path>
021        private String path;
022        private boolean pathIsSet = false;
023        
024        // operand: <base>
025        private Class<?> base;
026        private boolean baseIsSet = false;
027        
028        // operand: <resource>
029        private String resource;
030        private boolean resourceIsSet = false;
031        
032        // operand: <input>
033        private org.unix4j.io.Input input;
034        private boolean inputIsSet = false;
035        
036        /**
037         * Constructor to use if no options are specified.
038         */
039        public FromArguments() {
040                super();
041        }
042
043        private Object resolveVariable(VariableContext context, String variable) {
044                final Object value = context.getValue(variable);
045                if (value != null) {
046                        return value;
047                }
048                throw new IllegalArgumentException("cannot resolve variable " + variable + 
049                                " in command: from " + this);
050        }
051        private <V> V convert(ExecutionContext context, String operandName, Class<V> operandType, Object value) {
052                final ValueConverter<V> converter = context.getValueConverterFor(operandType);
053                final V convertedValue;
054                if (converter != null) {
055                        convertedValue = converter.convert(value);
056                } else {
057                        convertedValue = null;
058                }
059                if (convertedValue != null) {
060                        return convertedValue;
061                }
062                throw new IllegalArgumentException("cannot convert --" + operandName + 
063                                " value '" + value + "' into the type " + operandType.getName() + 
064                                " for from command");
065        }
066        
067        @Override
068        public FromArguments getForContext(ExecutionContext context) {
069                if (context == null) {
070                        throw new NullPointerException("context cannot be null");
071                }
072                //no String... args for this command, hence nothing to resolve
073                return this;
074        }
075        
076        /**
077         * Returns the {@code <path>} operand value (variables are NOT resolved): the file to use as input; wildcards * and ? are supported; relative 
078                        paths are resolved on the basis of the current working directory.
079         * 
080         * @return the {@code <path>} operand value (variables are not resolved)
081         * @throws IllegalStateException if this operand has never been set
082         * @see #getPath(ExecutionContext)
083         */
084        public String getPath() {
085                if (pathIsSet) {
086                        return path;
087                }
088                throw new IllegalStateException("operand has not been set: " + path);
089        }
090        /**
091         * Returns the {@code <path>} (variables are resolved): the file to use as input; wildcards * and ? are supported; relative 
092                        paths are resolved on the basis of the current working directory.
093         * 
094         * @param context the execution context used to resolve variables
095         * @return the {@code <path>} operand value after resolving variables
096         * @throws IllegalStateException if this operand has never been set
097         * @see #getPath()
098         */
099        public String getPath(ExecutionContext context) {
100                final String value = getPath();
101                if (Arg.isVariable(value)) {
102                        final Object resolved = resolveVariable(context.getVariableContext(), value);
103                        final String converted = convert(context, "path", String.class, resolved);
104                        return converted;
105                }
106                return value;
107        }
108
109        /**
110         * Returns true if the {@code <path>} operand has been set. 
111         * <p>
112         * Note that this method returns true even if {@code null} was passed to the
113         * {@link #setPath(String)} method.
114         * 
115         * @return      true if the setter for the {@code <path>} operand has 
116         *                      been called at least once
117         */
118        public boolean isPathSet() {
119                return pathIsSet;
120        }
121        /**
122         * Sets {@code <path>}: the file to use as input; wildcards * and ? are supported; relative 
123                        paths are resolved on the basis of the current working directory.
124         * 
125         * @param path the value for the {@code <path>} operand
126         */
127        public void setPath(String path) {
128                this.path = path;
129                this.pathIsSet = true;
130        }
131        /**
132         * Returns the {@code <base>} operand value: base class for subsequent {@code resource} operand if relative paths are used.
133         * 
134         * @return the {@code <base>} operand value (variables are not resolved)
135         * @throws IllegalStateException if this operand has never been set
136         * 
137         */
138        public Class<?> getBase() {
139                if (baseIsSet) {
140                        return base;
141                }
142                throw new IllegalStateException("operand has not been set: " + base);
143        }
144
145        /**
146         * Returns true if the {@code <base>} operand has been set. 
147         * <p>
148         * Note that this method returns true even if {@code null} was passed to the
149         * {@link #setBase(Class)} method.
150         * 
151         * @return      true if the setter for the {@code <base>} operand has 
152         *                      been called at least once
153         */
154        public boolean isBaseSet() {
155                return baseIsSet;
156        }
157        /**
158         * Sets {@code <base>}: base class for subsequent {@code resource} operand if relative paths are used.
159         * 
160         * @param base the value for the {@code <base>} operand
161         */
162        public void setBase(Class<?> base) {
163                this.base = base;
164                this.baseIsSet = true;
165        }
166        /**
167         * Returns the {@code <resource>} operand value (variables are NOT resolved): a path to the file to redirect to the next command. The resource needs
168                        to be on the classpath. If the file is in the root directory, the 
169                        filename should be prefixed with a forward slash. e.g.:
170                        {@code "/test-file.txt"}.
171                        <p>
172                        If the file is in a package, then the package should be specified
173                        prefixed with a forward slash, and with each dot "." replaced with a
174                        forward slash. e.g.:
175                        {@code "/org/company/mypackage/test-file.txt"}.
176                        A {@code base} class operand can be provided for relative paths; see
177                        {@link java.lang.Class#getResourceAsStream(String)} for details about
178                        resource loading.
179         * 
180         * @return the {@code <resource>} operand value (variables are not resolved)
181         * @throws IllegalStateException if this operand has never been set
182         * @see #getResource(ExecutionContext)
183         */
184        public String getResource() {
185                if (resourceIsSet) {
186                        return resource;
187                }
188                throw new IllegalStateException("operand has not been set: " + resource);
189        }
190        /**
191         * Returns the {@code <resource>} (variables are resolved): a path to the file to redirect to the next command. The resource needs
192                        to be on the classpath. If the file is in the root directory, the 
193                        filename should be prefixed with a forward slash. e.g.:
194                        {@code "/test-file.txt"}.
195                        <p>
196                        If the file is in a package, then the package should be specified
197                        prefixed with a forward slash, and with each dot "." replaced with a
198                        forward slash. e.g.:
199                        {@code "/org/company/mypackage/test-file.txt"}.
200                        A {@code base} class operand can be provided for relative paths; see
201                        {@link java.lang.Class#getResourceAsStream(String)} for details about
202                        resource loading.
203         * 
204         * @param context the execution context used to resolve variables
205         * @return the {@code <resource>} operand value after resolving variables
206         * @throws IllegalStateException if this operand has never been set
207         * @see #getResource()
208         */
209        public String getResource(ExecutionContext context) {
210                final String value = getResource();
211                if (Arg.isVariable(value)) {
212                        final Object resolved = resolveVariable(context.getVariableContext(), value);
213                        final String converted = convert(context, "resource", String.class, resolved);
214                        return converted;
215                }
216                return value;
217        }
218
219        /**
220         * Returns true if the {@code <resource>} operand has been set. 
221         * <p>
222         * Note that this method returns true even if {@code null} was passed to the
223         * {@link #setResource(String)} method.
224         * 
225         * @return      true if the setter for the {@code <resource>} operand has 
226         *                      been called at least once
227         */
228        public boolean isResourceSet() {
229                return resourceIsSet;
230        }
231        /**
232         * Sets {@code <resource>}: a path to the file to redirect to the next command. The resource needs
233                        to be on the classpath. If the file is in the root directory, the 
234                        filename should be prefixed with a forward slash. e.g.:
235                        {@code "/test-file.txt"}.
236                        <p>
237                        If the file is in a package, then the package should be specified
238                        prefixed with a forward slash, and with each dot "." replaced with a
239                        forward slash. e.g.:
240                        {@code "/org/company/mypackage/test-file.txt"}.
241                        A {@code base} class operand can be provided for relative paths; see
242                        {@link java.lang.Class#getResourceAsStream(String)} for details about
243                        resource loading.
244         * 
245         * @param resource the value for the {@code <resource>} operand
246         */
247        public void setResource(String resource) {
248                this.resource = resource;
249                this.resourceIsSet = true;
250        }
251        /**
252         * Returns the {@code <input>} operand value: the input object to read from
253         * 
254         * @return the {@code <input>} operand value (variables are not resolved)
255         * @throws IllegalStateException if this operand has never been set
256         * 
257         */
258        public org.unix4j.io.Input getInput() {
259                if (inputIsSet) {
260                        return input;
261                }
262                throw new IllegalStateException("operand has not been set: " + input);
263        }
264
265        /**
266         * Returns true if the {@code <input>} operand has been set. 
267         * <p>
268         * Note that this method returns true even if {@code null} was passed to the
269         * {@link #setInput(org.unix4j.io.Input)} method.
270         * 
271         * @return      true if the setter for the {@code <input>} operand has 
272         *                      been called at least once
273         */
274        public boolean isInputSet() {
275                return inputIsSet;
276        }
277        /**
278         * Sets {@code <input>}: the input object to read from
279         * 
280         * @param input the value for the {@code <input>} operand
281         */
282        public void setInput(org.unix4j.io.Input input) {
283                this.input = input;
284                this.inputIsSet = true;
285        }
286        
287
288        @Override
289        public String toString() {
290                // ok, we have options or arguments or both
291                final StringBuilder sb = new StringBuilder();
292
293{
294                        // operand: <path>
295                        if (pathIsSet) {
296                                if (sb.length() > 0) sb.append(' ');
297                                sb.append("--").append("path");
298                                sb.append(" ").append(toString(getPath()));
299                        }
300                        // operand: <base>
301                        if (baseIsSet) {
302                                if (sb.length() > 0) sb.append(' ');
303                                sb.append("--").append("base");
304                                sb.append(" ").append(toString(getBase()));
305                        }
306                        // operand: <resource>
307                        if (resourceIsSet) {
308                                if (sb.length() > 0) sb.append(' ');
309                                sb.append("--").append("resource");
310                                sb.append(" ").append(toString(getResource()));
311                        }
312                        // operand: <input>
313                        if (inputIsSet) {
314                                if (sb.length() > 0) sb.append(' ');
315                                sb.append("--").append("input");
316                                sb.append(" ").append(toString(getInput()));
317                        }
318                }
319                
320                return sb.toString();
321        }
322        private static String toString(Object value) {
323                if (value != null && value.getClass().isArray()) {
324                        return ArrayUtil.toString(value);
325                }
326                return String.valueOf(value);
327        }
328}