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.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