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

org.organicdesign.fp.collections.UnmodMap Maven / Gradle / Ivy

Go to download

Immutable Clojure collections and a Transformation abstraction for Java 8+, immutably, type-safely, and with good performance. Name will change to "Paguro" in November 2016.

The newest version!
// Copyright 2015-04-13 PlanBase Inc. & Glen Peterson
//
// 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 org.organicdesign.fp.collections;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.organicdesign.fp.tuple.Tuple2;

/**
 An unmodifiable map.
 This cannot extend Collection because the remove() method would then be inherited
 from both Collection and Map and Collection.remove() returns a boolean while Map.remove() returns
 a V (the type of the value in the key/value pair).  Maybe an UnmodSizedIterable is called for?
 */
public interface UnmodMap extends Map, UnmodIterable>, Sized {
    // ========================================== Static ==========================================

    /**
     Implements equals and hashCode() methods compatible with java.util.Map (which ignores order)
     to make defining unmod Maps easier.  Inherits hashCode() and toString() from
     AbstractUnmodIterable.
     */
    abstract class AbstractUnmodMap extends AbstractUnmodIterable>
            implements UnmodMap {

        @Override public boolean equals(Object other) {
            if (this == other) { return true; }
            if (!(other instanceof Map)) { return false; }

            Map that = (Map) other;
            if (that.size() != size()) { return false; }

            try {
                for (Entry e : this) {
                    K key = e.getKey();
                    V value = e.getValue();
                    if (value == null) {
                        if (!(that.get(key) == null && that.containsKey(key))) {
                            return false;
                        }
                    } else {
                        if (!value.equals(that.get(key))) {
                            return false;
                        }
                    }
                }
            } catch (ClassCastException unused) {
                return false;
            } catch (NullPointerException unused) {
                return false;
            }
            return true;
        }
    }

    /**
     * A map entry (key-value pair).  The UnmodMap.entrySet method returns
     * a collection-view of the map, whose elements are of this class.  The
     * only way to obtain a reference to a map entry is from the
     * iterator of this collection-view.
     *
     * @see UnmodMap#entrySet()
     */
    interface UnEntry extends Map.Entry {
        class EntryToUnEntryIter implements UnmodIterator> {
            //, Serializable {
            // For serializable.  Make sure to change whenever internal data format changes.
            // private static final long serialVersionUID = 20160903082500L;

            private final Iterator> innerIter;
            EntryToUnEntryIter(Iterator> i) { innerIter = i; }

            @Override public boolean hasNext() { return innerIter.hasNext(); }
            @Override public UnEntry next() {
                class Wrapper implements UnEntry, Serializable {
                    // For serializable.  Make sure to change whenever internal data format changes.
                    private static final long serialVersionUID = 20160903082500L;

                    private final Entry entry;
                    private Wrapper(Entry e) { entry = e; }
                    @Override public K getKey() { return entry.getKey(); }
                    @Override public V getValue() { return entry.getValue(); }
                    @Override
                    public boolean equals(Object other) {
                        if (this == other) { return true; }
                        if ( !(other instanceof Entry) ) { return false; }

                        Entry that = (Entry) other;
                        return Objects.equals(entry.getKey(), that.getKey()) &&
                               Objects.equals(entry.getValue(), that.getValue());
                    }

                    @Override public int hashCode() {
                        return entry.hashCode();
                    }

                    @Override public String toString() {
                        return "entry(" + entry.getKey() + "," + entry.getValue() + ")";
                    }
                };

                return new Wrapper(innerIter.next());
            }
        }

        class EntryToUnEntrySortedIter extends EntryToUnEntryIter
                implements UnmodSortedIterator> {
            //, Serializable {
            // For serializable.  Make sure to change whenever internal data format changes.
//            private static final long serialVersionUID = 20160903082500L;

            EntryToUnEntrySortedIter(Iterator> i) { super(i); }
        }

        class UnmodKeyIter implements UnmodIterator {
            //, Serializable {
            // For serializable.  Make sure to change whenever internal data format changes.
            // private static final long serialVersionUID = 20160903174100L;

            private final Iterator> iter;
            UnmodKeyIter(Iterator> i) { iter = i; }

            @Override public boolean hasNext() { return iter.hasNext(); }
            @Override public K next() { return iter.next().getKey(); }
        }

        class UnmodSortedKeyIter extends UnmodKeyIter implements UnmodSortedIterator {
            // , Serializable {
            // For serializable.  Make sure to change whenever internal data format changes.
//            private static final long serialVersionUID = 20160903174100L;

            UnmodSortedKeyIter(Iterator> i) { super(i); }
        }

