/*
* Copyright (c) 2014 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
*
* 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 io.werval.util;
import java.io.Serializable;
import java.util.Arrays;
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.Objects;
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;
/**
* UnmodifiableMultiValueMap.
*/
final class UnmodifiableMultiValueMap<K, V>
implements MultiValueMap<K, V>, Serializable
{
private static final long serialVersionUID = 1L;
private final MultiValueMap<? extends K, ? extends V> delegate;
private transient Set<K> keySet;
private transient Set<Map.Entry<K, List<V>>> entrySet;
private transient Collection<List<V>> values;
UnmodifiableMultiValueMap( MultiValueMap<? extends K, ? extends V> delegate )
{
Objects.requireNonNull( delegate, "Original MultiValueMap" );
this.delegate = delegate;
}
@Override
public void add( K key, V value, V... moreValues )
{
throw new UnsupportedOperationException();
}
@Override
public void setSingle( K key, V value )
{
throw new UnsupportedOperationException();
}
@Override
public void setAll( Map<K, V> map )
{
throw new UnsupportedOperationException();
}
@Override
public V getSingle( K key )
{
return ( (MultiValueMap<K, V>) delegate ).getSingle( key );
}
@Override
public V getFirst( K key )
{
return ( (MultiValueMap<K, V>) delegate ).getFirst( key );
}
@Override
public V getLast( K key )
{
return ( (MultiValueMap<K, V>) delegate ).getLast( key );
}
@Override
public Map<K, V> toMapSingleValues()
{
return ( (MultiValueMap<K, V>) delegate ).toMapSingleValues();
}
@Override
public Map<K, V> toMapFirstValues()
{
return ( (MultiValueMap<K, V>) delegate ).toMapFirstValues();
}
@Override
public Map<K, V> toMapLastValues()
{
return ( (MultiValueMap<K, V>) delegate ).toMapLastValues();
}
@Override
public int size()
{
return delegate.size();
}
@Override
public boolean isEmpty()
{
return delegate.isEmpty();
}
@Override
public boolean containsKey( Object key )
{
return delegate.containsKey( key );
}
@Override
public boolean containsValue( Object value )
{
return delegate.containsValue( value );
}
@Override
public List<V> get( Object key )
{
return ( (Map<K, List<V>>) delegate ).get( key );
}
@Override
public List<V> put( K key, List<V> value )
{
throw new UnsupportedOperationException();
}
@Override
public List<V> remove( Object key )
{
throw new UnsupportedOperationException();
}
@Override
public void putAll( Map<? extends K, ? extends List<V>> m )
{
throw new UnsupportedOperationException();
}
@Override
public void clear()
{
delegate.clear();
}
@Override
public Set<K> keySet()
{
if( keySet == null )
{
keySet = Collections.unmodifiableSet( delegate.keySet() );
}
return keySet;
}
@Override
public Collection<List<V>> values()
{
if( values == null )
{
values = Collections.unmodifiableCollection( ( (Map<K, List<V>>) delegate ).values() );
}
return values;
}
@Override
public Set<Entry<K, List<V>>> entrySet()
{
if( entrySet == null )
{
entrySet = new UnmodifiableEntrySet<>( ( (Map<K, List<V>>) delegate ).entrySet() );
}
return entrySet;
}
@Override
public List<V> getOrDefault( Object key, List<V> defaultValue )
{
// Safe cast as we don't change the value
return ( (Map<K, List<V>>) delegate ).getOrDefault( key, defaultValue );
}
@Override
public void forEach( BiConsumer<? super K, ? super List<V>> action )
{
// Safe cast as we don't change the value
( (Map<K, List<V>>) delegate ).forEach( action );
}
@Override
public void replaceAll( BiFunction<? super K, ? super List<V>, ? extends List<V>> function )
{
throw new UnsupportedOperationException();
}
@Override
public List<V> putIfAbsent( K key, List<V> value )
{
throw new UnsupportedOperationException();
}
@Override
public boolean remove( Object key, Object value )
{
throw new UnsupportedOperationException();
}
@Override
public boolean replace( K key, List<V> oldValue, List<V> newValue )
{
throw new UnsupportedOperationException();
}
@Override
public List<V> replace( K key, List<V> value )
{
throw new UnsupportedOperationException();
}
@Override
public List<V> computeIfAbsent( K key, Function<? super K, ? extends List<V>> mappingFunction )
{
throw new UnsupportedOperationException();
}
@Override
public List<V> computeIfPresent(
K key, BiFunction<? super K, ? super List<V>, ? extends List<V>> remappingFunction
)
{
throw new UnsupportedOperationException();
}
@Override
public List<V> compute( K key, BiFunction<? super K, ? super List<V>, ? extends List<V>> remappingFunction )
{
throw new UnsupportedOperationException();
}
@Override
public List<V> merge(
K key, List<V> value, BiFunction<? super List<V>, ? super List<V>, ? extends List<V>> remappingFunction
)
{
throw new UnsupportedOperationException();
}
@Override
public int hashCode()
{
return delegate.hashCode();
}
@Override @SuppressWarnings( value = "EqualsWhichDoesntCheckParameterClass" )
public boolean equals( Object obj )
{
return delegate.equals( obj );
}
@Override
public String toString()
{
return delegate.toString();
}
/**
* We need this class in addition to UnmodifiableSet as
* Map.Entries themselves permit modification of the backing Map
* via their setValue operation. This class is subtle: there are
* many possible attacks that must be thwarted.
*
* @serial include
*/
@SuppressWarnings( value = "EqualsAndHashcode" )
static class UnmodifiableEntrySet<K, V>
extends UnmodifiableSet<Map.Entry<K, V>>
{
private static final long serialVersionUID = 7854390611657943733L;
UnmodifiableEntrySet( Set<? extends Map.Entry<? extends K, ? extends V>> s )
{
// Need to cast to raw in order to work around a limitation in the type system
super( (Set) s );
}
static <K, V> Consumer<Map.Entry<K, V>> entryConsumer( Consumer<? super Entry<K, V>> action )
{
return (java.util.Map.Entry<K, V> e) -> action.accept( new UnmodifiableEntry<>( e ) );
}
@Override
public void forEach( Consumer<? super Entry<K, V>> action )
{
Objects.requireNonNull( action );
c.forEach( entryConsumer( action ) );
}
static final class UnmodifiableEntrySetSpliterator<K, V>
implements Spliterator<Entry<K, V>>
{
private final Spliterator<Map.Entry<K, V>> s;
UnmodifiableEntrySetSpliterator( Spliterator<Entry<K, V>> s )
{
this.s = s;
}
@Override
public boolean tryAdvance( Consumer<? super Entry<K, V>> action )
{
Objects.requireNonNull( action );
return s.tryAdvance( entryConsumer( action ) );
}
@Override
public void forEachRemaining( Consumer<? super Entry<K, V>> action )
{
Objects.requireNonNull( action );
s.forEachRemaining( entryConsumer( action ) );
}
@Override
public Spliterator<Entry<K, V>> trySplit()
{
Spliterator<Entry<K, V>> split = s.trySplit();
return split == null ? null : new UnmodifiableEntrySetSpliterator<>( split );
}
@Override
public long estimateSize()
{
return s.estimateSize();
}
@Override
public long getExactSizeIfKnown()
{
return s.getExactSizeIfKnown();
}
@Override
public int characteristics()
{
return s.characteristics();
}
@Override
public boolean hasCharacteristics( int characteristics )
{
return s.hasCharacteristics( characteristics );
}
@Override
public Comparator<? super Entry<K, V>> getComparator()
{
return s.getComparator();
}
}
@Override
public Spliterator<Entry<K, V>> spliterator()
{
return new UnmodifiableEntrySetSpliterator<>( (Spliterator<Map.Entry<K, V>>) c.spliterator() );
}
@Override
public Stream<Entry<K, V>> stream()
{
return StreamSupport.stream( spliterator(), false );
}
@Override
public Stream<Entry<K, V>> parallelStream()
{
return StreamSupport.stream( spliterator(), true );
}
@Override
public Iterator<Map.Entry<K, V>> iterator()
{
return new Iterator<Map.Entry<K, V>>()
{
private final Iterator<? extends Map.Entry<? extends K, ? extends V>> i = c.iterator();
@Override
public boolean hasNext()
{
return i.hasNext();
}
@Override
public Map.Entry<K, V> next()
{
return new UnmodifiableEntry<>( i.next() );
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
@Override
public Object[] toArray()
{
Object[] a = c.toArray();
for( int i = 0; i < a.length; i++ )
{
a[i] = new UnmodifiableEntry<>( (Map.Entry<? extends K, ? extends V>) a[i] );
}
return a;
}
@Override
public <T> T[] toArray( T[] a )
{
// We don't pass a to c.toArray, to avoid window of
// vulnerability wherein an unscrupulous multithreaded client
// could get his hands on raw (unwrapped) Entries from c.
@SuppressWarnings( value = "SuspiciousToArrayCall" )
Object[] arr = c.toArray( a.length == 0 ? a : Arrays.copyOf( a, 0 ) );
for( int i = 0; i < arr.length; i++ )
{
arr[i] = new UnmodifiableEntry<>( (Map.Entry<? extends K, ? extends V>) arr[i] );
}
if( arr.length > a.length )
{
return (T[]) arr;
}
System.arraycopy( arr, 0, a, 0, arr.length );
if( a.length > arr.length )
{
a[arr.length] = null;
}
return a;
}
/**
* This method is overridden to protect the backing set against
* an object with a nefarious equals function that senses
* that the equality-candidate is Map.Entry and calls its
* setValue method.
*/
@Override
public boolean contains( Object o )
{
if( !( o instanceof Map.Entry ) )
{
return false;
}
return c.contains( new UnmodifiableEntry<>( (Map.Entry<?, ?>) o ) );
}
/**
* The next two methods are overridden to protect against
* an unscrupulous List whose contains(Object o) method senses
* when o is a Map.Entry, and calls o.setValue.
*/
@Override @SuppressWarnings( value = "element-type-mismatch" )
public boolean containsAll( Collection<?> coll )
{
// Invokes safe contains() above
for( Object e : coll )
{
if( !contains( e ) )
{
return false;
}
}
return true;
}
@Override
public boolean equals( Object o )
{
if( o == this )
{
return true;
}
if( !( o instanceof Set ) )
{
return false;
}
Set<?> s = (Set<?>) o;
if( s.size() != c.size() )
{
return false;
}
return containsAll( s ); // Invokes safe containsAll() above
}
/**
* This "wrapper class" serves two purposes: it prevents
* the client from modifying the backing Map, by short-circuiting
* the setValue method, and it protects the backing Map against
* an ill-behaved Map.Entry that attempts to modify another
* Map Entry when asked to perform an equality check.
*/
private static final class UnmodifiableEntry<K, V>
implements Map.Entry<K, V>
{
private final Map.Entry<? extends K, ? extends V> entry;
private UnmodifiableEntry( Map.Entry<? extends K, ? extends V> entry )
{
this.entry = Objects.requireNonNull( entry );
}
@Override
public K getKey()
{
return entry.getKey();
}
@Override
public V getValue()
{
return entry.getValue();
}
@Override
public V setValue( V value )
{
throw new UnsupportedOperationException();
}
@Override
public int hashCode()
{
return entry.hashCode();
}
@Override
public boolean equals( Object o )
{
if( this == o )
{
return true;
}
if( !( o instanceof Map.Entry ) )
{
return false;
}
Map.Entry<?, ?> t = (Map.Entry<?, ?>) o;
return Objects.equals( entry.getKey(), t.getKey() ) && Objects.equals( entry.getValue(), t.getValue() );
}
@Override
public String toString()
{
return entry.toString();
}
}
}
static class UnmodifiableSet<E>
extends UnmodifiableCollection<E>
implements Set<E>, Serializable
{
private static final long serialVersionUID = -9215047833775013803L;
UnmodifiableSet( Set<? extends E> s )
{
super( s );
}
@Override @SuppressWarnings( value = "EqualsWhichDoesntCheckParameterClass" )
public boolean equals( Object o )
{
return o == this || c.equals( o );
}
@Override
public int hashCode()
{
return c.hashCode();
}
}
static class UnmodifiableCollection<E>
implements Collection<E>, Serializable
{
private static final long serialVersionUID = 1820017752578914078L;
final Collection<? extends E> c;
UnmodifiableCollection( Collection<? extends E> c )
{
if( c == null )
{
throw new NullPointerException();
}
this.c = c;
}
@Override
public int size()
{
return c.size();
}
@Override
public boolean isEmpty()
{
return c.isEmpty();
}
@Override
public boolean contains( Object o )
{
return c.contains( o );
}
@Override
public Object[] toArray()
{
return c.toArray();
}
@Override @SuppressWarnings( value = "SuspiciousToArrayCall" )
public <T> T[] toArray( T[] a )
{
return c.toArray( a );
}
@Override
public String toString()
{
return c.toString();
}
@Override
public Iterator<E> iterator()
{
return new Iterator<E>()
{
private final Iterator<? extends E> i = c.iterator();
@Override
public boolean hasNext()
{
return i.hasNext();
}
@Override
public E next()
{
return i.next();
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
@Override
public void forEachRemaining( Consumer<? super E> action )
{
// Use backing collection version
i.forEachRemaining( action );
}
};
}
@Override
public boolean add( E e )
{
throw new UnsupportedOperationException();
}
@Override
public boolean remove( Object o )
{
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll( Collection<?> coll )
{
return c.containsAll( coll );
}
@Override
public boolean addAll( Collection<? extends E> coll )
{
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll( Collection<?> coll )
{
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll( Collection<?> coll )
{
throw new UnsupportedOperationException();
}
@Override
public void clear()
{
throw new UnsupportedOperationException();
}
// Override default methods in Collection
@Override
public void forEach( Consumer<? super E> action )
{
c.forEach( action );
}
@Override
public boolean removeIf( Predicate<? super E> filter )
{
throw new UnsupportedOperationException();
}
@Override
public Spliterator<E> spliterator()
{
return (Spliterator<E>) c.spliterator();
}
@Override
public Stream<E> stream()
{
return (Stream<E>) c.stream();
}
@Override
public Stream<E> parallelStream()
{
return (Stream<E>) c.parallelStream();
}
}
}