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