001    /**
002     *      jline - Java console input library
003     *      Copyright (c) 2002-2006, Marc Prud'hommeaux mwp1@cornell.edu
004     *      All rights reserved.
005     *
006     *      Redistribution and use in source and binary forms, with or
007     *      without modification, are permitted provided that the following
008     *      conditions are met:
009     *
010     *      Redistributions of source code must retain the above copyright
011     *      notice, this list of conditions and the following disclaimer.
012     *
013     *      Redistributions in binary form must reproduce the above copyright
014     *      notice, this list of conditions and the following disclaimer
015     *      in the documentation and/or other materials provided with
016     *      the distribution.
017     *
018     *      Neither the name of JLine nor the names of its contributors
019     *      may be used to endorse or promote products derived from this
020     *      software without specific prior written permission.
021     *
022     *      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
023     *      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
024     *      BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
025     *      AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
026     *      EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
027     *      FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
028     *      OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
029     *      PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
030     *      DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
031     *      AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
032     *      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
033     *      IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
034     *      OF THE POSSIBILITY OF SUCH DAMAGE.
035     */
036    package jline;
037    
038    import java.util.*;
039    
040    
041    /**
042     *  A {@link Completor} implementation that invokes a child completor
043     *  using the appropriate <i>separator</i> argument. This
044     *  can be used instead of the individual completors having to
045     *  know about argument parsing semantics.
046     *  <p>
047     *  <strong>Example 1</strong>: Any argument of the command line can
048     *  use file completion.
049     *  <p>
050     *  <pre>
051     *      consoleReader.addCompletor (new ArgumentCompletor (
052     *              new {@link FileNameCompletor} ()))
053     *  </pre>
054     *  <p>
055     *  <strong>Example 2</strong>: The first argument of the command line
056     *  can be completed with any of "foo", "bar", or "baz", and remaining
057     *  arguments can be completed with a file name.
058     *  <p>
059     *  <pre>
060     *      consoleReader.addCompletor (new ArgumentCompletor (
061     *              new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"})));
062     *      consoleReader.addCompletor (new ArgumentCompletor (
063     *              new {@link FileNameCompletor} ()));
064     *  </pre>
065     *
066     *  <p>
067     *      When the argument index is past the last embedded completors, the last
068     *      completors is always used. To disable this behavior, have the last
069     *      completor be a {@link NullCompletor}. For example:
070     *      </p>
071     *
072     *      <pre>
073     *      consoleReader.addCompletor (new ArgumentCompletor (
074     *              new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"}),
075     *              new {@link SimpleCompletor} (new String [] { "xxx", "yyy", "xxx"}),
076     *              new {@link NullCompletor}
077     *              ));
078     *      </pre>
079     *  <p>
080     *  TODO: handle argument quoting and escape characters
081     *  </p>
082     *
083     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
084     */
085    public class ArgumentCompletor
086            implements Completor
087    {
088            final Completor []                      completors;
089            final ArgumentDelimiter         delim;
090            boolean                                         strict = true;
091    
092    
093            /**
094             *  Constuctor: create a new completor with the default
095             *  argument separator of " ".
096             *
097             *  @param  completor  the embedded completor
098             */
099            public ArgumentCompletor (final Completor completor)
100            {
101                    this (new Completor [] { completor });
102            }
103    
104    
105            /**
106             *  Constuctor: create a new completor with the default
107             *  argument separator of " ".
108             *
109             *  @param  completors  the List of completors to use
110             */
111            public ArgumentCompletor (final List completors)
112            {
113                    this ((Completor [])completors.toArray (
114                            new Completor [completors.size ()]));
115            }
116    
117    
118            /**
119             *  Constuctor: create a new completor with the default
120             *  argument separator of " ".
121             *
122             *  @param  completors  the embedded argument completors
123             */
124            public ArgumentCompletor (final Completor [] completors)
125            {
126                    this (completors, new WhitespaceArgumentDelimiter ());
127            }
128    
129    
130            /**
131             *  Constuctor: create a new completor with the specified
132             *  argument delimiter.
133             *
134             *  @param  completor   the embedded completor
135             *  @param  delim               the delimiter for parsing arguments
136             */
137            public ArgumentCompletor (final Completor completor,
138                    final ArgumentDelimiter delim)
139            {
140                    this (new Completor [] { completor }, delim);
141            }
142    
143    
144            /**
145             *  Constuctor: create a new completor with the specified
146             *  argument delimiter.
147             *
148             *  @param  completors  the embedded completors
149             *  @param  delim               the delimiter for parsing arguments
150             */
151            public ArgumentCompletor (final Completor [] completors,
152                    final ArgumentDelimiter delim)
153            {
154                    this.completors = completors;
155                    this.delim = delim;
156            }
157    
158    
159            /**
160             *  If true, a completion at argument index N will only succeed
161             *  if all the completions from 0-(N-1) also succeed.
162             */
163            public void setStrict (final boolean strict)
164            {
165                    this.strict = strict;
166            }
167    
168    
169            /**
170             *  Returns whether a completion at argument index N will succees
171             *  if all the completions from arguments 0-(N-1) also succeed.
172             */
173            public boolean getStrict ()
174            {
175                    return this.strict;
176            }
177    
178    
179            public int complete (final String buffer, final int cursor,
180                    final List candidates)
181            {
182                    ArgumentList list = delim.delimit (buffer, cursor);
183                    int argpos = list.getArgumentPosition ();
184                    int argIndex = list.getCursorArgumentIndex ();
185    
186                    if (argIndex < 0)
187                       return -1;
188    
189                    final Completor comp;
190    
191                    // if we are beyond the end of the completors, just use the last one
192                    if (argIndex >= completors.length)
193                            comp = completors [completors.length - 1];
194                    else
195                            comp = completors [argIndex];
196    
197                    // ensure that all the previous completors are successful before
198                    // allowing this completor to pass (only if strict is true).
199                    for (int i = 0; getStrict () && i < argIndex; i++)
200                    {
201                            Completor sub = completors [i >= completors.length
202                                    ? completors.length - 1 : i];
203                            String [] args = list.getArguments ();
204                            String arg = args == null || i >= args.length ? "" : args [i];
205    
206                            List subCandidates = new LinkedList ();
207                            if (sub.complete (arg, arg.length (), subCandidates) == -1)
208                                    return -1;
209    
210                            if (subCandidates.size () == 0)
211                                    return -1;
212                    }
213    
214                    int ret = comp.complete (list.getCursorArgument (), argpos, candidates);
215                    if (ret == -1)
216                            return -1;
217    
218                    int pos = ret + (list.getBufferPosition () - argpos) + 1;
219    
220                    /**
221                     *      Special case: when completing in the middle of a line, and the
222                     *      area under the cursor is a delimiter, then trim any delimiters
223                     *      from the candidates, since we do not need to have an extra
224                     *      delimiter.
225                     *
226                     *      E.g., if we have a completion for "foo", and we
227                     *      enter "f bar" into the buffer, and move to after the "f"
228                     *      and hit TAB, we want "foo bar" instead of "foo  bar".
229                     */
230                    if (cursor != buffer.length () && delim.isDelimiter (buffer, cursor))
231                    {
232                            for (int i = 0; i < candidates.size (); i++)
233                            {
234                                    String val = candidates.get (i).toString ();
235                                    while (val.length () > 0 &&
236                                            delim.isDelimiter (val, val.length () - 1))
237                                            val = val.substring (0, val.length () - 1);
238    
239                                    candidates.set (i, val);
240                            }
241                    }
242    
243                    ConsoleReader.debug ("Completing " + buffer + "(pos=" + cursor + ") "
244                            + "with: " + candidates + ": offset=" + pos);
245    
246                    return pos;
247            }
248    
249    
250            /**
251             *  The {@link ArgumentCompletor.ArgumentDelimiter} allows custom
252             *  breaking up of a {@link String} into individual arguments in
253             *  order to dispatch the arguments to the nested {@link Completor}.
254             *
255             *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
256             */
257            public static interface ArgumentDelimiter
258            {
259                    /**
260                     *  Break the specified buffer into individual tokens
261                     *  that can be completed on their own.
262                     *
263                     *  @param  buffer                      the buffer to split
264                     *  @param  argumentPosition    the current position of the
265                     *                                              cursor in the buffer
266                     *  @return                     the tokens
267                     */
268                    ArgumentList delimit (String buffer, int argumentPosition);
269    
270    
271                    /**
272                     *  Returns true if the specified character is a whitespace
273                     *  parameter.
274                     *
275                     *  @param  buffer      the complete command buffer
276                     *  @param  pos         the index of the character in the buffer
277                     *  @return                     true if the character should be a delimiter
278                     */
279                    boolean isDelimiter (String buffer, int pos);
280            }
281    
282    
283            /**
284             *  Abstract implementation of a delimiter that uses the
285             *  {@link #isDelimiter} method to determine if a particular
286             *  character should be used as a delimiter.
287             *
288             *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
289             */
290            public static abstract class AbstractArgumentDelimiter
291                    implements ArgumentDelimiter
292            {
293                    private char [] quoteChars = new char [] { '\'', '"' };
294                    private char [] escapeChars = new char [] { '\\' };
295    
296    
297                    public void setQuoteChars (final char [] quoteChars)
298                    {
299                            this.quoteChars = quoteChars;
300                    }
301    
302    
303                    public char [] getQuoteChars ()
304                    {
305                            return this.quoteChars;
306                    }
307    
308    
309                    public void setEscapeChars (final char [] escapeChars)
310                    {
311                            this.escapeChars = escapeChars;
312                    }
313    
314    
315                    public char [] getEscapeChars ()
316                    {
317                            return this.escapeChars;
318                    }
319    
320    
321    
322                    public ArgumentList delimit (final String buffer, final int cursor)
323                    {
324                            List args = new LinkedList ();
325                            StringBuffer arg = new StringBuffer ();
326                            int argpos = -1;
327                            int bindex = -1;
328    
329                            for (int i = 0; buffer != null && i <= buffer.length (); i++)
330                            {
331                                    // once we reach the cursor, set the
332                                    // position of the selected index
333                                    if (i == cursor)
334                                    {
335                                            bindex = args.size ();
336                                            // the position in the current argument is just the
337                                            // length of the current argument
338                                            argpos = arg.length ();
339                                    }
340    
341                                    if (i == buffer.length () || isDelimiter (buffer, i))
342                                    {
343                                            if (arg.length () > 0)
344                                            {
345                                                    args.add (arg.toString ());
346                                                    arg.setLength (0); // reset the arg
347                                            }
348                                    }
349                                    else
350                                    {
351                                            arg.append (buffer.charAt (i));
352                                    }
353                            }
354    
355                            return new ArgumentList (
356                                    (String [])args.toArray (new String [args.size ()]),
357                                    bindex, argpos, cursor);
358                    }
359    
360    
361                    /**
362                     *  Returns true if the specified character is a whitespace
363                     *  parameter. Check to ensure that the character is not
364                     *  escaped by any of
365                     *  {@link #getQuoteChars}, and is not escaped by ant of the
366                     *  {@link #getEscapeChars}, and returns true from
367                     *  {@link #isDelimiterChar}.
368                     *
369                     *  @param  buffer      the complete command buffer
370                     *  @param  pos         the index of the character in the buffer
371                     *  @return                     true if the character should be a delimiter
372                     */
373                    public boolean isDelimiter (final String buffer, final int pos)
374                    {
375                            if (isQuoted (buffer, pos))
376                                    return false;
377                            if (isEscaped (buffer, pos))
378                                    return false;
379    
380                            return isDelimiterChar (buffer, pos);
381                    }
382    
383    
384                    public boolean isQuoted (final String buffer, final int pos)
385                    {
386                            return false;
387                    }
388    
389    
390                    public boolean isEscaped (final String buffer, final int pos)
391                    {
392                            if (pos <= 0)
393                                    return false;
394    
395                            for (int i = 0; escapeChars != null && i < escapeChars.length; i++)
396                            {
397                                    if (buffer.charAt (pos) == escapeChars [i])
398                                            return !isEscaped (buffer, pos - 1); // escape escape
399                            }
400    
401                            return false;
402                    }
403    
404    
405                    /**
406                     *  Returns true if the character at the specified position
407                     *  if a delimiter. This method will only be called if the
408                     *  character is not enclosed in any of the
409                     *  {@link #getQuoteChars}, and is not escaped by ant of the
410                     *  {@link #getEscapeChars}. To perform escaping manually,
411                     *  override {@link #isDelimiter} instead.
412                     */
413                    public abstract boolean isDelimiterChar (String buffer, int pos);
414            }
415    
416    
417            /**
418             *  {@link ArgumentCompletor.ArgumentDelimiter}
419             *  implementation that counts all
420             *  whitespace (as reported by {@link Character#isWhitespace})
421             *  as being a delimiter.
422             *
423             *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
424             */
425            public static class WhitespaceArgumentDelimiter
426                    extends AbstractArgumentDelimiter
427            {
428                    /**
429                     *  The character is a delimiter if it is whitespace, and the
430                     *  preceeding character is not an escape character.
431                     */
432                    public boolean isDelimiterChar (String buffer, int pos)
433                    {
434                            return Character.isWhitespace (buffer.charAt (pos));
435                    }
436            }
437    
438    
439            /**
440             *  The result of a delimited buffer.
441             *
442             *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
443             */
444            public static class ArgumentList
445            {
446                    private String [] arguments;
447                    private int cursorArgumentIndex;
448                    private int argumentPosition;
449                    private int bufferPosition;
450    
451                    /**
452                     *  @param  arguments                           the array of tokens
453                     *  @param  cursorArgumentIndex         the token index of the cursor
454                     *  @param  argumentPosition            the position of the cursor in the
455                     *                                                              current token
456                     *  @param  bufferPosition                      the position of the cursor in
457                     *                                                              the whole buffer
458                     */
459                    public ArgumentList (String [] arguments, int cursorArgumentIndex,
460                            int argumentPosition, int bufferPosition)
461                    {
462                            this.arguments = arguments;
463                            this.cursorArgumentIndex = cursorArgumentIndex;
464                            this.argumentPosition = argumentPosition;
465                            this.bufferPosition = bufferPosition;
466                    }
467    
468    
469                    public void setCursorArgumentIndex (int cursorArgumentIndex)
470                    {
471                            this.cursorArgumentIndex = cursorArgumentIndex;
472                    }
473    
474    
475                    public int getCursorArgumentIndex ()
476                    {
477                            return this.cursorArgumentIndex;
478                    }
479    
480    
481                    public String getCursorArgument ()
482                    {
483                            if (cursorArgumentIndex < 0
484                                    || cursorArgumentIndex >= arguments.length)
485                                    return null;
486    
487                            return arguments [cursorArgumentIndex];
488                    }
489    
490    
491                    public void setArgumentPosition (int argumentPosition)
492                    {
493                            this.argumentPosition = argumentPosition;
494                    }
495    
496    
497                    public int getArgumentPosition ()
498                    {
499                            return this.argumentPosition;
500                    }
501    
502    
503                    public void setArguments (String [] arguments)
504                    {
505                            this.arguments = arguments;
506                    }
507    
508    
509                    public String [] getArguments ()
510                    {
511                            return this.arguments;
512                    }
513    
514    
515                    public void setBufferPosition (int bufferPosition)
516                    {
517                            this.bufferPosition = bufferPosition;
518                    }
519    
520    
521                    public int getBufferPosition ()
522                    {
523                            return this.bufferPosition;
524                    }
525            }
526    }
527