1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.collection.MultiMap; 13 14 import hunt.collection.ArrayList; 15 import hunt.collection.HashMap; 16 import hunt.collection.List; 17 import hunt.collection.Map; 18 19 import hunt.text; 20 21 /** 22 * A multi valued Map. 23 * 24 * @param !V 25 * the entry type for multimap values 26 */ 27 class MultiMap(V) : HashMap!(string, List!(V)) { 28 29 // TODO: Tasks pending completion -@zxp at 10/15/2018, 6:24:35 PM 30 // replace this with hunt.collection.MultiValueMap 31 32 this() { 33 super(); 34 } 35 36 this(Map!(string, List!(V)) map) { 37 super(map); 38 } 39 40 this(MultiMap!V map) { 41 super(map); 42 } 43 44 /** 45 * Get multiple values. Single valued entries are converted to singleton 46 * lists. 47 * 48 * @param name 49 * The entry key. 50 * @return Unmodifieable List of values. 51 */ 52 List!(V) getValues(string name) { 53 List!(V) vals = super.get(name); 54 if ((vals is null) || vals.isEmpty()) { 55 return null; 56 } 57 return vals; 58 } 59 60 /** 61 * Get a value from a multiple value. If the value is not a multivalue, then 62 * index 0 retrieves the value or null. 63 * 64 * @param name 65 * The entry key. 66 * @param i 67 * Index of element to get. 68 * @return Unmodifieable List of values. 69 */ 70 V getValue(string name, int i=0) { 71 List!(V) vals = getValues(name); 72 if (vals is null) { 73 return V.init; 74 } 75 if (i == 0 && vals.isEmpty()) { 76 return V.init; 77 } 78 return vals.get(i); 79 } 80 81 /** 82 * Get value as string. Single valued items are converted to a string with 83 * the toString() Object method. Multi valued entries are converted to a 84 * comma separated List. No quoting of commas within values is performed. 85 * 86 * @param name 87 * The entry key. 88 * @return string value. 89 */ 90 string getString(string name) { 91 List!(V) vals = get(name); 92 if ((vals is null) || (vals.isEmpty())) { 93 return null; 94 } 95 96 if (vals.size() == 1) { 97 // simple form. 98 static if(is(V == string)) 99 return vals.get(0); 100 else 101 return vals.get(0).toString(); 102 } 103 104 // delimited form 105 StringBuilder values = new StringBuilder(128); 106 foreach (V e ; vals) { 107 if (e !is null) { 108 if (values.length() > 0) 109 values.append(','); 110 static if(is(V == string)) 111 values.append(e); 112 else 113 values.append(e.toString()); 114 } 115 } 116 return values.toString(); 117 } 118 119 /** 120 * Put multi valued entry. 121 * 122 * @param name 123 * The entry key. 124 * @param value 125 * The simple value 126 * @return The previous value or null. 127 */ 128 List!(V) put(string name, V value) { 129 if (value is null) { 130 return super.put(name, null); 131 } 132 List!(V) vals = new ArrayList!(V)(); 133 vals.add(value); 134 return put(name, vals); 135 } 136 137 alias put = HashMap!(string, List!(V)).put; 138 139 /** 140 * Shorthand version of putAll 141 * 142 * @param input 143 * the input map 144 */ 145 void putAllValues(Map!(string, V) input) { 146 foreach (string key, V value ; input) { 147 put(key, value); 148 } 149 } 150 151 /** 152 * Put multi valued entry. 153 * 154 * @param name 155 * The entry key. 156 * @param values 157 * The List of multiple values. 158 * @return The previous value or null. 159 */ 160 List!(V) putValues(string name, List!(V) values) { 161 return super.put(name, values); 162 } 163 164 /** 165 * Put multi valued entry. 166 * 167 * @param name 168 * The entry key. 169 * @param values 170 * The array of multiple values. 171 * @return The previous value or null. 172 */ 173 final List!(V) putValues(string name, V[] values...) { 174 List!(V) list = new ArrayList!(V)(); 175 // list.addAll(Arrays.asList(values)); 176 foreach(V v; values) 177 list.add(v); 178 return super.put(name, list); 179 } 180 181 /** 182 * Add value to multi valued entry. If the entry is single valued, it is 183 * converted to the first value of a multi valued entry. 184 * 185 * @param name 186 * The entry key. 187 * @param value 188 * The entry value. 189 */ 190 void add(string name, V value) { 191 List!(V) lo = get(name); 192 if (lo is null) { 193 lo = new ArrayList!(V)(); 194 } 195 lo.add(value); 196 super.put(name, lo); 197 } 198 199 /** 200 * Add values to multi valued entry. If the entry is single valued, it is 201 * converted to the first value of a multi valued entry. 202 * 203 * @param name 204 * The entry key. 205 * @param values 206 * The List of multiple values. 207 */ 208 void addValues(string name, List!(V) values) { 209 List!(V) lo = get(name); 210 if (lo is null) { 211 lo = new ArrayList!(V)(); 212 } 213 lo.addAll(values); 214 put(name, lo); 215 } 216 217 /** 218 * Add values to multi valued entry. If the entry is single valued, it is 219 * converted to the first value of a multi valued entry. 220 * 221 * @param name 222 * The entry key. 223 * @param values 224 * The string array of multiple values. 225 */ 226 void addValues(string name, V[] values) { 227 List!(V) lo = get(name); 228 if (lo is null) { 229 lo = new ArrayList!(V)(); 230 } 231 // lo.addAll(Arrays.asList(values)); 232 foreach(V v; values) 233 lo.add(v); 234 put(name, lo); 235 } 236 237 /** 238 * Merge values. 239 * 240 * @param map 241 * the map to overlay on top of this one, merging together values 242 * if needed. 243 * @return true if an existing key was merged with potentially new values, 244 * false if either no change was made, or there were only new keys. 245 */ 246 // bool addAllValues(MultiMap!V map) { 247 // bool merged = false; 248 249 // if ((map is null) || (map.isEmpty())) { 250 // // done 251 // return merged; 252 // } 253 254 // for (MapEntry!(string, List!(V)) entry : map.entrySet()) { 255 // string name = entry.getKey(); 256 // List!(V) values = entry.getValue(); 257 258 // if (this.containsKey(name)) { 259 // merged = true; 260 // } 261 262 // this.addValues(name, values); 263 // } 264 265 // return merged; 266 // } 267 268 /** 269 * Remove value. 270 * 271 * @param name 272 * The entry key. 273 * @param value 274 * The entry value. 275 * @return true if it was removed. 276 */ 277 bool removeValue(string name, V value) { 278 List!(V) lo = get(name); 279 if ((lo is null) || (lo.isEmpty())) { 280 return false; 281 } 282 bool ret = lo.remove(value); 283 if (lo.isEmpty()) { 284 remove(name); 285 } else { 286 put(name, lo); 287 } 288 return ret; 289 } 290 291 /** 292 * Test for a specific single value in the map. 293 * <p> 294 * NOTE: This is a SLOW operation, and is actively discouraged. 295 * 296 * @param value 297 * the value to search for 298 * @return true if contains simple value 299 */ 300 bool containsSimpleValue(V value) { 301 foreach (List!(V) vals ; byValue()) { 302 if ((vals.size() == 1) && vals.contains(value)) { 303 return true; 304 } 305 } 306 return false; 307 } 308 309 override 310 string toString() { 311 StringBuilder sb = new StringBuilder(); 312 sb.append('{'); 313 bool delim = false; 314 import std.conv; 315 foreach(string key, List!(V) vals; this) { 316 if (delim) { 317 sb.append(", "); 318 } 319 sb.append(key); 320 sb.append('='); 321 if (vals.size() == 1) { 322 sb.append(vals.get(0).to!string()); 323 } else { 324 sb.append(vals.toString()); 325 } 326 delim = true; 327 } 328 sb.append('}'); 329 return sb.toString(); 330 } 331 332 /** 333 * @return Map of string arrays 334 */ 335 // Map!(string, string[]) toStringArrayMap() { 336 // int s = size(); 337 // HashMap!(string, string[]) map = new (s * 3 / 2) class HashMap!(string, string[]) { 338 339 // // private enum long serialVersionUID = -6129887569971781626L; 340 // this(int initialCapacity) { 341 // super(initialCapacity) 342 // } 343 344 // override 345 // string toString() { 346 // StringBuilder b = new StringBuilder(); 347 // b.append('{'); 348 // for (string k : super.keySet()) { 349 // if (b.length() > 1) 350 // b.append(','); 351 // b.append(k); 352 // b.append('='); 353 // b.append(Arrays.asList(super.get(k))); 354 // } 355 356 // b.append('}'); 357 // return b.toString(); 358 // } 359 // }; 360 361 // foreach (string key, List!(V) vals; this) { 362 // string[] a = null; 363 // if (vals !is null) { 364 // a = new string[vals.size()]; 365 // a = vals.toArray(a); 366 // } 367 // map.put(key, a); 368 // } 369 // return map; 370 // } 371 372 }