1 /*
2  * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 /*
27  * This file is available under and governed by the GNU General Public
28  * License version 2 only, as published by the Free Software Foundation.
29  * However, the following notice accompanied the original version of this
30  * file:
31  *
32  * The MIT License
33  *
34  * Copyright (c) 2004-2015 Paul R. Holser, Jr.
35  *
36  * Permission is hereby granted, free of charge, to any person obtaining
37  * a copy of this software and associated documentation files (the
38  * "Software"), to deal in the Software without restriction, including
39  * without limitation the rights to use, copy, modify, merge, publish,
40  * distribute, sublicense, and/or sell copies of the Software, and to
41  * permit persons to whom the Software is furnished to do so, subject to
42  * the following conditions:
43  *
44  * The above copyright notice and this permission notice shall be
45  * included in all copies or substantial portions of the Software.
46  *
47  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
48  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
49  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
50  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
51  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
52  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
53  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
54  */
55 
56 package jdk.internal.joptsimple.internal;
57 
58 import java.util.Map;
59 import java.util.TreeMap;
60 
61 /**
62  * <p>A map whose keys are strings; when a key/value pair is added to the map, the longest unique abbreviations of that
63  * key are added as well, and associated with the value. Thus:</p>
64  *
65  * <pre>
66  *   <code>
67  *   abbreviations.put( "good", "bye" );
68  *   </code>
69  * </pre>
70  *
71  * <p>would make it such that you could retrieve the value {@code "bye"} from the map using the keys {@code "good"},
72  * {@code "goo"}, {@code "go"}, and {@code "g"}. A subsequent invocation of:</p>
73  * <pre>
74  *   <code>
75  *   abbreviations.put( "go", "fish" );
76  *   </code>
77  * </pre>
78  *
79  * <p>would make it such that you could retrieve the value {@code "bye"} using the keys {@code "good"} and
80  * {@code "goo"}, and the value {@code "fish"} using the key {@code "go"}.  The key {@code "g"} would yield
81  * {@code null}, since it would no longer be a unique abbreviation.</p>
82  *
83  * <p>The data structure is much like a "trie".</p>
84  *
85  * @param <V> a constraint on the types of the values in the map
86  * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
87  * @see <a href="http://perldoc.perl.org/Text/Abbrev.html">Perl's Text::Abbrev module</a>
88  * @see <a href="https://en.wikipedia.org/wiki/Radix_tree">Radix tree</a>
89  */
90 public class AbbreviationMap<V> implements OptionNameMap<V> {
91     private final Map<Character, AbbreviationMap<V>> children = new TreeMap<>();
92 
93     private String key;
94     private V value;
95     private int keysBeyond;
96 
97     /**
98      * <p>Tells whether the given key is in the map, or whether the given key is a unique
99      * abbreviation of a key that is in the map.</p>
100      *
101      * @param key key to look up
102      * @return {@code true} if {@code key} is present in the map
103      * @throws NullPointerException if {@code key} is {@code null}
104      */
105     @Override
contains(String key)106     public boolean contains(String key) {
107         return get(key) != null;
108     }
109 
110     /**
111      * <p>Answers the value associated with the given key.  The key can be a unique
112      * abbreviation of a key that is in the map. </p>
113      *
114      * @param key key to look up
115      * @return the value associated with {@code aKey}; or {@code null} if there is no
116      * such value or {@code aKey} is not a unique abbreviation of a key in the map
117      * @throws NullPointerException if {@code aKey} is {@code null}
118      */
119     @Override
get( String key )120     public V get( String key ) {
121         char[] chars = charsOf( key );
122 
123         AbbreviationMap<V> child = this;
124         for ( char each : chars ) {
125             child = child.children.get( each );
126             if ( child == null )
127                 return null;
128         }
129 
130         return child.value;
131     }
132 
133     /**
134      * <p>Associates a given value with a given key.  If there was a previous
135      * association, the old value is replaced with the new one.</p>
136      *
137      * @param key key to create in the map
138      * @param newValue value to associate with the key
139      * @throws NullPointerException if {@code aKey} or {@code newValue} is {@code null}
140      * @throws IllegalArgumentException if {@code aKey} is a zero-length string
141      */
142     @Override
put( String key, V newValue )143     public void put( String key, V newValue ) {
144         if ( newValue == null )
145             throw new NullPointerException();
146         if ( key.length() == 0 )
147             throw new IllegalArgumentException();
148 
149         char[] chars = charsOf(key);
150         add( chars, newValue, 0, chars.length );
151     }
152 
153     /**
154      * <p>Associates a given value with a given set of keys.  If there was a previous
155      * association, the old value is replaced with the new one.</p>
156      *
157      * @param keys keys to create in the map
158      * @param newValue value to associate with the key
159      * @throws NullPointerException if {@code keys} or {@code newValue} is {@code null}
160      * @throws IllegalArgumentException if any of {@code keys} is a zero-length string
161      */
162     @Override
putAll( Iterable<String> keys, V newValue )163     public void putAll( Iterable<String> keys, V newValue ) {
164         for ( String each : keys )
165             put( each, newValue );
166     }
167 
add( char[] chars, V newValue, int offset, int length )168     private boolean add( char[] chars, V newValue, int offset, int length ) {
169         if ( offset == length ) {
170             value = newValue;
171             boolean wasAlreadyAKey = key != null;
172             key = new String( chars );
173             return !wasAlreadyAKey;
174         }
175 
176         char nextChar = chars[ offset ];
177         AbbreviationMap<V> child = children.get( nextChar );
178         if ( child == null ) {
179             child = new AbbreviationMap<>();
180             children.put( nextChar, child );
181         }
182 
183         boolean newKeyAdded = child.add( chars, newValue, offset + 1, length );
184 
185         if ( newKeyAdded )
186             ++keysBeyond;
187 
188         if ( key == null )
189             value = keysBeyond > 1 ? null : newValue;
190 
191         return newKeyAdded;
192     }
193 
194     /**
195      * <p>If the map contains the given key, dissociates the key from its value.</p>
196      *
197      * @param key key to remove
198      * @throws NullPointerException if {@code aKey} is {@code null}
199      * @throws IllegalArgumentException if {@code aKey} is a zero-length string
200      */
201     @Override
remove( String key )202     public void remove( String key ) {
203         if ( key.length() == 0 )
204             throw new IllegalArgumentException();
205 
206         char[] keyChars = charsOf(key);
207         remove( keyChars, 0, keyChars.length );
208     }
209 
remove( char[] aKey, int offset, int length )210     private boolean remove( char[] aKey, int offset, int length ) {
211         if ( offset == length )
212             return removeAtEndOfKey();
213 
214         char nextChar = aKey[ offset ];
215         AbbreviationMap<V> child = children.get( nextChar );
216         if ( child == null || !child.remove( aKey, offset + 1, length ) )
217             return false;
218 
219         --keysBeyond;
220         if ( child.keysBeyond == 0 )
221             children.remove( nextChar );
222         if ( keysBeyond == 1 && key == null )
223             setValueToThatOfOnlyChild();
224 
225         return true;
226     }
227 
setValueToThatOfOnlyChild()228     private void setValueToThatOfOnlyChild() {
229         Map.Entry<Character, AbbreviationMap<V>> entry = children.entrySet().iterator().next();
230         AbbreviationMap<V> onlyChild = entry.getValue();
231         value = onlyChild.value;
232     }
233 
removeAtEndOfKey()234     private boolean removeAtEndOfKey() {
235         if ( key == null )
236             return false;
237 
238         key = null;
239         if ( keysBeyond == 1 )
240             setValueToThatOfOnlyChild();
241         else
242             value = null;
243 
244         return true;
245     }
246 
247     /**
248      * Gives a Java map representation of this abbreviation map.
249      *
250      * @return a Java map corresponding to this abbreviation map
251      */
252     @Override
toJavaUtilMap()253     public Map<String, V> toJavaUtilMap() {
254         Map<String, V> mappings = new TreeMap<>();
255         addToMappings( mappings );
256         return mappings;
257     }
258 
addToMappings( Map<String, V> mappings )259     private void addToMappings( Map<String, V> mappings ) {
260         if ( key != null )
261             mappings.put( key, value );
262 
263         for ( AbbreviationMap<V> each : children.values() )
264             each.addToMappings( mappings );
265     }
266 
charsOf( String aKey )267     private static char[] charsOf( String aKey ) {
268         char[] chars = new char[ aKey.length() ];
269         aKey.getChars( 0, aKey.length(), chars, 0 );
270         return chars;
271     }
272 }
273