KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > jline > ArgumentCompletor


1 /**
2  * jline - Java console input library
3  * Copyright (c) 2002-2006, Marc Prud'hommeaux mwp1@cornell.edu
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or
7  * without modification, are permitted provided that the following
8  * conditions are met:
9  *
10  * Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer
15  * in the documentation and/or other materials provided with
16  * the distribution.
17  *
18  * Neither the name of JLine nor the names of its contributors
19  * may be used to endorse or promote products derived from this
20  * software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
24  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
26  * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
28  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
33  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34  * OF THE POSSIBILITY OF SUCH DAMAGE.
35  */

36 package jline;
37
38 import java.util.*;
39
40
41 /**
42  * A {@link Completor} implementation that invokes a child completor
43  * using the appropriate <i>separator</i> argument. This
44  * can be used instead of the individual completors having to
45  * know about argument parsing semantics.
46  * <p>
47  * <strong>Example 1</strong>: Any argument of the command line can
48  * use file completion.
49  * <p>
50  * <pre>
51  * consoleReader.addCompletor (new ArgumentCompletor (
52  * new {@link FileNameCompletor} ()))
53  * </pre>
54  * <p>
55  * <strong>Example 2</strong>: The first argument of the command line
56  * can be completed with any of "foo", "bar", or "baz", and remaining
57  * arguments can be completed with a file name.
58  * <p>
59  * <pre>
60  * consoleReader.addCompletor (new ArgumentCompletor (
61  * new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"})));
62  * consoleReader.addCompletor (new ArgumentCompletor (
63  * new {@link FileNameCompletor} ()));
64  * </pre>
65  *
66  * <p>
67  * When the argument index is past the last embedded completors, the last
68  * completors is always used. To disable this behavior, have the last
69  * completor be a {@link NullCompletor}. For example:
70  * </p>
71  *
72  * <pre>
73  * consoleReader.addCompletor (new ArgumentCompletor (
74  * new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"}),
75  * new {@link SimpleCompletor} (new String [] { "xxx", "yyy", "xxx"}),
76  * new {@link NullCompletor}
77  * ));
78  * </pre>
79  * <p>
80  * TODO: handle argument quoting and escape characters
81  * </p>
82  *
83  * @author <a HREF="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
84  */

85 public class ArgumentCompletor
86     implements Completor
87 {
88     final Completor [] completors;
89     final ArgumentDelimiter delim;
90     boolean strict = true;
91
92
93     /**
94      * Constuctor: create a new completor with the default
95      * argument separator of " ".
96      *
97      * @param completor the embedded completor
98      */

99     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 JavaDoc 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 JavaDoc [] args = list.getArguments ();
204             String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc buffer, final int cursor)
323         {
324             List args = new LinkedList ();
325             StringBuffer JavaDoc arg = new StringBuffer JavaDoc ();
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 JavaDoc [])args.toArray (new String JavaDoc [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 JavaDoc 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 JavaDoc buffer, final int pos)
385         {
386             return false;
387         }
388
389
390         public boolean isEscaped (final String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc [] 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 JavaDoc [] 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 JavaDoc 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 JavaDoc [] arguments)
504         {
505             this.arguments = arguments;
506         }
507
508
509         public String JavaDoc [] 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
528
Popular Tags