001    /*
002     * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
003     *
004     * This software is distributable under the BSD license. See the terms of the
005     * BSD license in the documentation provided with this software.
006     */
007    package jline;
008    
009    import java.io.*;
010    import java.util.*;
011    
012    /**
013     *  <p>
014     *  A simple {@link Completor} implementation that handles a pre-defined
015     *  list of completion words.
016     *  </p>
017     *
018     *  <p>
019     *  Example usage:
020     *  </p>
021     *  <pre>
022     *  myConsoleReader.addCompletor (new SimpleCompletor (new String [] { "now", "yesterday", "tomorrow" }));
023     *  </pre>
024     *
025     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
026     */
027    public class SimpleCompletor implements Completor, Cloneable {
028        /**
029         *  The list of candidates that will be completed.
030         */
031        SortedSet candidates;
032    
033        /**
034         *  A delimiter to use to qualify completions.
035         */
036        String delimiter;
037        final SimpleCompletorFilter filter;
038    
039        /**
040         *  Create a new SimpleCompletor with a single possible completion
041         *  values.
042         */
043        public SimpleCompletor(final String candidateString) {
044            this(new String[] {
045                     candidateString
046                 });
047        }
048    
049        /**
050         *  Create a new SimpleCompletor with a list of possible completion
051         *  values.
052         */
053        public SimpleCompletor(final String[] candidateStrings) {
054            this(candidateStrings, null);
055        }
056    
057        public SimpleCompletor(final String[] strings,
058                               final SimpleCompletorFilter filter) {
059            this.filter = filter;
060            setCandidateStrings(strings);
061        }
062    
063        /**
064         *  Complete candidates using the contents of the specified Reader.
065         */
066        public SimpleCompletor(final Reader reader) throws IOException {
067            this(getStrings(reader));
068        }
069    
070        /**
071         *  Complete candidates using the whitespearated values in
072         *  read from the specified Reader.
073         */
074        public SimpleCompletor(final InputStream in) throws IOException {
075            this(getStrings(new InputStreamReader(in)));
076        }
077    
078        private static String[] getStrings(final Reader in)
079                                    throws IOException {
080            final Reader reader =
081                (in instanceof BufferedReader) ? in : new BufferedReader(in);
082    
083            List words = new LinkedList();
084            String line;
085    
086            while ((line = ((BufferedReader) reader).readLine()) != null) {
087                for (StringTokenizer tok = new StringTokenizer(line);
088                         tok.hasMoreTokens(); words.add(tok.nextToken())) {
089                    ;
090                }
091            }
092    
093            return (String[]) words.toArray(new String[words.size()]);
094        }
095    
096        public int complete(final String buffer, final int cursor, final List clist) {
097            String start = (buffer == null) ? "" : buffer;
098    
099            SortedSet matches = candidates.tailSet(start);
100    
101            for (Iterator i = matches.iterator(); i.hasNext();) {
102                String can = (String) i.next();
103    
104                if (!(can.startsWith(start))) {
105                    break;
106                }
107    
108                if (delimiter != null) {
109                    int index = can.indexOf(delimiter, cursor);
110    
111                    if (index != -1) {
112                        can = can.substring(0, index + 1);
113                    }
114                }
115    
116                clist.add(can);
117            }
118    
119            if (clist.size() == 1) {
120                clist.set(0, ((String) clist.get(0)) + " ");
121            }
122    
123            // the index of the completion is always from the beginning of
124            // the buffer.
125            return (clist.size() == 0) ? (-1) : 0;
126        }
127    
128        public void setDelimiter(final String delimiter) {
129            this.delimiter = delimiter;
130        }
131    
132        public String getDelimiter() {
133            return this.delimiter;
134        }
135    
136        public void setCandidates(final SortedSet candidates) {
137            if (filter != null) {
138                TreeSet filtered = new TreeSet();
139    
140                for (Iterator i = candidates.iterator(); i.hasNext();) {
141                    String element = (String) i.next();
142                    element = filter.filter(element);
143    
144                    if (element != null) {
145                        filtered.add(element);
146                    }
147                }
148    
149                this.candidates = filtered;
150            } else {
151                this.candidates = candidates;
152            }
153        }
154    
155        public SortedSet getCandidates() {
156            return Collections.unmodifiableSortedSet(this.candidates);
157        }
158    
159        public void setCandidateStrings(final String[] strings) {
160            setCandidates(new TreeSet(Arrays.asList(strings)));
161        }
162    
163        public void addCandidateString(final String candidateString) {
164            final String string =
165                (filter == null) ? candidateString : filter.filter(candidateString);
166    
167            if (string != null) {
168                candidates.add(string);
169            }
170        }
171    
172        public Object clone() throws CloneNotSupportedException {
173            return super.clone();
174        }
175    
176        /**
177         *  Filter for elements in the completor.
178         *
179         *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
180         */
181        public static interface SimpleCompletorFilter {
182            /**
183             *  Filter the specified String. To not filter it, return the
184             *  same String as the parameter. To exclude it, return null.
185             */
186            public String filter(String element);
187        }
188    
189        public static class NoOpFilter implements SimpleCompletorFilter {
190            public String filter(final String element) {
191                return element;
192            }
193        }
194    }