001    /**
002     *      jline - Java console input library
003     *      Copyright (c) 2002,2003 Marc Prud'hommeaux mwp1@cornell.edu
004     *      
005     *      This library is free software; you can redistribute it and/or
006     *      modify it under the terms of the GNU Lesser General Public
007     *      License as published by the Free Software Foundation; either
008     *      version 2.1 of the License, or (at your option) any later version.
009     *      
010     *      This library is distributed in the hope that it will be useful,
011     *      but WITHOUT ANY WARRANTY; without even the implied warranty of
012     *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     *      Lesser General Public License for more details.
014     *      
015     *      You should have received a copy of the GNU Lesser General Public
016     *      License along with this library; if not, write to the Free Software
017     *      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018     */
019    package jline;
020    
021    import java.io.*;
022    import java.util.*;
023    import java.text.MessageFormat;
024    
025    /**
026     *      <p>
027     *      A {@link CompletionHandler} that deals with multiple distinct completions
028     *      by outputting the complete list of possibilities to the console. This
029     *      mimics the behavior of the
030     *      <a href="http://www.gnu.org/directory/readline.html">readline</a>
031     *      library.
032     *      </p>
033     *
034     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
035     */
036    public class CandidateListCompletionHandler
037            implements CompletionHandler
038    {
039            private static ResourceBundle loc = ResourceBundle.getBundle (
040                    CandidateListCompletionHandler.class.getName ());
041    
042    
043            public boolean complete (final ConsoleReader reader,
044                    final List candidates, final int pos)
045                    throws IOException
046            {
047                    CursorBuffer buf = reader.getCursorBuffer ();
048    
049                    // if there is only one completion, then fill in the buffer
050                    if (candidates.size () == 1)
051                    {
052                            String value = candidates.get (0).toString ();
053    
054                            // fail if the only candidate is the same as the current buffer
055                            if (value.equals (buf.toString ()))
056                                    return false;
057                            setBuffer (reader, value, pos);
058                            return true;
059                    }
060                    else if (candidates.size () > 1)
061                    {
062                            String value = getUnambiguousCompletions (candidates);
063                            String bufString = buf.toString ();
064                            setBuffer (reader, value, pos);
065    
066                            // if we have changed the buffer, then just return withough
067                            // printing out all the subsequent candidates
068                            if (bufString.length () - pos + 1 != value.length ())
069                                    return true;
070                    }
071    
072                    reader.printNewline ();
073                    printCandidates (reader, candidates);
074    
075                    // redraw the current console buffer
076                    reader.drawLine ();
077    
078                    return true;
079            }
080    
081    
082            private static void setBuffer (ConsoleReader reader,
083                    String value, int offset)
084                    throws IOException
085            {
086                    while (reader.getCursorBuffer ().cursor >= offset
087                            && reader.backspace ());
088                    reader.putString (value);
089                    reader.setCursorPosition (offset + value.length ());
090            }
091    
092    
093            /**
094             *  Print out the candidates. If the size of the candidates
095             *  is greated than the {@link getAutoprintThreshhold},
096             *  they prompt with aq warning.
097             *
098             *  @param  candidates  the list of candidates to print
099             */
100            private final void printCandidates (ConsoleReader reader,
101                    Collection candidates)
102                    throws IOException
103            {
104                    Set distinct = new HashSet (candidates);
105    
106                    if (distinct.size () > reader.getAutoprintThreshhold ())
107                    {
108                            reader.printString (MessageFormat.format (
109                                    loc.getString ("display-candidates"),
110                                    new Object [] { new Integer (candidates.size ()) } ) + " ");
111    
112                            reader.flushConsole ();
113    
114                            int c;
115                            
116                            String noOpt = loc.getString ("display-candidates-no");
117                            String yesOpt = loc.getString ("display-candidates-yes");
118    
119                            while ((c = reader.readCharacter (
120                                    new char[] { yesOpt.charAt (0), noOpt.charAt (0) })) != -1)
121                            {
122                                    if (noOpt.startsWith (new String (new char[] {(char)c})))
123                                    {
124                                            reader.printNewline ();
125                                            return;
126                                    }
127                                    else if (yesOpt.startsWith (new String (new char[] {(char)c})))
128                                    {
129                                            break;
130                                    }
131                                    else
132                                    {
133                                            reader.beep ();
134                                    }
135                            }
136                    }
137    
138                    // copy the values and make them distinct, without otherwise
139                    // affecting the ordering. Only do it if the sizes differ.
140                    if (distinct.size () != candidates.size ())
141                    {
142                            Collection copy = new ArrayList ();
143                            for (Iterator i = candidates.iterator (); i.hasNext (); )
144                            {
145                                    Object next = i.next ();
146                                    if (!(copy.contains (next)))
147                                            copy.add (next);
148                            }
149    
150                            candidates = copy;
151                    }
152    
153                    reader.printNewline ();
154                    reader.printColumns (candidates);
155            }
156    
157    
158    
159    
160            /**
161             *  Returns a root that matches all the {@link String} elements
162             *  of the specified {@link List}, or null if there are
163             *  no commalities. For example, if the list contains
164             *  <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
165             *  method will return <i>foob</i>.
166             */
167            private final String getUnambiguousCompletions (final List candidates)
168            {
169                    if (candidates == null || candidates.size () == 0)
170                            return null;
171    
172                    // convert to an array for speed
173                    String [] strings = (String [])candidates.toArray (
174                            new String [candidates.size ()]);
175    
176                    String first = strings [0];
177                    StringBuffer candidate = new StringBuffer ();
178                    for (int i = 0; i < first.length (); i++)
179                    {
180                            if (startsWith (first.substring (0, i + 1), strings))
181                                    candidate.append (first.charAt (i));
182                            else
183                                    break;
184                    }
185    
186                    return candidate.toString ();
187            }
188    
189    
190            /**
191             *  @return  true is all the elements of <i>candidates</i>
192             *                      start with <i>starts</i>
193             */
194            private final boolean startsWith (final String starts,
195                    final String [] candidates)
196            {
197                    for (int i = 0; i < candidates.length; i++)
198                    {
199                            if (!candidates [i].startsWith (starts))
200                                    return false;
201                    }
202    
203                    return true;
204            }
205    }
206