ProxiedMap.java

// Generated by delombok at Mon Nov 18 07:27:48 UTC 2024
package de.larssh.utils.collection;

import static java.util.stream.Collectors.toList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import de.larssh.utils.Finals;
import de.larssh.utils.Nullables;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.Nullable;

/**
 * An abstract {@link Map} implementation pointing to a given map object.
 *
 * <p>
 * Therefore it allows implementing maps based on invisible map types, such as
 * {@link java.util.Collections#unmodifiableMap(Map)} or
 * {@link java.util.Collections#synchronizedMap(Map)}.
 *
 * @param <K> the type of keys in this map
 * @param <V> the type of values in this map
 */
public abstract class ProxiedMap<K, V> implements Map<K, V> {
	/**
	 * Wrapped map
	 */
	private final Map<K, V> map;
	/**
	 * The set of entries as defined by {@link Map#entrySet()}.
	 */
	@SuppressWarnings({"checkstyle:AnonInnerLength", "PMD.AvoidFieldNameMatchingMethodName"})
	private final Supplier<Set<Entry<K, V>>> entrySet = Finals.lazy(() -> new ProxiedSet<Entry<K, V>>(getProxiedForRead().entrySet()) {
		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean isModifiable() {
			return ProxiedMap.this.isModifiable();
		}
		/**
		 * {@inheritDoc}
		 */
		@Override
		public Iterator<Entry<K, V>> iterator() {
			return new ProxiedIterator<Entry<K, V>>(getWrappedForRead().iterator()) {
				/**
				 * {@inheritDoc}
				 */
				@Override
				public boolean isModifiable() {
					return ProxiedMap.this.isModifiable();
				}
				/**
				 * {@inheritDoc}
				 */
				@Nullable
				@Override
				public Entry<K, V> next() {
					return new ProxiedEntry<>(this::isModifiable, Nullables.orElseThrow(super.next()));
				}
			};
		}
		/**
		 * {@inheritDoc}
		 */
		@Override
		public Object[] toArray() {
			return  //
			stream().map(entry -> new ProxiedEntry<>(this::isModifiable, entry)).toArray();
		}
		/**
		 * {@inheritDoc}
		 */
		@Override
		public <T> T[] toArray(@Nullable final T[] array) {
			return  //
			stream().map(entry -> new ProxiedEntry<>(this::isModifiable, entry)).collect(toList()).toArray(array);
		}
	});
	/**
	 * The set of keys as defined by {@link Map#keySet()}.
	 */
	@SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
	private final Supplier<Set<K>> keySet = Finals.lazy(() -> new ProxiedSet<K>(getProxiedForRead().keySet()) {
		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean isModifiable() {
			return ProxiedMap.this.isModifiable();
		}
	});
	/**
	 * The collection of values as defined by {@link Map#values()}.
	 */
	@SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
	private final Supplier<Collection<V>> values = Finals.lazy(() -> new ProxiedCollection<V>(getProxiedForRead().values()) {
		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean isModifiable() {
			return ProxiedMap.this.isModifiable();
		}
	});

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void clear() {
		getProxiedIfModifiable().clear();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean containsKey(@Nullable final Object key) {
		return getProxiedForRead().containsKey(key);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean containsValue(@Nullable final Object value) {
		return getProxiedForRead().containsValue(value);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Set<Entry<K, V>> entrySet() {
		return entrySet.get();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(@CheckForNull final Object object) {
		return getProxiedForRead().equals(object);
	}

	/**
	 * {@inheritDoc}
	 */
	@Nullable
	@Override
	public V get(@Nullable final Object key) {
		return getProxiedForRead().get(key);
	}

	/**
	 * Verifies if this object is modifiable and either returns the wrapped map or
	 * throws an appropriate exception.
	 *
	 * @return the wrapped map if this object is modifiable
	 * @throws UnsupportedOperationException if this object is unmodifiable
	 */
	protected Map<K, V> getProxiedIfModifiable() {
		if (isModifiable()) {
			return map;
		}
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns the wrapped map without verifying if modifying it is prohibited.
	 *
	 * @return the wrapped map
	 */
	protected Map<K, V> getProxiedForRead() {
		return map;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		return getProxiedForRead().hashCode();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isEmpty() {
		return getProxiedForRead().isEmpty();
	}

	/**
	 * Flag specifying if this instance can be modified
	 *
	 * @return {@code true} if this instance is modifiable, else {@code false}
	 */
	public abstract boolean isModifiable();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Set<K> keySet() {
		return keySet.get();
	}

	/**
	 * {@inheritDoc}
	 */
	@Nullable
	@Override
	public V put(@Nullable final K key, @Nullable final V value) {
		return getProxiedIfModifiable().put(key, value);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void putAll(@Nullable final Map<? extends K, ? extends V> map) {
		getProxiedIfModifiable().putAll(map);
	}

	/**
	 * {@inheritDoc}
	 */
	@Nullable
	@Override
	public V remove(@Nullable final Object key) {
		return getProxiedIfModifiable().remove(key);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int size() {
		return getProxiedForRead().size();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		return getProxiedForRead().toString();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Collection<V> values() {
		return values.get();
	}


	/**
	 * An abstract {@link Entry} implementation pointing to a given entry object.
	 *
	 * @param <K> the type of keys in this map
	 * @param <V> the type of values in this map
	 */
	private static class ProxiedEntry<K, V> implements Entry<K, V> {
		/**
		 * Supplies whether this instance is modifiable or not
		 */
		@SuppressWarnings("PMD.LinguisticNaming")
		private final BooleanSupplier isModifiable;
		/**
		 * Wrapped entry
		 */
		private final Entry<K, V> entry;

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean equals(@CheckForNull final Object object) {
			return getEntry().equals(object);
		}

		/**
		 * {@inheritDoc}
		 */
		@Nullable
		@Override
		public K getKey() {
			return getEntry().getKey();
		}

		/**
		 * {@inheritDoc}
		 */
		@Nullable
		@Override
		public V getValue() {
			return getEntry().getValue();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public int hashCode() {
			return getEntry().hashCode();
		}

		/**
		 * {@inheritDoc}
		 */
		@Nullable
		@Override
		public V setValue(@Nullable final V value) {
			if (isModifiable.getAsBoolean()) {
				return getEntry().setValue(value);
			}
			throw new UnsupportedOperationException();
		}

		/**
		 * Creates a new {@code ProxiedEntry} instance.
		 *
		 * @param isModifiable Supplies whether this instance is modifiable or not
		 * @param entry Wrapped entry
		 */
		@java.lang.SuppressWarnings("all")
		@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
		@lombok.Generated
		public ProxiedEntry(final BooleanSupplier isModifiable, final Entry<K, V> entry) {
			this.isModifiable = isModifiable;
			this.entry = entry;
		}

		/**
		 * Wrapped entry
		 *
		 * @return the wrapped entry
		 */
		@java.lang.SuppressWarnings("all")
		@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
		@lombok.Generated
		public Entry<K, V> getEntry() {
			return this.entry;
		}
	}

	/**
	 * Creates a new {@code ProxiedMap} instance.
	 *
	 * @param map Wrapped map
	 */
	@java.lang.SuppressWarnings("all")
	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
	@lombok.Generated
	public ProxiedMap(final Map<K, V> map) {
		this.map = map;
	}
}