        class UnmodValIter implements UnmodIterator {
            //, Serializable {
            // For serializable.  Make sure to change whenever internal data format changes.
            // private static final long serialVersionUID = 20160903174100L;

            private final Iterator> iter;
            UnmodValIter(Iterator> i) { iter = i; }

            @Override public boolean hasNext() { return iter.hasNext(); }
            @Override public V next() { return iter.next().getValue(); }
        }

        class UnmodSortedValIter extends UnmodValIter implements UnmodSortedIterator {
                // , Serializable {
            // For serializable.  Make sure to change whenever internal data format changes.
//            private static final long serialVersionUID = 20160903174100L;

            UnmodSortedValIter(Iterator> i) { super(i); }
        }

        /**
         Use {@link org.organicdesign.fp.tuple.Tuple2#of(java.util.Map.Entry)} instead.
         */
        @Deprecated
        static  UnEntry entryToUnEntry(Map.Entry entry) {
            return Tuple2.of(entry);
        }

        static 
        UnmodIterator> entryIterToUnEntryUnIter(Iterator> innerIter) {
            return new EntryToUnEntryIter<>(innerIter);
        }

        static 
        UnmodSortedIterator>
        entryIterToUnEntrySortedUnIter(Iterator> innerIter) {
            return new EntryToUnEntrySortedIter<>(innerIter);
        }

        // This should be done with a cast, not with code.
//        static  UnmodSortedIterator> unSortIterEntToUnSortIterUnEnt(
//                UnmodSortedIterator> innerIter) {
//            return new UnmodSortedIterator>() {
//                @Override public boolean hasNext() { return innerIter.hasNext(); }
//                @Override public UnEntry next() {
//                    return UnmodMap.UnEntry.entryToUnEntry(innerIter.next());
//                }
//            };
//        }
//
        /** Not allowed - this is supposed to be unmodifiable */
        @SuppressWarnings("deprecation")
        @Override @Deprecated default V setValue(V value) {
            throw new UnsupportedOperationException("Modification attempted");
        }
    }

    // ========================================= Instance =========================================

    // Modification Operations

    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated default void clear() {
        throw new UnsupportedOperationException("Modification attempted");
    }

    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated
    default V compute(K key, BiFunction remappingFunction) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated
    default V computeIfAbsent(K key, Function mappingFunction) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated
    default V computeIfPresent(K key,
                               BiFunction remappingFunction) {
        throw new UnsupportedOperationException("Modification attempted");
    }
// boolean	containsKey(Object key)
// boolean	containsValue(Object value)

    /**
     Most maps are not designed for this - the default implementation has O(n) performance.
     {@inheritDoc}
     */
    // This is the place to define this slow operation so that it can be used in
    // values().contains(), UnmodSortedMap.containsValue() and UnmodSortedMap.values().contains().
    @SuppressWarnings("SuspiciousMethodCalls")
    @Override default boolean containsValue(Object value) {
        for (UnEntry item : this) {
            if (Objects.equals(item.getValue(), value)) { return true; }
        }
        return false;
    }

    /**
     Returns a view of the mappings contained in this map.  The set will contain UnmodMap.UnEntry
     items, but that return signature is illegal in Java, so you'll just have to remember. An
     UnmodMap is iterable, so this method is probably not nearly as useful as it once was.

     {@inheritDoc}
     */
    @Override default UnmodSet> entrySet() {
        class EntrySet extends UnmodSet.AbstractUnmodSet>
                implements Serializable {
            // For serializable.  Make sure to change whenever internal data format changes.
            private static final long serialVersionUID = 20160903104400L;

            private final UnmodMap parent;
            private EntrySet(UnmodMap p) { parent = p; }

            @SuppressWarnings("unchecked")
            @Override public boolean contains(Object o) {
                if ( !(o instanceof Entry) ) { return false; }
                Entry entry = (Entry) o;
                if (!parent.containsKey(entry.getKey())) { return false; }
                V value = parent.get(entry.getKey());
                return Objects.equals(entry.getValue(), value);
            }

            @SuppressWarnings("unchecked")
            @Override public UnmodIterator> iterator() {
                // Converting from
                // UnmodIterator> to
                // UnmodIterator>
                // Is a totally legal widening conversion (at runtime) because UnEntry extends
                // (is an) Entry.  But Java's type system doesn't know that because (I think)
                // it's a higher kinded type.  Thanks to type erasure, we can forget about all
                // that and cast it to a base type then suppress the unchecked warning.
                //
                // Hmm... It's possible for this to return an Entry if the wrapped collection
                // uses them...  Not sure how much that matters.
                return (UnmodIterator) parent.iterator();
            }

            @Override public int size() { return parent.size(); }

            @Override public String toString() {
                return UnmodIterable.toString("UnmodMap.entrySet", this);
            }
        }
        return new EntrySet(this);
    }


// boolean	equals(Object o)

//    @Override default boolean equals(Object other) {
//        // Cheapest operation first...
//        if (this == other) { return true; }
//
//        if ( (other == null) ||
//                !(other instanceof Map) ||
//                (this.hashCode() != other.hashCode()) ) {
//            return false;
//        }
//        // Details...
//        final Map that = (Map) other;
//        if (this.size() != that.size()) {
//            return false;
//        }
//        return this.entrySet().containsAll(that.entrySet());
//    }

// default void	forEach(BiConsumer action)
// V	get(Object key)
// default V	getOrDefault(Object key, V defaultValue)

//    @Override default int hashCode() {
//        if (size() == 0) { return 0; }
//        return Arrays.hashCode(entrySet().toArray());
//    };

