All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aspectran.utils.LinkedCaseInsensitiveMap Maven / Gradle / Ivy

There is a newer version: 8.1.5
Show newest version
/*
 * Copyright (c) 2008-2024 The Aspectran Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.aspectran.utils;

import com.aspectran.utils.annotation.jsr305.NonNull;
import com.aspectran.utils.annotation.jsr305.Nullable;

import java.io.Serial;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 

This class is a clone of org.springframework.util.LinkedCaseInsensitiveMap

* * {@link LinkedHashMap} variant that stores String keys in a case-insensitive * manner, for example for key-based access in a results table. * *

Preserves the original order as well as the original casing of keys, * while allowing for contains, get and remove calls with any case of key. * *

Does not support {@code null} keys. * * @author Juergen Hoeller * @author Phillip Webb * @param the value type */ public class LinkedCaseInsensitiveMap implements Map, Serializable, Cloneable { @Serial private static final long serialVersionUID = 4431694618621892446L; private final LinkedHashMap targetMap; private final HashMap caseInsensitiveKeys; private final Locale locale; @Nullable private transient volatile Set keySet; @Nullable private transient volatile Collection values; @Nullable private transient volatile Set> entrySet; /** * Create a new LinkedCaseInsensitiveMap that stores case-insensitive keys * according to the default Locale (by default in lower case). * * @see #convertKey(String) */ public LinkedCaseInsensitiveMap() { this((Locale)null); } /** * Create a new LinkedCaseInsensitiveMap that stores case-insensitive keys * according to the given Locale (by default in lower case). * @param locale the Locale to use for case-insensitive key conversion * @see #convertKey(String) */ public LinkedCaseInsensitiveMap(@Nullable Locale locale) { this(16, locale); } /** * Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap} * with the given initial capacity and stores case-insensitive keys * according to the default Locale (by default in lower case). * @param initialCapacity the initial capacity * @see #convertKey(String) */ public LinkedCaseInsensitiveMap(int initialCapacity) { this(initialCapacity, null); } /** * Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap} * with the given initial capacity and stores case-insensitive keys * according to the given Locale (by default in lower case). * @param initialCapacity the initial capacity * @param locale the Locale to use for case-insensitive key conversion * @see #convertKey(String) */ public LinkedCaseInsensitiveMap(int initialCapacity, @Nullable Locale locale) { this.targetMap = new LinkedHashMap(initialCapacity) { @Override public boolean containsKey(Object key) { return LinkedCaseInsensitiveMap.this.containsKey(key); } @Override protected boolean removeEldestEntry(Map.Entry eldest) { boolean doRemove = LinkedCaseInsensitiveMap.this.removeEldestEntry(eldest); if (doRemove) { removeCaseInsensitiveKey(eldest.getKey()); } return doRemove; } }; this.caseInsensitiveKeys = new HashMap<>(initialCapacity); this.locale = (locale != null ? locale : Locale.getDefault()); } /** * Copy constructor. */ @SuppressWarnings("unchecked") private LinkedCaseInsensitiveMap(@NonNull LinkedCaseInsensitiveMap other) { this.targetMap = (LinkedHashMap) other.targetMap.clone(); this.caseInsensitiveKeys = (HashMap) other.caseInsensitiveKeys.clone(); this.locale = other.locale; } // Implementation of java.util.Map @Override public int size() { return this.targetMap.size(); } @Override public boolean isEmpty() { return this.targetMap.isEmpty(); } @Override public boolean containsKey(Object key) { return (key instanceof String str && this.caseInsensitiveKeys.containsKey(convertKey(str))); } @Override public boolean containsValue(Object value) { return this.targetMap.containsValue(value); } @Override @Nullable public V get(Object key) { if (key instanceof String str) { String caseInsensitiveKey = this.caseInsensitiveKeys.get(convertKey(str)); if (caseInsensitiveKey != null) { return this.targetMap.get(caseInsensitiveKey); } } return null; } @Override @Nullable public V getOrDefault(Object key, V defaultValue) { if (key instanceof String str) { String caseInsensitiveKey = this.caseInsensitiveKeys.get(convertKey(str)); if (caseInsensitiveKey != null) { return this.targetMap.get(caseInsensitiveKey); } } return defaultValue; } @Override @Nullable public V put(String key, @Nullable V value) { String oldKey = this.caseInsensitiveKeys.put(convertKey(key), key); V oldKeyValue = null; if (oldKey != null && !oldKey.equals(key)) { oldKeyValue = this.targetMap.remove(oldKey); } V oldValue = this.targetMap.put(key, value); return (oldKeyValue != null ? oldKeyValue : oldValue); } @Override public void putAll(@NonNull Map map) { if (map.isEmpty()) { return; } map.forEach(this::put); } @Override @Nullable public V putIfAbsent(String key, @Nullable V value) { String oldKey = this.caseInsensitiveKeys.putIfAbsent(convertKey(key), key); if (oldKey != null) { V oldKeyValue = this.targetMap.get(oldKey); if (oldKeyValue != null) { return oldKeyValue; } else { key = oldKey; } } return this.targetMap.putIfAbsent(key, value); } @Override @Nullable public V computeIfAbsent(String key, @NonNull Function mappingFunction) { String oldKey = this.caseInsensitiveKeys.putIfAbsent(convertKey(key), key); if (oldKey != null) { V oldKeyValue = this.targetMap.get(oldKey); if (oldKeyValue != null) { return oldKeyValue; } else { key = oldKey; } } return this.targetMap.computeIfAbsent(key, mappingFunction); } @Override @Nullable public V remove(Object key) { if (key instanceof String str) { String caseInsensitiveKey = removeCaseInsensitiveKey(str); if (caseInsensitiveKey != null) { return this.targetMap.remove(caseInsensitiveKey); } } return null; } @Override public void clear() { this.caseInsensitiveKeys.clear(); this.targetMap.clear(); } @Override @NonNull public Set keySet() { Set keySet = this.keySet; if (keySet == null) { keySet = new KeySet(this.targetMap.keySet()); this.keySet = keySet; } return keySet; } @Override @NonNull public Collection values() { Collection values = this.values; if (values == null) { values = new Values(this.targetMap.values()); this.values = values; } return values; } @Override @NonNull public Set> entrySet() { Set> entrySet = this.entrySet; if (entrySet == null) { entrySet = new EntrySet(this.targetMap.entrySet()); this.entrySet = entrySet; } return entrySet; } @Override public void forEach(BiConsumer action) { this.targetMap.forEach(action); } @Override @SuppressWarnings("MethodDoesntCallSuperMethod") public LinkedCaseInsensitiveMap clone() { return new LinkedCaseInsensitiveMap<>(this); } @Override @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") public boolean equals(@Nullable Object other) { return (this == other || this.targetMap.equals(other)); } @Override public int hashCode() { return this.targetMap.hashCode(); } @Override public String toString() { return this.targetMap.toString(); } // Specific to LinkedCaseInsensitiveMap /** * Return the locale used by this {@code LinkedCaseInsensitiveMap}. * Used for case-insensitive key conversion. * * @see #LinkedCaseInsensitiveMap(Locale) * @see #convertKey(String) */ public Locale getLocale() { return this.locale; } /** * Convert the given key to a case-insensitive key. *

The default implementation converts the key * to lower-case according to this Map's Locale. * @param key the user-specified key * @return the key to use for storing * @see String#toLowerCase(Locale) */ protected String convertKey(@NonNull String key) { return key.toLowerCase(getLocale()); } /** * Determine whether this map should remove the given eldest entry. * @param eldest the candidate entry * @return {@code true} for removing it, {@code false} for keeping it */ protected boolean removeEldestEntry(Map.Entry eldest) { return false; } @Nullable private String removeCaseInsensitiveKey(String key) { return this.caseInsensitiveKeys.remove(convertKey(key)); } private class KeySet extends AbstractSet { private final Set delegate; KeySet(Set delegate) { this.delegate = delegate; } @Override public int size() { return this.delegate.size(); } @Override public boolean contains(Object o) { return this.delegate.contains(o); } @Override @NonNull public Iterator iterator() { return new KeySetIterator(); } @Override public boolean remove(Object o) { return LinkedCaseInsensitiveMap.this.remove(o) != null; } @Override public void clear() { LinkedCaseInsensitiveMap.this.clear(); } @Override public Spliterator spliterator() { return this.delegate.spliterator(); } @Override public void forEach(Consumer action) { this.delegate.forEach(action); } } private class Values extends AbstractCollection { private final Collection delegate; Values(Collection delegate) { this.delegate = delegate; } @Override public int size() { return this.delegate.size(); } @Override public boolean contains(Object o) { return this.delegate.contains(o); } @Override @NonNull public Iterator iterator() { return new ValuesIterator(); } @Override public void clear() { LinkedCaseInsensitiveMap.this.clear(); } @Override public Spliterator spliterator() { return this.delegate.spliterator(); } @Override public void forEach(Consumer action) { this.delegate.forEach(action); } } private class EntrySet extends AbstractSet> { private final Set> delegate; public EntrySet(Set> delegate) { this.delegate = delegate; } @Override public int size() { return this.delegate.size(); } @Override public boolean contains(Object o) { return this.delegate.contains(o); } @Override @NonNull public Iterator> iterator() { return new EntrySetIterator(); } @Override @SuppressWarnings("unchecked") public boolean remove(Object o) { if (this.delegate.remove(o)) { removeCaseInsensitiveKey(((Map.Entry) o).getKey()); return true; } return false; } @Override public void clear() { this.delegate.clear(); caseInsensitiveKeys.clear(); } @Override public Spliterator> spliterator() { return this.delegate.spliterator(); } @Override public void forEach(Consumer> action) { this.delegate.forEach(action); } } private abstract class EntryIterator implements Iterator { private final Iterator> delegate; @Nullable private Entry last; public EntryIterator() { this.delegate = targetMap.entrySet().iterator(); } protected Entry nextEntry() { Entry entry = this.delegate.next(); this.last = entry; return entry; } @Override public boolean hasNext() { return this.delegate.hasNext(); } @Override public void remove() { this.delegate.remove(); if (this.last != null) { removeCaseInsensitiveKey(this.last.getKey()); this.last = null; } } } private class KeySetIterator extends EntryIterator { @Override public String next() { return nextEntry().getKey(); } } private class ValuesIterator extends EntryIterator { @Override public V next() { return nextEntry().getValue(); } } private class EntrySetIterator extends EntryIterator> { @Override public Entry next() { return nextEntry(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy