/* * Copyright 2002-2023 the original author or authors. * * 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 * * https://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 org.springframework.util; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Spliterator; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.springframework.lang.Nullable; /** * Unmodifiable wrapper for {@link MultiValueMap}. * * @author Arjen Poutsma * @since 6.0 * @param the key type * @param the value element type */ final class UnmodifiableMultiValueMap implements MultiValueMap, Serializable { private static final long serialVersionUID = -8697084563854098920L; private final MultiValueMap delegate; @Nullable private transient Set keySet; @Nullable private transient Set>> entrySet; @Nullable private transient Collection> values; @SuppressWarnings("unchecked") public UnmodifiableMultiValueMap(MultiValueMap delegate) { Assert.notNull(delegate, "Delegate must not be null"); this.delegate = (MultiValueMap) delegate; } // delegation @Override public int size() { return this.delegate.size(); } @Override public boolean isEmpty() { return this.delegate.isEmpty(); } @Override public boolean containsKey(Object key) { return this.delegate.containsKey(key); } @Override public boolean containsValue(Object value) { return this.delegate.containsValue(value); } @Override @Nullable public List get(Object key) { List result = this.delegate.get(key); return (result != null ? Collections.unmodifiableList(result) : null); } @Override public V getFirst(K key) { return this.delegate.getFirst(key); } @Override public List getOrDefault(Object key, List defaultValue) { List result = this.delegate.getOrDefault(key, defaultValue); if (result != defaultValue) { result = Collections.unmodifiableList(result); } return result; } @Override public void forEach(BiConsumer> action) { this.delegate.forEach((k, vs) -> action.accept(k, Collections.unmodifiableList(vs))); } @Override public Map toSingleValueMap() { return this.delegate.toSingleValueMap(); } @Override public boolean equals(@Nullable Object other) { return (this == other || this.delegate.equals(other)); } @Override public int hashCode() { return this.delegate.hashCode(); } @Override public String toString() { return this.delegate.toString(); } // lazy init @Override public Set keySet() { if (this.keySet == null) { this.keySet = Collections.unmodifiableSet(this.delegate.keySet()); } return this.keySet; } @Override public Set>> entrySet() { if (this.entrySet == null) { this.entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet()); } return this.entrySet; } @Override public Collection> values() { if (this.values == null) { this.values = new UnmodifiableValueCollection<>(this.delegate.values()); } return this.values; } // unsupported @Nullable @Override public List put(K key, List value) { throw new UnsupportedOperationException(); } @Override public List putIfAbsent(K key, List value) { throw new UnsupportedOperationException(); } @Override public void putAll(Map> m) { throw new UnsupportedOperationException(); } @Override public List remove(Object key) { throw new UnsupportedOperationException(); } @Override public void add(K key, @Nullable V value) { throw new UnsupportedOperationException(); } @Override public void addAll(K key, List values) { throw new UnsupportedOperationException(); } @Override public void addAll(MultiValueMap values) { throw new UnsupportedOperationException(); } @Override public void addIfAbsent(K key, @Nullable V value) { throw new UnsupportedOperationException(); } @Override public void set(K key, @Nullable V value) { throw new UnsupportedOperationException(); } @Override public void setAll(Map values) { throw new UnsupportedOperationException(); } @Override public void replaceAll(BiFunction, ? extends List> function) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object key, Object value) { throw new UnsupportedOperationException(); } @Override public boolean replace(K key, List oldValue, List newValue) { throw new UnsupportedOperationException(); } @Override public List replace(K key, List value) { throw new UnsupportedOperationException(); } @Override public List computeIfAbsent(K key, Function> mappingFunction) { throw new UnsupportedOperationException(); } @Override public List computeIfPresent(K key, BiFunction, ? extends List> remappingFunction) { throw new UnsupportedOperationException(); } @Override public List compute(K key, BiFunction, ? extends List> remappingFunction) { throw new UnsupportedOperationException(); } @Override public List merge(K key, List value, BiFunction, ? super List, ? extends List> remappingFunction) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } private static class UnmodifiableEntrySet implements Set>>, Serializable { private static final long serialVersionUID = 2407578793783925203L; private final Set>> delegate; @SuppressWarnings("unchecked") public UnmodifiableEntrySet(Set>> delegate) { this.delegate = (Set>>) delegate; } // delegation @Override public int size() { return this.delegate.size(); } @Override public boolean isEmpty() { return this.delegate.isEmpty(); } @Override public boolean contains(Object o) { return this.delegate.contains(o); } @Override public boolean containsAll(Collection c) { return this.delegate.containsAll(c); } @Override public Iterator>> iterator() { Iterator>> iterator = this.delegate.iterator(); return new Iterator<>() { @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Entry> next() { return new UnmodifiableEntry<>(iterator.next()); } }; } @Override public Object[] toArray() { Object[] result = this.delegate.toArray(); filterArray(result); return result; } @Override public T[] toArray(T[] a) { T[] result = this.delegate.toArray(a); filterArray(result); return result; } @SuppressWarnings("unchecked") private void filterArray(Object[] result) { for (int i = 0; i < result.length; i++) { if (result[i] instanceof Map.Entry entry) { result[i] = new UnmodifiableEntry<>((Entry>) entry); } } } @Override public void forEach(Consumer>> action) { this.delegate.forEach(e -> action.accept(new UnmodifiableEntry<>(e))); } @Override public Stream>> stream() { return StreamSupport.stream(spliterator(), false); } @Override public Stream>> parallelStream() { return StreamSupport.stream(spliterator(), true); } @Override public Spliterator>> spliterator() { return new UnmodifiableEntrySpliterator<>(this.delegate.spliterator()); } @Override public boolean equals(@Nullable Object other) { return (this == other || other instanceof Set that && size() == that.size() && containsAll(that)); } @Override public int hashCode() { return this.delegate.hashCode(); } @Override public String toString() { return this.delegate.toString(); } // unsupported @Override public boolean add(Entry> kListEntry) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean removeIf(Predicate>> filter) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection>> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } private static class UnmodifiableEntrySpliterator implements Spliterator>> { private final Spliterator>> delegate; @SuppressWarnings("unchecked") public UnmodifiableEntrySpliterator( Spliterator>> delegate) { this.delegate = (Spliterator>>) delegate; } @Override public boolean tryAdvance(Consumer>> action) { return this.delegate.tryAdvance(entry -> action.accept(new UnmodifiableEntry<>(entry))); } @Override public void forEachRemaining(Consumer>> action) { this.delegate.forEachRemaining(entry -> action.accept(new UnmodifiableEntry<>(entry))); } @Override @Nullable public Spliterator>> trySplit() { Spliterator>> split = this.delegate.trySplit(); if (split != null) { return new UnmodifiableEntrySpliterator<>(split); } else { return null; } } @Override public long estimateSize() { return this.delegate.estimateSize(); } @Override public long getExactSizeIfKnown() { return this.delegate.getExactSizeIfKnown(); } @Override public int characteristics() { return this.delegate.characteristics(); } @Override public boolean hasCharacteristics(int characteristics) { return this.delegate.hasCharacteristics(characteristics); } @Override public Comparator>> getComparator() { return this.delegate.getComparator(); } } private static class UnmodifiableEntry implements Map.Entry> { private final Entry> delegate; @SuppressWarnings("unchecked") public UnmodifiableEntry(Entry> delegate) { Assert.notNull(delegate, "Delegate must not be null"); this.delegate = (Entry>) delegate; } @Override public K getKey() { return this.delegate.getKey(); } @Override public List getValue() { return Collections.unmodifiableList(this.delegate.getValue()); } @Override public List setValue(List value) { throw new UnsupportedOperationException(); } @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof Map.Entry that && getKey().equals(that.getKey()) && getValue().equals(that.getValue()))); } @Override public int hashCode() { return this.delegate.hashCode(); } @Override public String toString() { return this.delegate.toString(); } } } private static class UnmodifiableValueCollection implements Collection>, Serializable { private static final long serialVersionUID = 5518377583904339588L; private final Collection> delegate; public UnmodifiableValueCollection(Collection> delegate) { this.delegate = delegate; } // delegation @Override public int size() { return this.delegate.size(); } @Override public boolean isEmpty() { return this.delegate.isEmpty(); } @Override public boolean contains(Object o) { return this.delegate.contains(o); } @Override public boolean containsAll(Collection c) { return this.delegate.containsAll(c); } @Override public Object[] toArray() { Object[] result = this.delegate.toArray(); filterArray(result); return result; } @Override public T[] toArray(T[] a) { T[] result = this.delegate.toArray(a); filterArray(result); return result; } private void filterArray(Object[] array) { for (int i = 0; i < array.length; i++) { if (array[i] instanceof List list) { array[i] = Collections.unmodifiableList(list); } } } @Override public Iterator> iterator() { Iterator> iterator = this.delegate.iterator(); return new Iterator<>() { @Override public boolean hasNext() { return iterator.hasNext(); } @Override public List next() { return Collections.unmodifiableList(iterator.next()); } }; } @Override public void forEach(Consumer> action) { this.delegate.forEach(list -> action.accept(Collections.unmodifiableList(list))); } @Override public Spliterator> spliterator() { return new UnmodifiableValueSpliterator<>(this.delegate.spliterator()); } @Override public Stream> stream() { return StreamSupport.stream(spliterator(), false); } @Override public Stream> parallelStream() { return StreamSupport.stream(spliterator(), true); } @Override public boolean equals(@Nullable Object other) { return (this == other || this.delegate.equals(other)); } @Override public int hashCode() { return this.delegate.hashCode(); } @Override public String toString() { return this.delegate.toString(); } // unsupported @Override public boolean add(List ts) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean removeIf(Predicate> filter) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } private static class UnmodifiableValueSpliterator implements Spliterator> { private final Spliterator> delegate; public UnmodifiableValueSpliterator(Spliterator> delegate) { this.delegate = delegate; } @Override public boolean tryAdvance(Consumer> action) { return this.delegate.tryAdvance(l -> action.accept(Collections.unmodifiableList(l))); } @Override public void forEachRemaining(Consumer> action) { this.delegate.forEachRemaining(l -> action.accept(Collections.unmodifiableList(l))); } @Override @Nullable public Spliterator> trySplit() { Spliterator> split = this.delegate.trySplit(); if (split != null) { return new UnmodifiableValueSpliterator<>(split); } else { return null; } } @Override public long estimateSize() { return this.delegate.estimateSize(); } @Override public long getExactSizeIfKnown() { return this.delegate.getExactSizeIfKnown(); } @Override public int characteristics() { return this.delegate.characteristics(); } @Override public boolean hasCharacteristics(int characteristics) { return this.delegate.hasCharacteristics(characteristics); } @Override public Comparator> getComparator() { return this.delegate.getComparator(); } } } }