    /** {@inheritDoc} */
    @Override default boolean isEmpty() { return size() == 0; }

    /**
     Returns a view of the keys contained in this map.  An UnmodMap is iterable, so this method
     is probably not nearly as useful as it once was.

     {@inheritDoc}
     */
    @Override default UnmodSet keySet() {
        class KeySet extends UnmodSet.AbstractUnmodSet implements Serializable {
            // For serializable.  Make sure to change whenever internal data format changes.
            private static final long serialVersionUID = 20160903104400L;

            private final UnmodMap parent;
            private KeySet(UnmodMap p) { parent = p; }

            @SuppressWarnings("SuspiciousMethodCalls")
            @Override public boolean contains(Object o) { return parent.containsKey(o); }

            @Override public UnmodIterator iterator() {
                return new UnEntry.UnmodKeyIter<>(parent.iterator());
            }
            @Override public int size() { return parent.size(); }

            @Override public String toString() {
                return UnmodIterable.toString("UnmodMap.keySet", this);
            }
        }
        return new KeySet(this);
    }

    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated
    default V merge(K key, V value,
                    BiFunction remappingFunction) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated default V put(K key, V value) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated default void putAll(Map m) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated default V putIfAbsent(K key, V value) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated default V remove(Object key) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated default boolean remove(Object key, Object value) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated default boolean replace(K key, V oldValue, V newValue) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated default V replace(K key, V value) {
        throw new UnsupportedOperationException("Modification attempted");
    }
    /** Not allowed - this is supposed to be unmodifiable */
    @Override @Deprecated
    default void replaceAll(BiFunction function) {
        throw new UnsupportedOperationException("Modification attempted");
    }

// int	size()

    /**
     This method has been deprecated because it is impossible to implement equals() or hashCode()
     on the resulting collection, and calling this method is probably at least a missed opportunity,
     if not an outright error.  Use an UnmodMap as an UnmodIterable<UnmodMap.UnEntry> instead.

     If you don't care about eliminating duplicate values, and want a compatible return type call:
     
myMap.map((UnEntry<K,V> entry) -> entry.getValue())
             .toImSet();
If you want to keep a count of duplicates, try something like this, but it has a different signature:
ImMap<V,Integer> valueCounts = myMap.foldLeft(PersistentHashMap.empty(),
                     (ImMap<V,Integer> accum, UnEntry<K,V> origEntry) -> {
                             V inVal = origEntry.getValue();
                             return accum.assoc(inVal,
                                                accum.getOrElse(inVal, 0) + 1);
                         });
You really shouldn't turn values() into a List, because a List has order and an unsorted Map is unordered by key, and especially unordered by value. On a SortedMap, List is the proper return type. java.util.HashMap.values() returns an instance of java.util.HashMap.Values which does *not* have equals() or hashCode() defined. This is because List.equals() and Set.equals() return not-equal when compared to a Collection. There is no good way to implement a reflexive equals with both of those because they are just too different. Ultimately, Collection just isn't specific enough to instantiate, but we do it anyway here for backward compatibility. We don't implement equals() or hashCode() either because the result could have duplicates. If the Map isn't sorted, the result could have random ordering. {@inheritDoc} */ @Deprecated @Override default UnmodCollection values() { class Impl implements UnmodCollection, Serializable { // For serializable. Make sure to change whenever internal data format changes. private static final long serialVersionUID = 20160903104400L; private final UnmodMap parent; private Impl(UnmodMap p) { parent = p; } @SuppressWarnings("SuspiciousMethodCalls") @Override public boolean contains(Object o) { return parent.containsValue(o); } @Override public UnmodIterator iterator() { return new UnEntry.UnmodValIter<>(parent.iterator()); } @Override public int size() { return parent.size(); } @Override public String toString() { return UnmodIterable.toString("UnmodMap.values", this); } }; return new Impl(this); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy