    001    /**
    002     *      jline - Java console input library
    003     *      Copyright (c) 2002, 2003, 2004, 2005, 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.io.*;
    039    import java.util.*;
    040
    041
    042    /**
    043     *      <p>
    044     *  A simple {@link Completor} implementation that handles a pre-defined
    045     *  list of completion words.
    046     *  </p>
    047     *
    048     *      <p>
    049     *  Example usage:
    050     *  </p>
    051     *  <pre>
    052     *  myConsoleReader.addCompletor (new SimpleCompletor (new String [] { "now", "yesterday", "tomorrow" }));
    053     *  </pre>
    054     *
    055     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
    056     */
    057    public class SimpleCompletor
    058            implements Completor, Cloneable
    059    {
    060            /**
    061             *  The list of candidates that will be completed.
    062             */
    063            SortedSet candidates;
    064
    065
    066            /**
    067             *  A delimiter to use to qualify completions.
    068             */
    069            String delimiter;
    070
    071            final SimpleCompletorFilter filter;
    072
    073
    074            /**
    075             *  Create a new SimpleCompletor with a single possible completion
    076             *  values.
    077             */
    078            public SimpleCompletor (final String candidateString)
    079            {
    080                    this (new String [] { candidateString });
    081            }
    082
    083
    084            /**
    085             *  Create a new SimpleCompletor with a list of possible completion
    086             *  values.
    087             */
    088            public SimpleCompletor (final String [] candidateStrings)
    089            {
    090                    this (candidateStrings, null);
    091            }
    092
    093
    094            public SimpleCompletor (final String[] strings,
    095                    final SimpleCompletorFilter filter)
    096            {
    097                    this.filter = filter;
    098                    setCandidateStrings (strings);
    099            }
    100
    101
    102            /**
    103             *  Complete candidates using the contents of the specified Reader.
    104             */
    105            public SimpleCompletor (final Reader reader)
    106                    throws IOException
    107            {
    108                    this (getStrings (reader));
    109            }
    110
    111
    112            /**
    113             *  Complete candidates using the whitespearated values in
    114             *  read from the specified Reader.
    115             */
    116            public SimpleCompletor (final InputStream in)
    117                    throws IOException
    118            {
    119                    this (getStrings (new InputStreamReader (in)));
    120            }
    121
    122
    123            private static String [] getStrings (final Reader in)
    124                    throws IOException
    125            {
    126                    final Reader reader = in instanceof BufferedReader
    127                            ? in
    128                            : new BufferedReader (in);
    129
    130                    List words = new LinkedList ();
    131                    String line;
    132                    while ((line = ((BufferedReader)reader).readLine ()) != null)
    133                    {
    134                            for (StringTokenizer tok = new StringTokenizer (line);
    135                                    tok.hasMoreTokens (); words.add (tok.nextToken ()));
    136                    }
    137
    138                    return (String [])words.toArray (new String [words.size ()]);
    139            }
    140
    141
    142            public int complete (final String buffer, final int cursor,
    143                    final List clist)
    144            {
    145                    String start = buffer == null ? "" : buffer;
    146
    147                    SortedSet matches = candidates.tailSet (start);
    148                    for (Iterator i = matches.iterator (); i.hasNext (); )
    149                    {
    150                            String can = (String)i.next ();
    151                            if (!(can.startsWith (start)))
    152                                    break;
    153
    154                            if (delimiter != null)
    155                            {
    156                                    int index = can.indexOf (delimiter, cursor);
    157                                    if (index != -1)
    158                                            can = can.substring (0, index + 1);
    159                            }
    160                            clist.add (can);
    161                    }
    162
    163                    if (clist.size () == 1)
    164                            clist.set (0, ((String)clist.get (0)) + " ");
    165
    166                    // the index of the completion is always from the beginning of
    167                    // the buffer.
    168                    return clist.size () == 0 ? -1 : 0;
    169            }
    170
    171
    172            public void setDelimiter (final String delimiter)
    173            {
    174                    this.delimiter = delimiter;
    175            }
    176
    177
    178            public String getDelimiter ()
    179            {
    180                    return this.delimiter;
    181            }
    182
    183
    184
    185            public void setCandidates (final SortedSet candidates)
    186            {
    187                    if (filter != null)
    188                    {
    189                            TreeSet filtered = new TreeSet ();
    190                            for (Iterator i = candidates.iterator (); i.hasNext (); )
    191                            {
    192                                    String element = (String)i.next ();
    193                                    element = filter.filter (element);
    194                                    if (element != null)
    195                                            filtered.add (element);
    196                            }
    197
    198                            this.candidates = filtered;
    199                    }
    200                    else
    201                    {
    202                            this.candidates = candidates;
    203                    }
    204            }
    205
    206
    207            public SortedSet getCandidates ()
    208            {
    209                    return Collections.unmodifiableSortedSet (this.candidates);
    210            }
    211
    212
    213            public void setCandidateStrings (final String[] strings)
    214            {
    215                    setCandidates (new TreeSet (Arrays.asList (strings)));
    216            }
    217
    218
    219            public void addCandidateString (final String candidateString)
    220            {
    221                    final String string = filter == null
    222                            ? candidateString
    223                            : filter.filter (candidateString);
    224
    225                    if (string != null)
    226                            candidates.add (string);
    227            }
    228
    229
    230            public Object clone ()
    231                    throws CloneNotSupportedException
    232            {
    233                    return super.clone ();
    234            }
    235
    236
    237            /**
    238             *  Filter for elements in the completor.
    239             *
    240             *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
    241             */
    242            public static interface SimpleCompletorFilter
    243            {
    244                    /**
    245                     *  Filter the specified String. To not filter it, return the
    246                     *  same String as the parameter. To exclude it, return null.
    247                     */
    248                    public String filter (String element);
    249            }
    250
    251
    252            public static class NoOpFilter
    253                    implements SimpleCompletorFilter
    254            {
    255                    public String filter (final String element)
    256                    {
    257                            return element;
    258                    }
    259            }
    260    }