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