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 */
007package jline;
008
009import java.io.*;
010import 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 */
027public 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}