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