package org.codefx.libfx.collection.transform; import java.util.AbstractMap.SimpleEntry; import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; /** * Abstract superclass to {@link Map}s which transform another map. * <p> * This class allows null keys and values. Subclasses might override that by implementing aggressive null checks. * * @param <IK> * the inner key type, i.e. the type of the keys contained in the wrapped/inner map * @param <OK> * the outer key type, i.e. the type of keys appearing to be in this map * @param <IV> * the inner value type, i.e. the type of the values contained in the wrapped/inner map * @param <OV> * the outer value type, i.e. the type of values appearing to be in this map */ abstract class AbstractTransformingMap<IK, OK, IV, OV> implements Map<OK, OV> { // #begin FIELDS private final Set<OK> outerKeys; private final Collection<OV> outerValues; private final Set<Entry<OK, OV>> outerEntries; // #end FIELDS // #begin CONSTRUCTION /** * Creates a new abstract transforming map. */ protected AbstractTransformingMap() { outerKeys = new KeySetView(); outerValues = new ValueCollectionView(); outerEntries = new EntrySetView(); } // #end CONSTRUCTION // #begin IMPLEMENTATION OF 'Map<OK, OV>' /** * Indicates whether the specified collection is equivalent to this one. This is the case if it is also an * {@link AbstractTransformingMap} and wraps the same {@link #getInnerMap() innerMap}. * * @param otherMap * the {@link Collection} which is compared with this one * @return true if this and the specified collection are equivalent */ protected final boolean isThisMap(Map<?, ?> otherMap) { if (otherMap == this) return true; if (otherMap instanceof AbstractTransformingMap) { AbstractTransformingMap<?, ?, ?, ?> otherTransformingMap = (AbstractTransformingMap<?, ?, ?, ?>) otherMap; boolean sameInnerMap = otherTransformingMap.getInnerMap() == getInnerMap(); return sameInnerMap; } return false; } // size @Override public int size() { return getInnerMap().size(); } @Override public boolean isEmpty() { return getInnerMap().isEmpty(); } // contains @Override public boolean containsKey(Object key) { if (isOuterKey(key)) { @SuppressWarnings("unchecked") /* * This cast can not fail due to erasure but the following call to 'transformToInnerKey' might. In that case * a 'ClassCastException' will be thrown which is in accordance with the contract of 'containsKey'. If * 'isOuterKey' does its job well (which can be hard due to erasure) this will not happen. */ OK outerKey = (OK) key; return getInnerMap().containsKey( transformToInnerKey(outerKey)); } else return false; } @Override public boolean containsValue(Object value) { if (isOuterValue(value)) { @SuppressWarnings("unchecked") /* * This cast can not fail due to erasure but the following call to 'transformToInnerValue' might. In that * case a 'ClassCastException' will be thrown which is in accordance with the contract of 'containsValue'. * If 'isOuterValue' does its job well (which can be hard due to erasure) this will not happen. */ OV outerValue = (OV) value; return getInnerMap().containsValue( transformToInnerValue(outerValue)); } else return false; } // get @Override public OV get(Object key) { if (isOuterKey(key)) { @SuppressWarnings("unchecked") /* * This cast can not fail due to erasure but the following call to 'transformToInnerKey' might. In that case * a 'ClassCastException' will be thrown which is in accordance with the contract of 'get'. If 'isOuterKey' * does its job well (which can be hard due to erasure) this will not happen. */ OK outerKey = (OK) key; return transformToOuterValue(getInnerMap().get( transformToInnerKey(outerKey))); } else return null; } @Override public OV getOrDefault(Object key, OV defaultValue) { if (isOuterKey(key)) { @SuppressWarnings("unchecked") /* * This cast can not fail due to erasure but the following call to 'transformToInnerKey' might. In that case * a 'ClassCastException' will be thrown which is in accordance with the contract of 'get'. If 'isOuterKey' * does its job well (which can be hard due to erasure) this will not happen. */ OK outerKey = (OK) key; return transformToOuterValue(getInnerMap().getOrDefault( transformToInnerKey(outerKey), transformToInnerValue(defaultValue) )); } else return defaultValue; } // put / compute / merge / replace @Override public OV put(OK key, OV value) { return transformToOuterValue(getInnerMap().put( transformToInnerKey(key), transformToInnerValue(value))); } @Override public OV putIfAbsent(OK key, OV value) { return transformToOuterValue(getInnerMap().putIfAbsent( transformToInnerKey(key), transformToInnerValue(value))); } @Override public void putAll(Map<? extends OK, ? extends OV> outerMap) { Objects.requireNonNull(outerMap, "The argument 'outerMap' must not be null."); Map<IK, IV> asInner = new TransformToReadOnlyInnerMap(outerMap); getInnerMap().putAll(asInner); } @Override public OV compute(OK key, BiFunction<? super OK, ? super OV, ? extends OV> remappingFunction) { Objects.requireNonNull(remappingFunction, "The argument 'remappingFunction' must not be null."); return transformToOuterValue(getInnerMap().compute( transformToInnerKey(key), transformToInnerKeyValueToValueFunction(remappingFunction) )); } @Override public OV computeIfAbsent(OK key, Function<? super OK, ? extends OV> mappingFunction) { Objects.requireNonNull(mappingFunction, "The argument 'mappingFunction' must not be null."); return transformToOuterValue(getInnerMap().computeIfAbsent( transformToInnerKey(key), transformToInnerToKeyValueFunction(mappingFunction) )); } @Override public OV computeIfPresent(OK key, BiFunction<? super OK, ? super OV, ? extends OV> remappingFunction) { Objects.requireNonNull(remappingFunction, "The argument 'remappingFunction' must not be null."); return transformToOuterValue(getInnerMap().computeIfPresent( transformToInnerKey(key), transformToInnerKeyValueToValueFunction(remappingFunction) )); } @Override public OV merge(OK key, OV value, BiFunction<? super OV, ? super OV, ? extends OV> remappingFunction) { Objects.requireNonNull(remappingFunction, "The argument 'remappingFunction' must not be null."); return transformToOuterValue(getInnerMap().merge( transformToInnerKey(key), transformToInnerValue(value), transformToInnerValueValueToValueFunction(remappingFunction) )); } @Override public OV replace(OK key, OV value) { return transformToOuterValue(getInnerMap().replace( transformToInnerKey(key), transformToInnerValue(value))); } @Override public boolean replace(OK key, OV oldValue, OV newValue) { return getInnerMap().replace( transformToInnerKey(key), transformToInnerValue(oldValue), transformToInnerValue(newValue) ); } @Override public void replaceAll(BiFunction<? super OK, ? super OV, ? extends OV> function) { Objects.requireNonNull(function, "The argument 'function' must not be null."); getInnerMap().replaceAll(transformToInnerKeyValueToValueFunction(function)); } // remove @Override public OV remove(Object key) { if (isOuterKey(key)) { @SuppressWarnings("unchecked") /* * This cast can not fail due to erasure but the following call to 'transformToInnerKey' might. In that case * a 'ClassCastException' will be thrown which is in accordance with the contract of 'remove'. If * 'isOuterKey' does its job well (which can be hard due to erasure) this will not happen. */ OK outerKey = (OK) key; return transformToOuterValue(getInnerMap().remove( transformToInnerKey(outerKey))); } else return null; } @Override public boolean remove(Object key, Object value) { if (isOuterKey(key) && isOuterValue(value)) { /* * These casts can not fail due to erasure but the following calls to 'transformToInner...' might. In that * case a 'ClassCastException' will be thrown which is in accordance with the contract of 'remove'. If * 'isOuter...' does its job well (which can be hard due to erasure) this will not happen. */ @SuppressWarnings("unchecked") OK outerKey = (OK) key; @SuppressWarnings("unchecked") OV outerValue = (OV) value; return getInnerMap().remove( transformToInnerKey(outerKey), transformToInnerValue(outerValue) ); } else return false; } @Override public void clear() { getInnerMap().clear(); } // process @Override public void forEach(BiConsumer<? super OK, ? super OV> action) { Objects.requireNonNull(action, "The argument 'action' must not be null."); getInnerMap().forEach(transformToInnerKeyValueConsumer(action)); } // views @Override public Set<OK> keySet() { return outerKeys; } @Override public Collection<OV> values() { return outerValues; } @Override public Set<Entry<OK, OV>> entrySet() { return outerEntries; } // function transformation private Function<? super IK, ? extends IV> transformToInnerToKeyValueFunction( Function<? super OK, ? extends OV> function) { return innerKey -> transformToInnerValue(function.apply(transformToOuterKey(innerKey))); } private BiFunction<? super IK, ? super IV, ? extends IV> transformToInnerKeyValueToValueFunction( BiFunction<? super OK, ? super OV, ? extends OV> function) { return (innerKey, innerValue) -> transformToInnerValue(function.apply( transformToOuterKey(innerKey), transformToOuterValue(innerValue))); } private BiFunction<? super IV, ? super IV, ? extends IV> transformToInnerValueValueToValueFunction( BiFunction<? super OV, ? super OV, ? extends OV> function) { return (innerValue1, innerValue2) -> transformToInnerValue(function.apply( transformToOuterValue(innerValue1), transformToOuterValue(innerValue2))); } private BiConsumer<? super IK, ? super IV> transformToInnerKeyValueConsumer( BiConsumer<? super OK, ? super OV> consumer) { return (innerKey, innerValue) -> consumer.accept( transformToOuterKey(innerKey), transformToOuterValue(innerValue)); } // #end IMPLEMENTATION OF 'Map<OK, OV>' // #begin OBJECT @Override public final boolean equals(Object object) { if (object == this) return true; if (!(object instanceof Map)) return false; Map<?, ?> other = (Map<?, ?>) object; if (isThisMap(other)) return true; return outerEntries.equals(other.entrySet()); } @Override public final int hashCode() { return outerEntries.hashCode(); } @Override public String toString() { return outerEntries .stream() .map(Objects::toString) .collect(Collectors.joining(", ", "{", "}")); } // #end OBJECT // #begin ABSTRACT METHODS /** * @return the inner map wrapped by this transforming map */ protected abstract Map<IK, IV> getInnerMap(); /** * Checks whether the specified object might be an inner key. * <p> * This method does not have to be exact (which might be impossible due to involved generic types) and might produce * false positives (but no false negatives). * * @param object * the object to check; may be null * @return true if the object might be an inner key */ protected abstract boolean isInnerKey(Object object); /** * Transforms the specified key to an instance of the outer key type. * <p> * It can not be guaranteed that the specified key is really of the inner key type. If not, an exception can be * thrown. * * @param innerKey * the key to transform; may be null * @return the transformed key * @throws ClassCastException * if the specified key is not of the correct type */ protected abstract OK transformToOuterKey(IK innerKey) throws ClassCastException; /** * Checks whether the specified object might be an outer key. * <p> * This method does not have to be exact (which might be impossible due to involved generic types) and might produce * false positives (but no false negatives). * * @param object * the object to check; may be null * @return true if the object might be an outer key */ protected abstract boolean isOuterKey(Object object); /** * Transforms the specified key to an instance of the inner key type. * <p> * It can not be guaranteed that the specified key is really of the outer key type. If not, an exception can be * thrown. * * @param outerKey * the key to transform; may be null * @return the transformed key * @throws ClassCastException * if the specified key is not of the correct type */ protected abstract IK transformToInnerKey(OK outerKey) throws ClassCastException; /** * Checks whether the specified object might be an inner value. * <p> * This method does not have to be exact (which might be impossible due to involved generic types) and might produce * false positives (but no false negatives). * * @param object * the object to check; may be null * @return true if the object might be an inner value */ protected abstract boolean isInnerValue(Object object); /** * Transforms the specified value to an instance of the outer value type. * <p> * It can not be guaranteed that the specified value is really of the inner value type. If not, an exception can be * thrown. * * @param innerValue * the value to transform; may be null * @return the transformed value * @throws ClassCastException * if the specified value is not of the correct type */ protected abstract OV transformToOuterValue(IV innerValue) throws ClassCastException; /** * Checks whether the specified object might be an outer value. * <p> * This method does not have to be exact (which might be impossible due to involved generic types) and might produce * false positives (but no false negatives). * * @param object * the object to check; may be null * @return true if the object might be an outer value */ protected abstract boolean isOuterValue(Object object); /** * Transforms the specified value to an instance of the inner value type. * <p> * It can not be guaranteed that the specified value is really of the outer value type. If not, an exception can be * thrown. * * @param outerValue * the value to transform; may be null * @return the transformed value * @throws ClassCastException * if the specified value is not of the correct type */ protected abstract IV transformToInnerValue(OV outerValue) throws ClassCastException; // #end ABSTRACT METHODS // #begin INNER CLASSES /** * The view on this map's key set. * <p> * This view is a {@link TransformingSet} on the inner map's key set. */ private class KeySetView extends AbstractTransformingSet<IK, OK> { @Override protected Set<IK> getInnerSet() { return getInnerMap().keySet(); } @Override protected boolean isInnerElement(Object object) { return isInnerKey(object); } @Override protected OK transformToOuter(IK innerElement) { return transformToOuterKey(innerElement); } @Override protected boolean isOuterElement(Object object) { return isOuterKey(object); } @Override protected IK transformToInner(OK outerElement) { return transformToInnerKey(outerElement); } // prevent adding elements according to the contract of 'Map.keySet()' @Override public boolean add(OK element) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends OK> otherCollection) { throw new UnsupportedOperationException(); } } /** * The view on this map's values. * <p> * This view is a {@link TransformingCollection} on the inner map's values. */ private class ValueCollectionView extends AbstractTransformingCollection<IV, OV> { @Override protected Collection<IV> getInnerCollection() { return getInnerMap().values(); } @Override protected boolean isInnerElement(Object object) { return isInnerValue(object); } @Override protected OV transformToOuter(IV innerElement) { return transformToOuterValue(innerElement); } @Override protected boolean isOuterElement(Object object) { return isOuterValue(object); } @Override protected IV transformToInner(OV outerElement) { return transformToInnerValue(outerElement); } // prevent adding elements according to the contract of 'Map.values()' @Override public boolean add(OV element) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends OV> otherCollection) { throw new UnsupportedOperationException(); } // object @Override public boolean equals(Object object) { if (object == this) return true; if (!(object instanceof Collection)) return false; Collection<?> other = (Collection<?>) object; if (isThisCollection(other)) return true; return other.containsAll(this) && this.containsAll(other); } @Override public int hashCode() { int hashCode = 1; for (OV outerElement : this) hashCode = 31 * hashCode + (outerElement == null ? 0 : outerElement.hashCode()); return hashCode; } } /** * The view on this map's entry set. * <p> * This view is a {@link TransformingSet} on the inner map's entry set. */ private class EntrySetView extends AbstractTransformingSet<Entry<IK, IV>, Entry<OK, OV>> { @Override protected Set<Entry<IK, IV>> getInnerSet() { return getInnerMap().entrySet(); } @Override protected boolean isInnerElement(Object object) { if (!(object instanceof Entry<?, ?>)) // this also returns 'false' if object is 'null'; // that is correct because an entrySet can not contain null values return false; Entry<?, ?> entry = (Entry<?, ?>) object; return isInnerKey(entry.getKey()) && isInnerValue(entry.getValue()); } @Override protected Entry<OK, OV> transformToOuter(Entry<IK, IV> innerElement) { // the entry view is based on an inner view, which should never contain null Objects.requireNonNull(innerElement, "The argument 'innerElement' must not be null."); OK outerKey = transformToOuterKey(innerElement.getKey()); OV outerValue = transformToOuterValue(innerElement.getValue()); return new SimpleEntry<>(outerKey, outerValue); } @Override protected boolean isOuterElement(Object object) { if (!(object instanceof Entry<?, ?>)) // this also returns 'false' if object is 'null'; // that is correct because an entrySet can not contain null values return false; Entry<?, ?> entry = (Entry<?, ?>) object; return isOuterKey(entry.getKey()) && isOuterValue(entry.getValue()); } @Override protected Entry<IK, IV> transformToInner(Map.Entry<OK, OV> outerElement) { // someone might hand null to a method of this view (e.g. 'contains'); // since there can never be null values in an entry view of a map, this mapping can be fixed to null -> null; // the inner map's entry view will handle this case correctly if (outerElement == null) return null; IK innerKey = transformToInnerKey(outerElement.getKey()); IV innerValue = transformToInnerValue(outerElement.getValue()); return new SimpleEntry<>(innerKey, innerValue); } // prevent adding elements according to the contract of 'Map.entrySet()' @Override public boolean add(Entry<OK, OV> element) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends Entry<OK, OV>> otherCollection) { throw new UnsupportedOperationException(); } } private class TransformToReadOnlyInnerMap extends AbstractReadOnlyTransformingMap<OK, IK, OV, IV> { private final Map<? extends OK, ? extends OV> transformedMap; public TransformToReadOnlyInnerMap(Map<? extends OK, ? extends OV> transformedMap) { this.transformedMap = transformedMap; } // abstract methods @Override protected Map<OK, OV> getInnerMap() { @SuppressWarnings("unchecked") /* * This cast is not safe! But since this class only allows reading operations, it can not cause trouble. */ Map<OK, OV> unsafelyTypedMap = (Map<OK, OV>) transformedMap; return unsafelyTypedMap; } @Override protected boolean isInnerKey(Object object) { return AbstractTransformingMap.this.isOuterKey(object); } @Override protected IK transformToOuterKey(OK innerKey) { return AbstractTransformingMap.this.transformToInnerKey(innerKey); } @Override protected boolean isOuterKey(Object object) { return AbstractTransformingMap.this.isInnerKey(object); } @Override protected OK transformToInnerKey(IK outerKey) { return AbstractTransformingMap.this.transformToOuterKey(outerKey); } @Override protected boolean isInnerValue(Object object) { return AbstractTransformingMap.this.isOuterValue(object); } @Override protected IV transformToOuterValue(OV innerValue) { return AbstractTransformingMap.this.transformToInnerValue(innerValue); } @Override protected boolean isOuterValue(Object object) { return AbstractTransformingMap.this.isInnerValue(object); } @Override protected OV transformToInnerValue(IV outerValue) { return AbstractTransformingMap.this.transformToOuterValue(outerValue); } } // #end INNER CLASSES }