    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    import java.text.MessageFormat;
    041
    042    /**
    043     *      <p>
    044     *      A {@link CompletionHandler} that deals with multiple distinct completions
    045     *      by outputting the complete list of possibilities to the console. This
    046     *      mimics the behavior of the
    047     *      <a href="http://www.gnu.org/directory/readline.html">readline</a>
    048     *      library.
    049     *      </p>
    050     *
    051     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
    052     */
    053    public class CandidateListCompletionHandler
    054            implements CompletionHandler
    055    {
    056            private static ResourceBundle loc = ResourceBundle.getBundle (
    057                    CandidateListCompletionHandler.class.getName ());
    058
    059
    060            public boolean complete (final ConsoleReader reader,
    061                    final List candidates, final int pos)
    062                    throws IOException
    063            {
    064                    CursorBuffer buf = reader.getCursorBuffer ();
    065
    066                    // if there is only one completion, then fill in the buffer
    067                    if (candidates.size () == 1)
    068                    {
    069                            String value = candidates.get (0).toString ();
    070
    071                            // fail if the only candidate is the same as the current buffer
    072                            if (value.equals (buf.toString ()))
    073                                    return false;
    074                            setBuffer (reader, value, pos);
    075                            return true;
    076                    }
    077                    else if (candidates.size () > 1)
    078                    {
    079                            String value = getUnambiguousCompletions (candidates);
    080                            String bufString = buf.toString ();
    081                            setBuffer (reader, value, pos);
    082
    083                            // if we have changed the buffer, then just return withough
    084                            // printing out all the subsequent candidates
    085                            if (bufString.length () - pos + 1 != value.length ())
    086                                    return true;
    087                    }
    088
    089                    reader.printNewline ();
    090                    printCandidates (reader, candidates);
    091
    092                    // redraw the current console buffer
    093                    reader.drawLine ();
    094
    095                    return true;
    096            }
    097
    098
    099            private static void setBuffer (ConsoleReader reader,
    100                    String value, int offset)
    101                    throws IOException
    102            {
    103                    while (reader.getCursorBuffer ().cursor >= offset
    104                            && reader.backspace ());
    105                    reader.putString (value);
    106                    reader.setCursorPosition (offset + value.length ());
    107            }
    108
    109
    110            /**
    111             *  Print out the candidates. If the size of the candidates
    112             *  is greated than the {@link getAutoprintThreshhold},
    113             *  they prompt with aq warning.
    114             *
    115             *  @param  candidates  the list of candidates to print
    116             */
    117            private final void printCandidates (ConsoleReader reader,
    118                    Collection candidates)
    119                    throws IOException
    120            {
    121                    Set distinct = new HashSet (candidates);
    122
    123                    if (distinct.size () > reader.getAutoprintThreshhold ())
    124                    {
    125                            reader.printString (MessageFormat.format (
    126                                    loc.getString ("display-candidates"),
    127                                    new Object [] { new Integer (candidates.size ()) } ) + " ");
    128
    129                            reader.flushConsole ();
    130
    131                            int c;
    132
    133                            String noOpt = loc.getString ("display-candidates-no");
    134                            String yesOpt = loc.getString ("display-candidates-yes");
    135
    136                            while ((c = reader.readCharacter (
    137                                    new char[] { yesOpt.charAt (0), noOpt.charAt (0) })) != -1)
    138                            {
    139                                    if (noOpt.startsWith (new String (new char[] {(char)c})))
    140                                    {
    141                                            reader.printNewline ();
    142                                            return;
    143                                    }
    144                                    else if (yesOpt.startsWith (new String (new char[] {(char)c})))
    145                                    {
    146                                            break;
    147                                    }
    148                                    else
    149                                    {
    150                                            reader.beep ();
    151                                    }
    152                            }
    153                    }
    154
    155                    // copy the values and make them distinct, without otherwise
    156                    // affecting the ordering. Only do it if the sizes differ.
    157                    if (distinct.size () != candidates.size ())
    158                    {
    159                            Collection copy = new ArrayList ();
    160                            for (Iterator i = candidates.iterator (); i.hasNext (); )
    161                            {
    162                                    Object next = i.next ();
    163                                    if (!(copy.contains (next)))
    164                                            copy.add (next);
    165                            }
    166
    167                            candidates = copy;
    168                    }
    169
    170                    reader.printNewline ();
    171                    reader.printColumns (candidates);
    172            }
    173
    174
    175
    176
    177            /**
    178             *  Returns a root that matches all the {@link String} elements
    179             *  of the specified {@link List}, or null if there are
    180             *  no commalities. For example, if the list contains
    181             *  <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
    182             *  method will return <i>foob</i>.
    183             */
    184            private final String getUnambiguousCompletions (final List candidates)
    185            {
    186                    if (candidates == null || candidates.size () == 0)
    187                            return null;
    188
    189                    // convert to an array for speed
    190                    String [] strings = (String [])candidates.toArray (
    191                            new String [candidates.size ()]);
    192
    193                    String first = strings [0];
    194                    StringBuffer candidate = new StringBuffer ();
    195                    for (int i = 0; i < first.length (); i++)
    196                    {
    197                            if (startsWith (first.substring (0, i + 1), strings))
    198                                    candidate.append (first.charAt (i));
    199                            else
    200                                    break;
    201                    }
    202
    203                    return candidate.toString ();
    204            }
    205
    206
    207            /**
    208             *  @return  true is all the elements of <i>candidates</i>
    209             *                      start with <i>starts</i>
    210             */
    211            private final boolean startsWith (final String starts,
    212                    final String [] candidates)
    213            {
    214                    for (int i = 0; i < candidates.length; i++)
    215                    {
    216                            if (!candidates [i].startsWith (starts))
    217                                    return false;
    218                    }
    219
    220                    return true;
    221            }
    222    }
    223