001package org.unix4j.unix.sed;
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.Sed;
017
018/**
019 * Arguments and options for the {@link Sed sed} command.
020 */
021public final class SedArguments implements Arguments<SedArguments> {
022        
023        private final SedOptions options;
024
025        
026        // operand: <script>
027        private String script;
028        private boolean scriptIsSet = false;
029        
030        // operand: <regexp>
031        private String regexp;
032        private boolean regexpIsSet = false;
033        
034        // operand: <string1>
035        private String string1;
036        private boolean string1IsSet = false;
037        
038        // operand: <replacement>
039        private String replacement;
040        private boolean replacementIsSet = false;
041        
042        // operand: <string2>
043        private String string2;
044        private boolean string2IsSet = false;
045        
046        // operand: <occurrence>
047        private int[] occurrence;
048        private boolean occurrenceIsSet = false;
049        
050        // operand: <args>
051        private String[] args;
052        private boolean argsIsSet = false;
053        
054        /**
055         * Constructor to use if no options are specified.
056         */
057        public SedArguments() {
058                this.options = SedOptions.EMPTY;
059        }
060
061        /**
062         * Constructor with option set containing the selected command options.
063         * 
064         * @param options the selected options
065         * @throws NullPointerException if the argument is null
066         */
067        public SedArguments(SedOptions options) {
068                if (options == null) {
069                        throw new NullPointerException("options argument cannot be null");
070                }
071                this.options = options;
072        }
073        
074        /**
075         * Returns the options set containing the selected command options. Returns
076         * an empty options set if no option has been selected.
077         * 
078         * @return set with the selected options
079         */
080        public SedOptions getOptions() {
081                return options;
082        }
083
084        /**
085         * Constructor string arguments encoding options and arguments, possibly
086         * also containing variable expressions. 
087         * 
088         * @param args string arguments for the command
089         * @throws NullPointerException if args is null
090         */
091        public SedArguments(String... args) {
092                this();
093                this.args = args;
094                this.argsIsSet = true;
095        }
096        private Object[] resolveVariables(VariableContext context, String... unresolved) {
097                final Object[] resolved = new Object[unresolved.length];
098                for (int i = 0; i < resolved.length; i++) {
099                        final String expression = unresolved[i];
100                        if (Arg.isVariable(expression)) {
101                                resolved[i] = resolveVariable(context, expression);
102                        } else {
103                                resolved[i] = expression;
104                        }
105                }
106                return resolved;
107        }
108        private <V> V convertList(ExecutionContext context, String operandName, Class<V> operandType, List<Object> values) {
109                if (values.size() == 1) {
110                        final Object value = values.get(0);
111                        return convert(context, operandName, operandType, value);
112                }
113                return convert(context, operandName, operandType, values);
114        }
115
116        private Object resolveVariable(VariableContext context, String variable) {
117                final Object value = context.getValue(variable);
118                if (value != null) {
119                        return value;
120                }
121                throw new IllegalArgumentException("cannot resolve variable " + variable + 
122                                " in command: sed " + this);
123        }
124        private <V> V convert(ExecutionContext context, String operandName, Class<V> operandType, Object value) {
125                final ValueConverter<V> converter = context.getValueConverterFor(operandType);
126                final V convertedValue;
127                if (converter != null) {
128                        convertedValue = converter.convert(value);
129                } else {
130                        if (SedOptions.class.equals(operandType)) {
131                                convertedValue = operandType.cast(SedOptions.CONVERTER.convert(value));
132                        } else {
133                                convertedValue = null;
134                        }
135                }
136                if (convertedValue != null) {
137                        return convertedValue;
138                }
139                throw new IllegalArgumentException("cannot convert --" + operandName + 
140                                " value '" + value + "' into the type " + operandType.getName() + 
141                                " for sed command");
142        }
143        
144        @Override
145        public SedArguments getForContext(ExecutionContext context) {
146                if (context == null) {
147                        throw new NullPointerException("context cannot be null");
148                }
149                if (!argsIsSet || args.length == 0) {
150                        //nothing to resolve
151                        return this;
152                }
153
154                //check if there is at least one variable
155                boolean hasVariable = false;
156                for (final String arg : args) {
157                        if (arg != null && arg.startsWith("$")) {
158                                hasVariable = true;
159                                break;
160                        }
161                }
162                //resolve variables
163                final Object[] resolvedArgs = hasVariable ? resolveVariables(context.getVariableContext(), this.args) : this.args;
164                
165                //convert now
166                final List<String> defaultOperands = Arrays.asList("script");
167                final Map<String, List<Object>> map = ArgsUtil.parseArgs("options", defaultOperands, resolvedArgs);
168                final SedOptions.Default options = new SedOptions.Default();
169                final SedArguments argsForContext = new SedArguments(options);
170                for (final Map.Entry<String, List<Object>> e : map.entrySet()) {
171                        if ("script".equals(e.getKey())) {
172                                        
173                                final String value = convertList(context, "script", String.class, e.getValue());  
174                                argsForContext.setScript(value);
175                        } else if ("regexp".equals(e.getKey())) {
176                                        
177                                final String value = convertList(context, "regexp", String.class, e.getValue());  
178                                argsForContext.setRegexp(value);
179                        } else if ("string1".equals(e.getKey())) {
180                                        
181                                final String value = convertList(context, "string1", String.class, e.getValue());  
182                                argsForContext.setString1(value);
183                        } else if ("replacement".equals(e.getKey())) {
184                                        
185                                final String value = convertList(context, "replacement", String.class, e.getValue());  
186                                argsForContext.setReplacement(value);
187                        } else if ("string2".equals(e.getKey())) {
188                                        
189                                final String value = convertList(context, "string2", String.class, e.getValue());  
190                                argsForContext.setString2(value);
191                        } else if ("occurrence".equals(e.getKey())) {
192                                        
193                                final int[] value = convertList(context, "occurrence", int[].class, e.getValue());  
194                                argsForContext.setOccurrence(value);
195                        } else if ("args".equals(e.getKey())) {
196                                throw new IllegalStateException("invalid operand '" + e.getKey() + "' in sed command args: " + Arrays.toString(args));
197                        } else if ("options".equals(e.getKey())) {
198                                        
199                                final SedOptions value = convertList(context, "options", SedOptions.class, e.getValue());  
200                                options.setAll(value);
201                        } else {
202                                throw new IllegalStateException("invalid operand '" + e.getKey() + "' in sed command args: " + Arrays.toString(args));
203                        }
204                }
205                return argsForContext;
206        }
207        
208        /**
209         * Returns the {@code <script>} operand value (variables are NOT resolved): Sed script as one string, such as "s/original/replacement/g".
210         * 
211         * @return the {@code <script>} operand value (variables are not resolved)
212         * @throws IllegalStateException if this operand has never been set
213         * @see #getScript(ExecutionContext)
214         */
215        public String getScript() {
216                if (scriptIsSet) {
217                        return script;
218                }
219                throw new IllegalStateException("operand has not been set: " + script);
220        }
221        /**
222         * Returns the {@code <script>} (variables are resolved): Sed script as one string, such as "s/original/replacement/g".
223         * 
224         * @param context the execution context used to resolve variables
225         * @return the {@code <script>} operand value after resolving variables
226         * @throws IllegalStateException if this operand has never been set
227         * @see #getScript()
228         */
229        public String getScript(ExecutionContext context) {
230                final String value = getScript();
231                if (Arg.isVariable(value)) {
232                        final Object resolved = resolveVariable(context.getVariableContext(), value);
233                        final String converted = convert(context, "script", String.class, resolved);
234                        return converted;
235                }
236                return value;
237        }
238
239        /**
240         * Returns true if the {@code <script>} operand has been set. 
241         * <p>
242         * Note that this method returns true even if {@code null} was passed to the
243         * {@link #setScript(String)} method.
244         * 
245         * @return      true if the setter for the {@code <script>} operand has 
246         *                      been called at least once
247         */
248        public boolean isScriptSet() {
249                return scriptIsSet;
250        }
251        /**
252         * Sets {@code <script>}: Sed script as one string, such as "s/original/replacement/g".
253         * 
254         * @param script the value for the {@code <script>} operand
255         */
256        public void setScript(String script) {
257                this.script = script;
258                this.scriptIsSet = true;
259        }
260        /**
261         * Returns the {@code <regexp>} operand value (variables are NOT resolved): Regular expression matched against a line.
262         * 
263         * @return the {@code <regexp>} operand value (variables are not resolved)
264         * @throws IllegalStateException if this operand has never been set
265         * @see #getRegexp(ExecutionContext)
266         */
267        public String getRegexp() {
268                if (regexpIsSet) {
269                        return regexp;
270                }
271                throw new IllegalStateException("operand has not been set: " + regexp);
272        }
273        /**
274         * Returns the {@code <regexp>} (variables are resolved): Regular expression matched against a line.
275         * 
276         * @param context the execution context used to resolve variables
277         * @return the {@code <regexp>} operand value after resolving variables
278         * @throws IllegalStateException if this operand has never been set
279         * @see #getRegexp()
280         */
281        public String getRegexp(ExecutionContext context) {
282                final String value = getRegexp();
283                if (Arg.isVariable(value)) {
284                        final Object resolved = resolveVariable(context.getVariableContext(), value);
285                        final String converted = convert(context, "regexp", String.class, resolved);
286                        return converted;
287                }
288                return value;
289        }
290
291        /**
292         * Returns true if the {@code <regexp>} operand has been set. 
293         * <p>
294         * Note that this method returns true even if {@code null} was passed to the
295         * {@link #setRegexp(String)} method.
296         * 
297         * @return      true if the setter for the {@code <regexp>} operand has 
298         *                      been called at least once
299         */
300        public boolean isRegexpSet() {
301                return regexpIsSet;
302        }
303        /**
304         * Sets {@code <regexp>}: Regular expression matched against a line.
305         * 
306         * @param regexp the value for the {@code <regexp>} operand
307         */
308        public void setRegexp(String regexp) {
309                this.regexp = regexp;
310                this.regexpIsSet = true;
311        }
312        /**
313         * Returns the {@code <string1>} operand value (variables are NOT resolved): Regular expression matched against a line for all commands except 
314                        for command y where string1 contains the source characters for the 
315                        translation.
316         * 
317         * @return the {@code <string1>} operand value (variables are not resolved)
318         * @throws IllegalStateException if this operand has never been set
319         * @see #getString1(ExecutionContext)
320         */
321        public String getString1() {
322                if (string1IsSet) {
323                        return string1;
324                }
325                throw new IllegalStateException("operand has not been set: " + string1);
326        }
327        /**
328         * Returns the {@code <string1>} (variables are resolved): Regular expression matched against a line for all commands except 
329                        for command y where string1 contains the source characters for the 
330                        translation.
331         * 
332         * @param context the execution context used to resolve variables
333         * @return the {@code <string1>} operand value after resolving variables
334         * @throws IllegalStateException if this operand has never been set
335         * @see #getString1()
336         */
337        public String getString1(ExecutionContext context) {
338                final String value = getString1();
339                if (Arg.isVariable(value)) {
340                        final Object resolved = resolveVariable(context.getVariableContext(), value);
341                        final String converted = convert(context, "string1", String.class, resolved);
342                        return converted;
343                }
344                return value;
345        }
346
347        /**
348         * Returns true if the {@code <string1>} operand has been set. 
349         * <p>
350         * Note that this method returns true even if {@code null} was passed to the
351         * {@link #setString1(String)} method.
352         * 
353         * @return      true if the setter for the {@code <string1>} operand has 
354         *                      been called at least once
355         */
356        public boolean isString1Set() {
357                return string1IsSet;
358        }
359        /**
360         * Sets {@code <string1>}: Regular expression matched against a line for all commands except 
361                        for command y where string1 contains the source characters for the 
362                        translation.
363         * 
364         * @param string1 the value for the {@code <string1>} operand
365         */
366        public void setString1(String string1) {
367                this.string1 = string1;
368                this.string1IsSet = true;
369        }
370        /**
371         * Returns the {@code <replacement>} operand value (variables are NOT resolved): Replacement string for substitute command. The characters "$0"
372                        appearing in the replacement are replaced by the line matching
373                        the regexp.  The characters "$n", where n is a digit other than zero,
374                        are replaced by the text matched by the corresponding backreference
375                        expression (aka group).  The special meaning of "$n" in this context
376                        can be suppressed by preceding it by a backslash.
377         * 
378         * @return the {@code <replacement>} operand value (variables are not resolved)
379         * @throws IllegalStateException if this operand has never been set
380         * @see #getReplacement(ExecutionContext)
381         */
382        public String getReplacement() {
383                if (replacementIsSet) {
384                        return replacement;
385                }
386                throw new IllegalStateException("operand has not been set: " + replacement);
387        }
388        /**
389         * Returns the {@code <replacement>} (variables are resolved): Replacement string for substitute command. The characters "$0"
390                        appearing in the replacement are replaced by the line matching
391                        the regexp.  The characters "$n", where n is a digit other than zero,
392                        are replaced by the text matched by the corresponding backreference
393                        expression (aka group).  The special meaning of "$n" in this context
394                        can be suppressed by preceding it by a backslash.
395         * 
396         * @param context the execution context used to resolve variables
397         * @return the {@code <replacement>} operand value after resolving variables
398         * @throws IllegalStateException if this operand has never been set
399         * @see #getReplacement()
400         */
401        public String getReplacement(ExecutionContext context) {
402                final String value = getReplacement();
403                if (Arg.isVariable(value)) {
404                        final Object resolved = resolveVariable(context.getVariableContext(), value);
405                        final String converted = convert(context, "replacement", String.class, resolved);
406                        return converted;
407                }
408                return value;
409        }
410
411        /**
412         * Returns true if the {@code <replacement>} operand has been set. 
413         * <p>
414         * Note that this method returns true even if {@code null} was passed to the
415         * {@link #setReplacement(String)} method.
416         * 
417         * @return      true if the setter for the {@code <replacement>} operand has 
418         *                      been called at least once
419         */
420        public boolean isReplacementSet() {
421                return replacementIsSet;
422        }
423        /**
424         * Sets {@code <replacement>}: Replacement string for substitute command. The characters "$0"
425                        appearing in the replacement are replaced by the line matching
426                        the regexp.  The characters "$n", where n is a digit other than zero,
427                        are replaced by the text matched by the corresponding backreference
428                        expression (aka group).  The special meaning of "$n" in this context
429                        can be suppressed by preceding it by a backslash.
430         * 
431         * @param replacement the value for the {@code <replacement>} operand
432         */
433        public void setReplacement(String replacement) {
434                this.replacement = replacement;
435                this.replacementIsSet = true;
436        }
437        /**
438         * Returns the {@code <string2>} operand value (variables are NOT resolved): Replacement string for substitute command s; appended, inserted or
439                        changed text for a, i and c command; destination characters for
440                        translate command y; ignored by all other commands.
441                        <p>
442                        If string2 is a replacement string for the substitute command: the
443                        characters "$0" appearing in the replacement are replaced
444                        by the line matching the regexp; the characters "$n", where n is a
445                        digit other than zero, are replaced by the text matched by the
446                        corresponding backreference expression (aka group).  The special
447                        meaning of "$n" in this context can be suppressed by preceding it
448                        by a backslash.
449<p>
450                        (This operand only applies to the commands s, a, i, c and y and is 
451                        ignored by all other commands).
452         * 
453         * @return the {@code <string2>} operand value (variables are not resolved)
454         * @throws IllegalStateException if this operand has never been set
455         * @see #getString2(ExecutionContext)
456         */
457        public String getString2() {
458                if (string2IsSet) {
459                        return string2;
460                }
461                throw new IllegalStateException("operand has not been set: " + string2);
462        }
463        /**
464         * Returns the {@code <string2>} (variables are resolved): Replacement string for substitute command s; appended, inserted or
465                        changed text for a, i and c command; destination characters for
466                        translate command y; ignored by all other commands.
467                        <p>
468                        If string2 is a replacement string for the substitute command: the
469                        characters "$0" appearing in the replacement are replaced
470                        by the line matching the regexp; the characters "$n", where n is a
471                        digit other than zero, are replaced by the text matched by the
472                        corresponding backreference expression (aka group).  The special
473                        meaning of "$n" in this context can be suppressed by preceding it
474                        by a backslash.
475<p>
476                        (This operand only applies to the commands s, a, i, c and y and is 
477                        ignored by all other commands).
478         * 
479         * @param context the execution context used to resolve variables
480         * @return the {@code <string2>} operand value after resolving variables
481         * @throws IllegalStateException if this operand has never been set
482         * @see #getString2()
483         */
484        public String getString2(ExecutionContext context) {
485                final String value = getString2();
486                if (Arg.isVariable(value)) {
487                        final Object resolved = resolveVariable(context.getVariableContext(), value);
488                        final String converted = convert(context, "string2", String.class, resolved);
489                        return converted;
490                }
491                return value;
492        }
493
494        /**
495         * Returns true if the {@code <string2>} operand has been set. 
496         * <p>
497         * Note that this method returns true even if {@code null} was passed to the
498         * {@link #setString2(String)} method.
499         * 
500         * @return      true if the setter for the {@code <string2>} operand has 
501         *                      been called at least once
502         */
503        public boolean isString2Set() {
504                return string2IsSet;
505        }
506        /**
507         * Sets {@code <string2>}: Replacement string for substitute command s; appended, inserted or
508                        changed text for a, i and c command; destination characters for
509                        translate command y; ignored by all other commands.
510                        <p>
511                        If string2 is a replacement string for the substitute command: the
512                        characters "$0" appearing in the replacement are replaced
513                        by the line matching the regexp; the characters "$n", where n is a
514                        digit other than zero, are replaced by the text matched by the
515                        corresponding backreference expression (aka group).  The special
516                        meaning of "$n" in this context can be suppressed by preceding it
517                        by a backslash.
518<p>
519                        (This operand only applies to the commands s, a, i, c and y and is 
520                        ignored by all other commands).
521         * 
522         * @param string2 the value for the {@code <string2>} operand
523         */
524        public void setString2(String string2) {
525                this.string2 = string2;
526                this.string2IsSet = true;
527        }
528        /**
529         * Returns the {@code <occurrence>} operand value: Substitute for the given occurrences only of the regexp found within 
530                        the matched string; the occurrence indices are one-based. If empty 
531                        or omitted, all occurrences are substituted.
532                        <p>
533                        (This operand only applies to the substitute command and is ignored
534                        by all other commands).
535         * 
536         * @return the {@code <occurrence>} operand value (variables are not resolved)
537         * @throws IllegalStateException if this operand has never been set
538         * 
539         */
540        public int[] getOccurrence() {
541                if (occurrenceIsSet) {
542                        return occurrence;
543                }
544                throw new IllegalStateException("operand has not been set: " + occurrence);
545        }
546
547        /**
548         * Returns true if the {@code <occurrence>} operand has been set. 
549         * <p>
550         * Note that this method returns true even if {@code null} was passed to the
551         * {@link #setOccurrence(int[])} method.
552         * 
553         * @return      true if the setter for the {@code <occurrence>} operand has 
554         *                      been called at least once
555         */
556        public boolean isOccurrenceSet() {
557                return occurrenceIsSet;
558        }
559        /**
560         * Sets {@code <occurrence>}: Substitute for the given occurrences only of the regexp found within 
561                        the matched string; the occurrence indices are one-based. If empty 
562                        or omitted, all occurrences are substituted.
563                        <p>
564                        (This operand only applies to the substitute command and is ignored
565                        by all other commands).
566         * 
567         * @param occurrence the value for the {@code <occurrence>} operand
568         */
569        public void setOccurrence(int... occurrence) {
570                this.occurrence = occurrence;
571                this.occurrenceIsSet = true;
572        }
573        /**
574         * Returns the {@code <args>} operand value: String arguments defining the options and operands for the command. 
575                        Options can be specified by acronym (with a leading dash "-") or by 
576                        long name (with two leading dashes "--"). Operands other than the
577                        default "--script" operand have to be prefixed with the operand name
578                        (e.g. "--occurrence" for subsequent occurrence indices).
579         * 
580         * @return the {@code <args>} operand value (variables are not resolved)
581         * @throws IllegalStateException if this operand has never been set
582         * 
583         */
584        public String[] getArgs() {
585                if (argsIsSet) {
586                        return args;
587                }
588                throw new IllegalStateException("operand has not been set: " + args);
589        }
590
591        /**
592         * Returns true if the {@code <args>} operand has been set. 
593         * 
594         * @return      true if the setter for the {@code <args>} operand has 
595         *                      been called at least once
596         */
597        public boolean isArgsSet() {
598                return argsIsSet;
599        }
600        
601        /**
602         * Returns true if the {@code --}{@link SedOption#quiet quiet} option
603         * is set. The option is also known as {@code -}n option.
604         * <p>
605         * Description: Suppress the default output (in which each line, after it is 
606                        examined for editing, is written to standard output). Only lines 
607                        explicitly selected for output are written.
608         * 
609         * @return true if the {@code --quiet} or {@code -n} option is set
610         */
611        public boolean isQuiet() {
612                return getOptions().isSet(SedOption.quiet);
613        }
614        /**
615         * Returns true if the {@code --}{@link SedOption#global global} option
616         * is set. The option is also known as {@code -}g option.
617         * <p>
618         * Description: Globally substitute for all non-overlapping instances of the regexp 
619                        rather than just the first one. 
620                        <p>
621                        (This option is ignored if the occurrence operand is specified).
622         * 
623         * @return true if the {@code --global} or {@code -g} option is set
624         */
625        public boolean isGlobal() {
626                return getOptions().isSet(SedOption.global);
627        }
628        /**
629         * Returns true if the {@code --}{@link SedOption#print print} option
630         * is set. The option is also known as {@code -}p option.
631         * <p>
632         * Description: Write the matched line to standard output.
633         * 
634         * @return true if the {@code --print} or {@code -p} option is set
635         */
636        public boolean isPrint() {
637                return getOptions().isSet(SedOption.print);
638        }
639        /**
640         * Returns true if the {@code --}{@link SedOption#lineNumber lineNumber} option
641         * is set. The option is also known as {@code -}l option.
642         * <p>
643         * Description: Writes the current line number on a separate line to the standard 
644                        output.
645         * 
646         * @return true if the {@code --lineNumber} or {@code -l} option is set
647         */
648        public boolean isLineNumber() {
649                return getOptions().isSet(SedOption.lineNumber);
650        }
651        /**
652         * Returns true if the {@code --}{@link SedOption#ignoreCase ignoreCase} option
653         * is set. The option is also known as {@code -}I option.
654         * <p>
655         * Description: Use case insensitive pattern matching.
656         * 
657         * @return true if the {@code --ignoreCase} or {@code -I} option is set
658         */
659        public boolean isIgnoreCase() {
660                return getOptions().isSet(SedOption.ignoreCase);
661        }
662        /**
663         * Returns true if the {@code --}{@link SedOption#substitute substitute} option
664         * is set. The option is also known as {@code -}s option.
665         * <p>
666         * Description: Substitutes the replacement string for instances of the regexp in 
667                        the matched line.
668<p>
669                        The characters "$0" appearing in the replacement are replaced
670                        by the line matching the regexp.  The characters "$n", where n is a
671                        digit other than zero, are replaced by the text matched by the
672                        corresponding backreference expression (aka group).  The special
673                        meaning of "$n" in this context can be suppressed by preceding it
674                        by a backslash.
675<p>
676                        A line can be split by substituting a newline ('\n') into it. 
677                        <p>
678                        A substitution is considered to have been performed even if the 
679                        replacement string is identical to the string that it replaces.
680         * 
681         * @return true if the {@code --substitute} or {@code -s} option is set
682         */
683        public boolean isSubstitute() {
684                return getOptions().isSet(SedOption.substitute);
685        }
686        /**
687         * Returns true if the {@code --}{@link SedOption#append append} option
688         * is set. The option is also known as {@code -}a option.
689         * <p>
690         * Description: Append string2 as a separate line after the matched line.
691         * 
692         * @return true if the {@code --append} or {@code -a} option is set
693         */
694        public boolean isAppend() {
695                return getOptions().isSet(SedOption.append);
696        }
697        /**
698         * Returns true if the {@code --}{@link SedOption#insert insert} option
699         * is set. The option is also known as {@code -}i option.
700         * <p>
701         * Description: Insert string2 as a separate line before the matched line.
702         * 
703         * @return true if the {@code --insert} or {@code -i} option is set
704         */
705        public boolean isInsert() {
706                return getOptions().isSet(SedOption.insert);
707        }
708        /**
709         * Returns true if the {@code --}{@link SedOption#change change} option
710         * is set. The option is also known as {@code -}c option.
711         * <p>
712         * Description: Write string2 as a separate line instead of the matched line.
713         * 
714         * @return true if the {@code --change} or {@code -c} option is set
715         */
716        public boolean isChange() {
717                return getOptions().isSet(SedOption.change);
718        }
719        /**
720         * Returns true if the {@code --}{@link SedOption#delete delete} option
721         * is set. The option is also known as {@code -}d option.
722         * <p>
723         * Description: Delete the matched line.
724         * 
725         * @return true if the {@code --delete} or {@code -d} option is set
726         */
727        public boolean isDelete() {
728                return getOptions().isSet(SedOption.delete);
729        }
730        /**
731         * Returns true if the {@code --}{@link SedOption#translate translate} option
732         * is set. The option is also known as {@code -}y option.
733         * <p>
734         * Description: Replace all occurrences of characters in string1 with the 
735                        corresponding characters in string2. If the number of characters in 
736                        the two strings are not equal, or if any of the characters in 
737                        string1 appear more than once, the results are undefined.
738         * 
739         * @return true if the {@code --translate} or {@code -y} option is set
740         */
741        public boolean isTranslate() {
742                return getOptions().isSet(SedOption.translate);
743        }
744
745        @Override
746        public String toString() {
747                // ok, we have options or arguments or both
748                final StringBuilder sb = new StringBuilder();
749
750                if (argsIsSet) {
751                        for (String arg : args) {
752                                if (sb.length() > 0) sb.append(' ');
753                                sb.append(arg);
754                        }
755                } else {
756                
757                        // first the options
758                        if (options.size() > 0) {
759                                sb.append(DefaultOptionSet.toString(options));
760                        }
761                        // operand: <script>
762                        if (scriptIsSet) {
763                                if (sb.length() > 0) sb.append(' ');
764                                sb.append("--").append("script");
765                                sb.append(" ").append(toString(getScript()));
766                        }
767                        // operand: <regexp>
768                        if (regexpIsSet) {
769                                if (sb.length() > 0) sb.append(' ');
770                                sb.append("--").append("regexp");
771                                sb.append(" ").append(toString(getRegexp()));
772                        }
773                        // operand: <string1>
774                        if (string1IsSet) {
775                                if (sb.length() > 0) sb.append(' ');
776                                sb.append("--").append("string1");
777                                sb.append(" ").append(toString(getString1()));
778                        }
779                        // operand: <replacement>
780                        if (replacementIsSet) {
781                                if (sb.length() > 0) sb.append(' ');
782                                sb.append("--").append("replacement");
783                                sb.append(" ").append(toString(getReplacement()));
784                        }
785                        // operand: <string2>
786                        if (string2IsSet) {
787                                if (sb.length() > 0) sb.append(' ');
788                                sb.append("--").append("string2");
789                                sb.append(" ").append(toString(getString2()));
790                        }
791                        // operand: <occurrence>
792                        if (occurrenceIsSet) {
793                                if (sb.length() > 0) sb.append(' ');
794                                sb.append("--").append("occurrence");
795                                sb.append(" ").append(toString(getOccurrence()));
796                        }
797                        // operand: <args>
798                        if (argsIsSet) {
799                                if (sb.length() > 0) sb.append(' ');
800                                sb.append("--").append("args");
801                                sb.append(" ").append(toString(getArgs()));
802                        }
803                }
804                
805                return sb.toString();
806        }
807        private static String toString(Object value) {
808                if (value != null && value.getClass().isArray()) {
809                        return ArrayUtil.toString(value);
810                }
811                return String.valueOf(value);
812        }
813}