001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    
020    package org.crsh.cli.spi;
021    
022    import java.io.Serializable;
023    import java.util.Collections;
024    import java.util.Iterator;
025    import java.util.LinkedHashMap;
026    import java.util.Map;
027    import java.util.Set;
028    
029    /**
030     * <p>An immutable object representing the complation of a value. A completion is described by:</p>
031     *
032     * <ol>
033     *   <li>A prefix: an optional value that is relevant when more than a result is provided. The prefix value must be a
034     *   suffix of the completion prefix, it is used to shorten the completed prefix in order to make the completions
035     *   values shorter. For instance a path completion returning several values could be displayed in columns, however only
036     *   the last name of the path would be displayed and not the full path.</li>
037     *   <li>A list of <code>Map.Entry&lt;String, Boolean&gt;</code>  map where the key is string value of the completion
038     *   and the boolean value tells whether the value is a suffix (i.e it ends the value) or not (i.e it can be further
039     *   more completed).</li>
040     * </ol>
041     *
042     * <p>The following guidelines should be respected:</p>
043     * <ul>
044     *   <li>An empty completion means no completion can be determined.</li>
045     *   <li>A singleton map means the match was entire and completion will happen with the unique entry.</li>
046     *   <li>A map containing a list of string values sharing a common prefix indicates to use this common prefix.</li>
047     *   <li>A list containing strings with no common prefix (other than the empty string) instruct to display the list of
048     *   possible completions. In that case the completion result prefix is used to preped the returned suffixes when
049     *   displayed in rows.</li>
050     *   <li>When a match is considered as entire (the entry value is set to true), the completion should contain a
051     *   trailing value that is usually a white space (but it could be a quote for quoted values).</li>
052     * </ul>
053     *
054     * <p>Example: a completer that would complete colors could</p>
055     * <ul>
056     *   <li>return the result ["lack ":true,"lue ":true] with the prefix "b" for the prefix "b".</li>
057     *   <li>return the result ["e ":true] with the prefix "blu" for the prefix "blu".</li>
058     *   <li>return the result [] for the prefix "z".</li>
059     * </ul>
060     *
061     * <p>Example: a completer that would complete java packages could</p>
062     * <ul>
063     *   <li>return the map ["ext":true,"ext.spi":false] for the prefix "java.t"</li>
064     * </ul>
065     */
066    public final class Completion implements Iterable<Map.Entry<String, Boolean>>, Serializable {
067    
068      /** . */
069      private static final Completion EMPTY = create("");
070    
071      public static Builder builder(String prefix) {
072        return new Builder(prefix);
073      }
074    
075      public static Completion create() {
076        return EMPTY;
077      }
078    
079      public static Completion create(String prefix) {
080        return create(prefix, Collections.<String, Boolean>emptyMap());
081      }
082    
083      public static Completion create(String prefix, String suffix, boolean value) {
084        return create(prefix, Collections.singletonMap(suffix, value));
085      }
086    
087      public static Completion create(String suffix, boolean value) {
088        return create("", suffix, value);
089      }
090    
091      public static Completion create(String prefix, Map<String, Boolean> suffixes) {
092        return new Completion(prefix, suffixes);
093      }
094    
095      /** . */
096      private final String prefix;
097    
098      /** . */
099      private final Map<String, Boolean> values;
100    
101      private Completion(String prefix, Map<String, Boolean> values) {
102        if (prefix == null) {
103          throw new NullPointerException("No null prefix allowed");
104        }
105        if (values == null) {
106          throw new NullPointerException("No null suffixes allowed");
107        }
108    
109        //
110        this.prefix = prefix;
111        this.values = values;
112      }
113    
114      public Iterator<Map.Entry<String, Boolean>> iterator() {
115        return values.entrySet().iterator();
116      }
117    
118      public Set<String> getValues() {
119        return values.keySet();
120      }
121    
122      public boolean isEmpty() {
123        return values.isEmpty();
124      }
125    
126      public Boolean get(String key) {
127        return values.get(key);
128      }
129    
130      public int getSize() {
131        return values.size();
132      }
133    
134      public String getPrefix() {
135        return prefix;
136      }
137    
138      @Override
139      public int hashCode() {
140        return prefix.hashCode() ^ values.hashCode();
141      }
142    
143      @Override
144      public boolean equals(Object obj) {
145        if (obj == this) {
146          return true;
147        }
148        if (obj instanceof Completion) {
149          Completion that = (Completion)obj;
150          return prefix.equals(that.prefix) && values.equals(that.values);
151        }
152        return false;
153      }
154    
155      @Override
156      public String toString() {
157        return "Completion[prefix=" + prefix + ",entries=" + values + "]";
158      }
159    
160      public static class Builder {
161    
162        /** . */
163        private String prefix;
164    
165        /** . */
166        private Map<String, Boolean> entries;
167    
168        public Builder(String prefix) {
169          this.prefix = prefix;
170          this.entries = null;
171        }
172    
173        public Builder add(String key, boolean value) {
174          if (entries == null) {
175            entries = new LinkedHashMap<String, Boolean>();
176          }
177          entries.put(key, value);
178          return this;
179        }
180    
181        public Completion build() {
182          return create(prefix,  entries != null ? entries : Collections.<String, Boolean>emptyMap());
183        }
184      }
185    }