001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.collections; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.ThrowableUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022 023import java.lang.reflect.*; 024import java.util.*; 025import java.util.concurrent.*; 026import java.util.function.*; 027 028/** 029 * A fluent builder for constructing {@link Map} instances with various configuration options. 030 * 031 * <p> 032 * This builder provides a flexible and type-safe way to construct maps with support for adding entries, 033 * other maps, sorting by keys, and applying modifiers like unmodifiable or sparse modes. It's particularly 034 * useful when constructing maps dynamically from multiple sources or with conditional entries. 035 * 036 * <p> 037 * Instances of this builder can be created using {@link #create(Class, Class)} or the convenience method 038 * {@link org.apache.juneau.commons.utils.CollectionUtils#mapb(Class, Class)}. 039 * 040 * <h5 class='section'>Features:</h5> 041 * <ul class='spaced-list'> 042 * <li>Fluent API - all methods return <c>this</c> for method chaining 043 * <li>Multiple add methods - single entries, pairs, other maps 044 * <li>Arbitrary input support - automatic type conversion with {@link #addAny(Object...)} 045 * <li>Pair adding - {@link #addPairs(Object...)} for varargs key-value pairs 046 * <li>Filtering support - exclude unwanted entries via {@link #filtered()} or {@link #filtered(BiPredicate)} 047 * <li>Sorting support - natural key order or custom {@link Comparator} 048 * <li>Sparse mode - return <jk>null</jk> for empty maps 049 * <li>Unmodifiable mode - create immutable maps 050 * <li>Custom conversion functions - type conversion via {@link #keyFunction(Function)} and {@link #valueFunction(Function)} 051 * </ul> 052 * 053 * <h5 class='section'>Examples:</h5> 054 * <p class='bjava'> 055 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 056 * 057 * <jc>// Basic usage - returns Map</jc> 058 * Map<String,Integer> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 059 * .add(<js>"one"</js>, 1) 060 * .add(<js>"two"</js>, 2) 061 * .add(<js>"three"</js>, 3) 062 * .build(); 063 * 064 * <jc>// Using pairs - returns Map</jc> 065 * Map<String,String> <jv>props</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, String.<jk>class</jk>) 066 * .addPairs(<js>"host"</js>, <js>"localhost"</js>, <js>"port"</js>, <js>"8080"</js>) 067 * .build(); 068 * 069 * <jc>// With sorting by key - returns Map</jc> 070 * Map<String,Integer> <jv>sorted</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 071 * .add(<js>"zebra"</js>, 3) 072 * .add(<js>"apple"</js>, 1) 073 * .add(<js>"banana"</js>, 2) 074 * .sorted() 075 * .build(); <jc>// Returns TreeMap with natural key order</jc> 076 * 077 * <jc>// Immutable map - returns Map</jc> 078 * Map<String,String> <jv>config</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, String.<jk>class</jk>) 079 * .add(<js>"env"</js>, <js>"prod"</js>) 080 * .add(<js>"region"</js>, <js>"us-west"</js>) 081 * .unmodifiable() 082 * .build(); 083 * 084 * <jc>// From multiple sources - returns Map</jc> 085 * Map<String,Integer> <jv>existing</jv> = Map.of(<js>"a"</js>, 1, <js>"b"</js>, 2); 086 * Map<String,Integer> <jv>combined</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 087 * .addAll(<jv>existing</jv>) 088 * .add(<js>"c"</js>, 3) 089 * .build(); 090 * 091 * <jc>// Sparse mode - returns null when empty</jc> 092 * Map<String,String> <jv>maybeNull</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, String.<jk>class</jk>) 093 * .sparse() 094 * .build(); <jc>// Returns null, not empty map</jc> 095 * 096 * <jc>// FluentMap wrapper - use buildFluent()</jc> 097 * FluentMap<String,Integer> <jv>fluent</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 098 * .add(<js>"one"</js>, 1) 099 * .buildFluent(); 100 * 101 * <jc>// FilteredMap - use buildFiltered()</jc> 102 * FilteredMap<String,Integer> <jv>filtered</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 103 * .filtered((k, v) -> v > 0) 104 * .add(<js>"a"</js>, 5) 105 * .add(<js>"b"</js>, -1) <jc>// Filtered out</jc> 106 * .buildFiltered(); 107 * </p> 108 * 109 * <h5 class='section'>Thread Safety:</h5> 110 * <p> 111 * This class is <b>not thread-safe</b>. Each builder instance should be used by a single thread or 112 * properly synchronized. 113 * 114 * <h5 class='section'>See Also:</h5><ul> 115 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsCollections">Collections Package</a> 116 * <li class='jc'>{@link Lists} 117 * <li class='jc'>{@link Sets} 118 * </ul> 119 * 120 * @param <K> The key type. 121 * @param <V> The value type. 122 */ 123public class Maps<K,V> { 124 125 /** 126 * Static creator. 127 * 128 * @param <K> Key type. 129 * @param <V> Value type. 130 * @param keyType The key type. Must not be <jk>null</jk>. 131 * @param valueType The value type. Must not be <jk>null</jk>. 132 * @return A new builder. 133 */ 134 public static <K,V> Maps<K,V> create(Class<K> keyType, Class<V> valueType) { 135 return new Maps<>(assertArgNotNull("keyType", keyType), assertArgNotNull("valueType", valueType)); 136 } 137 138 /** 139 * Static creator without explicit type parameters. 140 * 141 * <p> 142 * This is a convenience method that creates a builder without requiring explicit type parameters. 143 * The types will be inferred from usage context. Internally uses <c>Object.class</c> for both 144 * key and value types, which allows any types to be added. 145 * 146 * <h5 class='section'>Example:</h5> 147 * <p class='bjava'> 148 * Map<String, Integer> <jv>map</jv> = Maps.<jsm>create</jsm>() 149 * .add(<js>"one"</js>, 1) 150 * .add(<js>"two"</js>, 2) 151 * .build(); 152 * </p> 153 * 154 * @param <K> Key type. 155 * @param <V> Value type. 156 * @return A new builder. 157 */ 158 @SuppressWarnings({ "unchecked", "rawtypes" }) 159 public static <K,V> Maps<K,V> create() { 160 return new Maps(Object.class, Object.class); 161 } 162 163 private Map<K,V> map; 164 private boolean unmodifiable = false, sparse = false, concurrent = false, ordered = false; 165 private Comparator<K> comparator; 166 167 private BiPredicate<K,V> filter; 168 private Class<K> keyType; 169 private Class<V> valueType; 170 171 private Function<Object,K> keyFunction; 172 private Function<Object,V> valueFunction; 173 174 /** 175 * Constructor. 176 * 177 * @param keyType The key type. Must not be <jk>null</jk>. 178 * @param valueType The value type. Must not be <jk>null</jk>. 179 */ 180 public Maps(Class<K> keyType, Class<V> valueType) { 181 this.keyType = assertArgNotNull("keyType", keyType); 182 this.valueType = assertArgNotNull("valueType", valueType); 183 } 184 185 /** 186 * Adds a single entry to this map. 187 * 188 * <p> 189 * Note: Filtering is applied at build time, not when adding entries. 190 * 191 * @param key The map key. 192 * @param value The map value. 193 * @return This object. 194 */ 195 public Maps<K,V> add(K key, V value) { 196 if (map == null) 197 map = new LinkedHashMap<>(); 198 map.put(key, value); 199 return this; 200 } 201 202 /** 203 * Appends the contents of the specified map into this map. 204 * 205 * <p> 206 * This is a no-op if the value is <jk>null</jk>. 207 * 208 * <p> 209 * Note: Filtering is applied at build time, not when adding entries. 210 * 211 * @param value The map to add to this map. 212 * @return This object. 213 */ 214 public Maps<K,V> addAll(Map<K,V> value) { 215 if (nn(value)) { 216 if (map == null) 217 map = new LinkedHashMap<>(); 218 map.putAll(value); 219 } 220 return this; 221 } 222 223 /** 224 * Adds arbitrary values to this map. 225 * 226 * <p> 227 * Objects can be any of the following: 228 * <ul> 229 * <li>Maps of key/value types convertible to the key/value types of this map. 230 * </ul> 231 * 232 * <p> 233 * Each entry from the maps will be added using {@link #add(Object, Object)}, which applies 234 * key/value function conversion if configured. Non-Map objects will cause a {@link RuntimeException} to be thrown. 235 * 236 * @param values The values to add. Can contain <jk>null</jk> values (ignored). 237 * @return This object. 238 * @throws RuntimeException If a non-Map object is provided. 239 */ 240 @SuppressWarnings("unchecked") 241 public Maps<K,V> addAny(Object...values) { 242 for (var o : values) { 243 if (nn(o)) { 244 if (o instanceof Map o2) { 245 o2.forEach((k, v) -> { 246 K key = convertKey(k); 247 V value = convertValue(v); 248 add(key, value); 249 }); 250 } else { 251 throw rex("Object of type {0} could not be converted to type {1}", cn(o), "Map"); 252 } 253 } 254 } 255 return this; 256 } 257 258 /** 259 * Adds a list of key/value pairs to this map. 260 * 261 * @param pairs The pairs to add. 262 * @return This object. 263 */ 264 @SuppressWarnings("unchecked") 265 public Maps<K,V> addPairs(Object...pairs) { 266 assertArgNotNull("pairs", pairs); 267 if (pairs.length % 2 != 0) 268 throw illegalArg("Odd number of parameters passed into Maps.addPairs(...)"); 269 for (var i = 0; i < pairs.length; i += 2) 270 add((K)pairs[i], (V)pairs[i + 1]); 271 return this; 272 } 273 274 /** 275 * Builds the map. 276 * 277 * <p> 278 * Applies filtering, sorting, ordering, concurrent, unmodifiable, and sparse options. 279 * 280 * <p> 281 * Map type selection: 282 * <ul> 283 * <li>If {@link #sorted()} is set: Uses {@link TreeMap} (or {@link java.util.concurrent.ConcurrentSkipListMap} if concurrent) 284 * <li>If {@link #ordered()} is set: Uses {@link LinkedHashMap} (or synchronized LinkedHashMap if concurrent) 285 * <li>Otherwise: Uses {@link HashMap} (or {@link java.util.concurrent.ConcurrentHashMap} if concurrent) 286 * </ul> 287 * 288 * <p> 289 * If filtering is applied, the result is wrapped in a {@link FilteredMap}. 290 * 291 * @return The built map, or {@code null} if {@link #sparse()} is set and the map is empty. 292 */ 293 public Map<K,V> build() { 294 295 if (sparse && e(map)) 296 return null; 297 298 var map2 = (Map<K,V>)null; 299 300 if (ordered) { 301 map2 = new LinkedHashMap<>(); 302 if (concurrent) 303 map2 = Collections.synchronizedMap(map2); 304 } else if (nn(comparator)) { 305 map2 = concurrent ? new ConcurrentSkipListMap<>(comparator) : new TreeMap<>(comparator); 306 } else { 307 map2 = concurrent ? new ConcurrentHashMap<>() : new HashMap<>(); 308 } 309 310 if (nn(filter) || nn(keyFunction) || nn(valueFunction)) { 311 var map3b = FilteredMap.create(keyType, valueType); 312 if (nn(filter)) 313 map3b.filter(filter); 314 if (nn(keyFunction)) 315 map3b.keyFunction(keyFunction); 316 if (nn(valueFunction)) 317 map3b.valueFunction(valueFunction); 318 map2 = map3b.inner(map2).build(); 319 } 320 321 if (nn(map)) 322 map2.putAll(map); 323 324 if (unmodifiable) 325 map2 = Collections.unmodifiableMap(map2); 326 327 return map2; 328 } 329 330 /** 331 * Builds the map and wraps it in a {@link FluentMap}. 332 * 333 * <p> 334 * This is a convenience method that calls {@link #build()} and wraps the result in a {@link FluentMap}. 335 * 336 * <h5 class='section'>Example:</h5> 337 * <p class='bjava'> 338 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 339 * 340 * FluentMap<String,Integer> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 341 * .add(<js>"one"</js>, 1) 342 * .add(<js>"two"</js>, 2) 343 * .buildFluent(); 344 * </p> 345 * 346 * @return The built map wrapped in a {@link FluentMap}, or {@code null} if {@link #sparse()} is set and the map is empty. 347 */ 348 public FluentMap<K,V> buildFluent() { 349 Map<K,V> result = build(); 350 return result == null ? null : new FluentMap<>(result); 351 } 352 353 /** 354 * Builds the map as a {@link FilteredMap}. 355 * 356 * <p> 357 * Map type selection: 358 * <ul> 359 * <li>If {@link #sorted()} is set: Uses {@link TreeMap} (or {@link java.util.concurrent.ConcurrentSkipListMap} if concurrent) 360 * <li>If {@link #ordered()} is set: Uses {@link LinkedHashMap} (or synchronized LinkedHashMap if concurrent) 361 * <li>Otherwise: Uses {@link HashMap} (or {@link java.util.concurrent.ConcurrentHashMap} if concurrent) 362 * </ul> 363 * 364 * <h5 class='section'>Example:</h5> 365 * <p class='bjava'> 366 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 367 * 368 * FilteredMap<String,Integer> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 369 * .filtered((k, v) -> v != <jk>null</jk> && v > 0) 370 * .add(<js>"a"</js>, 5) 371 * .add(<js>"b"</js>, -1) <jc>// Will be filtered out</jc> 372 * .buildFiltered(); 373 * </p> 374 * 375 * <p> 376 * Note: If {@link #unmodifiable()} is set, the returned map will be wrapped in an unmodifiable view, 377 * which may cause issues if the FilteredMap tries to modify it internally. It's recommended to avoid 378 * using {@link #unmodifiable()} when calling this method. 379 * 380 * @return The built map as a {@link FilteredMap}, or {@code null} if {@link #sparse()} is set and the map is empty. 381 */ 382 public FilteredMap<K,V> buildFiltered() { 383 var m = build(); 384 if (m == null) // sparse mode and empty 385 return null; 386 if (m instanceof FilteredMap<K,V> m2) 387 return m2; 388 // Note that if unmodifiable is true, 'm' will be unmodifiable and will cause an error if you try 389 // to insert a value from within FilteredMap. 390 return FilteredMap.create(keyType, valueType).inner(m).build(); 391 } 392 393 /** 394 * Sets the key conversion function for converting keys in {@link #addAny(Object...)}. 395 * 396 * <p> 397 * The function is applied to each key when adding entries from maps in {@link #addAny(Object...)}. 398 * 399 * @param keyFunction The function to convert keys. Must not be <jk>null</jk>. 400 * @return This object. 401 */ 402 public Maps<K,V> keyFunction(Function<Object,K> keyFunction) { 403 this.keyFunction = assertArgNotNull("keyFunction", keyFunction); 404 return this; 405 } 406 407 /** 408 * Sets the value conversion function for converting values in {@link #addAny(Object...)}. 409 * 410 * <p> 411 * The function is applied to each value when adding entries from maps in {@link #addAny(Object...)}. 412 * 413 * @param valueFunction The function to convert values. Must not be <jk>null</jk>. 414 * @return This object. 415 */ 416 public Maps<K,V> valueFunction(Function<Object,V> valueFunction) { 417 this.valueFunction = assertArgNotNull("valueFunction", valueFunction); 418 return this; 419 } 420 421 /** 422 * Sets both key and value conversion functions. 423 * 424 * <p> 425 * Convenience method for setting both functions at once. 426 * 427 * @param keyFunction The function to convert keys. Must not be <jk>null</jk>. 428 * @param valueFunction The function to convert values. Must not be <jk>null</jk>. 429 * @return This object. 430 */ 431 public Maps<K,V> functions(Function<Object,K> keyFunction, Function<Object,V> valueFunction) { 432 this.keyFunction = assertArgNotNull("keyFunction", keyFunction); 433 this.valueFunction = assertArgNotNull("valueFunction", valueFunction); 434 return this; 435 } 436 437 /** 438 * Applies a default filter that excludes common "empty" or "unset" values from being added to the map. 439 * 440 * <p> 441 * The following values are filtered out: 442 * <ul> 443 * <li>{@code null} 444 * <li>{@link Boolean#FALSE} 445 * <li>Numbers with {@code intValue() == -1} 446 * <li>Empty arrays 447 * <li>Empty {@link Map Maps} 448 * <li>Empty {@link Collection Collections} 449 * </ul> 450 * 451 * <h5 class='section'>Example:</h5> 452 * <p class='bjava'> 453 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 454 * 455 * FluentMap<String,Object> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Object.<jk>class</jk>) 456 * .filtered() 457 * .add(<js>"name"</js>, <js>"John"</js>) 458 * .add(<js>"age"</js>, -1) <jc>// Filtered out at build time</jc> 459 * .add(<js>"enabled"</js>, <jk>false</jk>) <jc>// Filtered out at build time</jc> 460 * .add(<js>"tags"</js>, <jk>new</jk> String[0]) <jc>// Filtered out at build time</jc> 461 * .build(); 462 * </p> 463 * 464 * @return This object. 465 */ 466 public Maps<K,V> filtered() { 467 // @formatter:off 468 return filtered((k, v) -> ! ( 469 v == null 470 || (v instanceof Boolean v2 && v2.equals(false)) 471 || (v instanceof Number v3 && v3.intValue() == -1) 472 || (isArray(v) && Array.getLength(v) == 0) 473 || (v instanceof Map v2 && v2.isEmpty()) 474 || (v instanceof Collection v3 && v3.isEmpty()) 475 )); 476 // @formatter:on 477 } 478 479 /** 480 * Applies a filter predicate to entries when building the map. 481 * 482 * <p> 483 * The filter receives both the key and value of each entry. Entries where the predicate returns 484 * {@code true} will be kept; entries where it returns {@code false} will be filtered out. 485 * 486 * <p> 487 * This method can be called multiple times. When called multiple times, all filters are combined 488 * using AND logic - an entry must pass all filters to be kept in the map. 489 * 490 * <p> 491 * Note: Filtering is applied at build time, not when adding entries. 492 * 493 * <h5 class='section'>Example:</h5> 494 * <p class='bjava'> 495 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 496 * 497 * <jc>// Keep only non-null, non-empty string values</jc> 498 * Map<String,String> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, String.<jk>class</jk>) 499 * .filtered((k, v) -> v != <jk>null</jk> && !v.equals(<js>""</js>)) 500 * .add(<js>"a"</js>, <js>"foo"</js>) 501 * .add(<js>"b"</js>, <jk>null</jk>) <jc>// Filtered out at build time</jc> 502 * .add(<js>"c"</js>, <js>""</js>) <jc>// Filtered out at build time</jc> 503 * .build(); 504 * 505 * <jc>// Multiple filters combined with AND</jc> 506 * Map<String,Integer> <jv>map2</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 507 * .filtered((k, v) -> v != <jk>null</jk>) <jc>// First filter</jc> 508 * .filtered((k, v) -> v > 0) <jc>// Second filter (ANDed with first)</jc> 509 * .filtered((k, v) -> ! k.startsWith(<js>"_"</js>)) <jc>// Third filter (ANDed with previous)</jc> 510 * .add(<js>"a"</js>, 5) 511 * .add(<js>"_b"</js>, 10) <jc>// Filtered out (starts with "_")</jc> 512 * .add(<js>"c"</js>, -1) <jc>// Filtered out (not > 0)</jc> 513 * .build(); 514 * </p> 515 * 516 * @param filter The filter predicate. Must not be <jk>null</jk>. 517 * @return This object. 518 */ 519 public Maps<K,V> filtered(BiPredicate<K,V> filter) { 520 BiPredicate<K,V> newFilter = assertArgNotNull("filter", filter); 521 if (this.filter == null) 522 this.filter = newFilter; 523 else 524 this.filter = this.filter.and(newFilter); 525 return this; 526 } 527 528 /** 529 * Converts the set into a {@link SortedMap}. 530 * 531 * <p> 532 * Note: If {@link #ordered()} was previously called, calling this method will override it. 533 * The last method called ({@link #ordered()} or {@link #sorted()}) determines the final map type. 534 * 535 * @return This object. 536 */ 537 @SuppressWarnings("unchecked") 538 public Maps<K,V> sorted() { 539 return sorted((Comparator<K>)Comparator.naturalOrder()); 540 } 541 542 /** 543 * Converts the set into a {@link SortedMap} using the specified comparator. 544 * 545 * <p> 546 * Note: If {@link #ordered()} was previously called, calling this method will override it. 547 * The last method called ({@link #ordered()} or {@link #sorted()}) determines the final map type. 548 * 549 * @param comparator The comparator to use for sorting. Must not be <jk>null</jk>. 550 * @return This object. 551 */ 552 public Maps<K,V> sorted(Comparator<K> comparator) { 553 this.comparator = assertArgNotNull("comparator", comparator); 554 ordered = false; 555 return this; 556 } 557 558 /** 559 * When specified, the {@link #build()} method will return <jk>null</jk> if the map is empty. 560 * 561 * <p> 562 * Otherwise {@link #build()} will never return <jk>null</jk>. 563 * 564 * @return This object. 565 */ 566 public Maps<K,V> sparse() { 567 sparse = true; 568 return this; 569 } 570 571 /** 572 * When specified, {@link #build()} will return an unmodifiable map. 573 * 574 * @return This object. 575 */ 576 public Maps<K,V> unmodifiable() { 577 unmodifiable = true; 578 return this; 579 } 580 581 /** 582 * When specified, {@link #build()} will return a thread-safe map. 583 * 584 * <p> 585 * The thread-safety implementation depends on other settings: 586 * <ul> 587 * <li>If {@link #sorted()} is set: Uses {@link java.util.concurrent.ConcurrentSkipListMap} 588 * <li>If {@link #ordered()} is set: Uses {@link java.util.Collections#synchronizedMap(java.util.Map)} 589 * <li>Otherwise: Uses {@link java.util.concurrent.ConcurrentHashMap} 590 * </ul> 591 * 592 * <p> 593 * This is useful when the map needs to be accessed from multiple threads. 594 * 595 * <h5 class='section'>Example:</h5> 596 * <p class='bjava'> 597 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 598 * 599 * <jc>// Create a thread-safe map using ConcurrentHashMap</jc> 600 * Map<String,Integer> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 601 * .add(<js>"one"</js>, 1) 602 * .add(<js>"two"</js>, 2) 603 * .concurrent() 604 * .build(); 605 * 606 * <jc>// Create a thread-safe ordered map</jc> 607 * Map<String,Integer> <jv>map2</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 608 * .ordered() 609 * .concurrent() 610 * .add(<js>"one"</js>, 1) 611 * .build(); 612 * </p> 613 * 614 * @return This object. 615 */ 616 public Maps<K,V> concurrent() { 617 concurrent = true; 618 return this; 619 } 620 621 /** 622 * Sets whether {@link #build()} should return a thread-safe map. 623 * 624 * <p> 625 * The thread-safety implementation depends on other settings: 626 * <ul> 627 * <li>If {@link #sorted()} is set: Uses {@link java.util.concurrent.ConcurrentSkipListMap} 628 * <li>If {@link #ordered()} is set: Uses {@link java.util.Collections#synchronizedMap(java.util.Map)} 629 * <li>Otherwise: Uses {@link java.util.concurrent.ConcurrentHashMap} 630 * </ul> 631 * 632 * <p> 633 * This is useful when the map needs to be accessed from multiple threads. 634 * 635 * <h5 class='section'>Example:</h5> 636 * <p class='bjava'> 637 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 638 * 639 * <jc>// Conditionally create a thread-safe map</jc> 640 * Map<String,Integer> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 641 * .add(<js>"one"</js>, 1) 642 * .concurrent(<jv>needsThreadSafety</jv>) 643 * .build(); 644 * </p> 645 * 646 * @param value Whether to make the map thread-safe. 647 * @return This object. 648 */ 649 public Maps<K,V> concurrent(boolean value) { 650 concurrent = value; 651 return this; 652 } 653 654 /** 655 * When specified, {@link #build()} will use a {@link LinkedHashMap} to preserve insertion order. 656 * 657 * <p> 658 * If not specified, a {@link HashMap} is used by default (no guaranteed order). 659 * 660 * <p> 661 * Note: If {@link #sorted()} was previously called, calling this method will override it. 662 * The last method called ({@link #ordered()} or {@link #sorted()}) determines the final map type. 663 * 664 * <h5 class='section'>Example:</h5> 665 * <p class='bjava'> 666 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 667 * 668 * <jc>// Create an ordered map (preserves insertion order)</jc> 669 * Map<String,Integer> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 670 * .ordered() 671 * .add(<js>"one"</js>, 1) 672 * .add(<js>"two"</js>, 2) 673 * .build(); 674 * </p> 675 * 676 * @return This object. 677 */ 678 public Maps<K,V> ordered() { 679 return ordered(true); 680 } 681 682 /** 683 * Sets whether {@link #build()} should use a {@link LinkedHashMap} to preserve insertion order. 684 * 685 * <p> 686 * If <c>false</c> (default), a {@link HashMap} is used (no guaranteed order). 687 * If <c>true</c>, a {@link LinkedHashMap} is used (preserves insertion order). 688 * 689 * <p> 690 * Note: If {@link #sorted()} was previously called, calling this method with <c>true</c> will override it. 691 * The last method called ({@link #ordered()} or {@link #sorted()}) determines the final map type. 692 * 693 * <h5 class='section'>Example:</h5> 694 * <p class='bjava'> 695 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 696 * 697 * <jc>// Conditionally create an ordered map</jc> 698 * Map<String,Integer> <jv>map</jv> = <jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>) 699 * .ordered(<jv>preserveOrder</jv>) 700 * .add(<js>"one"</js>, 1) 701 * .build(); 702 * </p> 703 * 704 * @param value Whether to preserve insertion order. 705 * @return This object. 706 */ 707 public Maps<K,V> ordered(boolean value) { 708 ordered = value; 709 if (ordered) 710 comparator = null; 711 return this; 712 } 713 714 /** 715 * Converts a key object to the key type. 716 * 717 * @param o The object to convert. 718 * @return The converted key. 719 */ 720 @SuppressWarnings("unchecked") 721 private K convertKey(Object o) { 722 if (keyType.isInstance(o)) 723 return (K)o; 724 if (nn(keyFunction)) 725 return keyFunction.apply(o); 726 throw rex("Object of type {0} could not be converted to key type {1}", cn(o), cn(keyType)); 727 } 728 729 /** 730 * Converts a value object to the value type. 731 * 732 * @param o The object to convert. 733 * @return The converted value. 734 */ 735 @SuppressWarnings("unchecked") 736 private V convertValue(Object o) { 737 if (valueType.isInstance(o)) 738 return (V)o; 739 if (nn(valueFunction)) 740 return valueFunction.apply(o); 741 throw rex("Object of type {0} could not be converted to value type {1}", cn(o), cn(valueType)); 742 } 743}