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

com.github.protobufel.multikeymap.BaseMultiKeyMap Maven / Gradle / Ivy

Go to download

Java 8 implementation of the multi-key map. It behaves like a regular generic Map with the additional ability of getting its values by any combination of partial keys.

The newest version!
/*
 *    Copyright 2017 David Tesler
 *
 *    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 com.github.protobufel.multikeymap;

import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;

import static com.github.protobufel.multikeymap.Collectors.intersectSets;

class BaseMultiKeyMap, V> implements MultiKeyMap, Serializable {
    private static final long serialVersionUID = 995884597801625434L;
    static boolean enableParallelStreaming = false;
    /**
     * The base map holding all the Map data
     *
     * @serial
     */
    private Map fullMap;
    private transient LiteSetMultimap partMap;
    private transient Set keySet;
    private transient Collection values;
    private transient Set> entrySet;

    BaseMultiKeyMap() {
        this(new HashMap<>(), LiteSetMultimap.newInstance());
    }

    BaseMultiKeyMap(final Map sourceMap) {
        this(new HashMap<>(Objects.requireNonNull(sourceMap)), LiteSetMultimap.newInstance());
    }

    BaseMultiKeyMap(final Map fullMap, final LiteSetMultimap partMap) {
        super();
        this.fullMap = Objects.requireNonNull(fullMap);
        this.partMap = Objects.requireNonNull(partMap);
    }

    private void writeObject(java.io.ObjectOutputStream out)
            throws IOException {
        out.writeObject(fullMap);
        out.writeBoolean(partMap.isConcurrent());
    }

    private void readObject(java.io.ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        final boolean concurrent = in.readBoolean();
        partMap = LiteSetMultimap.newInstance(concurrent);
        fullMap.forEach((k, v) -> putPartial(k));
    }

    private void readObjectNoData()
            throws ObjectStreamException {
        fullMap = new HashMap<>();
        partMap = LiteSetMultimap.newInstance();
    }

    static boolean isEnableParallelStreaming() {
        return enableParallelStreaming;
    }

    static void setEnableParallelStreaming(final boolean enableParallelStreaming) {
        BaseMultiKeyMap.enableParallelStreaming = enableParallelStreaming;
    }

    @Override
    public String toString() {
        return fullMap.toString();
    }

    // TODO remove or re-enable after comparing the performance w/ the Java 8 based
    // implementation
    // @Override
    // public Optional> getFullKeysByPartialKey(final Iterable partialKey) {
    // Objects.requireNonNull(partialKey);
    //
    // if (partMap.isEmpty()) {
    // return Optional.empty();
    // }
    //
    // final List> subResults = new ArrayList<>();
    // int minSize = Integer.MAX_VALUE;
    // int minPos = -1;
    //
    // for (final T subKey : partialKey) {
    // final Set subResult = partMap.get(Objects.requireNonNull(subKey));
    //
    // if (subResult.size() == 0) {
    // return Optional.empty();
    // } else if (subResult.size() < minSize) {
    // minSize = subResult.size();
    // minPos = subResults.size();
    // }
    //
    // subResults.add(subResult);
    // }
    //
    // if (subResults.isEmpty()) {
    // return Optional.empty();
    // }
    //
    // final Set result = new HashSet<>(subResults.get(minPos));
    //
    // if (subResults.size() == 1) {
    // return Optional.of(result.stream());
    // }
    //
    // for (int i = 0; i < subResults.size(); i++) {
    // if (i != minPos) {
    // if (result.retainAll(subResults.get(i)) && result.isEmpty()) {
    // return Optional.empty();
    // }
    // }
    // }
    //
    // return Optional.of(result.stream());
    // }

    @Override
    public Stream getFullKeysByPartialKey(final Iterable partialKey) {
        Objects.requireNonNull(partialKey);

        if (partMap.isEmpty()) {
            return Stream.empty();
        }

        if (!(partialKey instanceof Set)) {
            return getFullKeysByPartialKey(partialKey, Collections.emptyList());
        }

        // Java 8 doesn't allow to break the processing and also discourages stateful functions
        // untill Java 9 takeWhile!
        // final Set> sets = ((Set) partialKey).stream().unordered()
        // .map(subKey -> partMap.get(Objects.requireNonNull(subKey))).collect(toSet());

        final List> sets = new ArrayList<>();

        for (final T subKey : partialKey) {
            final Set set = partMap.get(Objects.requireNonNull(subKey));

            if (set == null) {
                return Stream.empty();
            }

            sets.add(set);
        }

        if (sets.isEmpty()) {
            return Stream.empty();
        }

        final Set result = intersectSets(sets, isEnableParallelStreaming());
        return result.isEmpty() ? Stream.empty() : result.stream();
    }

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

    @Override
    public boolean isEmpty() {
        return fullMap.isEmpty();
    }

    @Override
    public boolean containsKey(final Object key) {
        return fullMap.containsKey(key);
    }

    @Override
    public boolean containsValue(final Object value) {
        return fullMap.containsValue(value);
    }

    @Override
    public V get(final Object key) {
        return fullMap.get(Objects.requireNonNull(key));
    }

    @Override
    public V put(final K key, final V value) {
        Objects.requireNonNull(value);
        final Object[] oldValue = {null};

        fullMap.compute(key, (k, v) -> {
            if (v == null) {
                putPartial(k);
            } else {
                oldValue[0] = v;
            }

            return value;
        });

        @SuppressWarnings("unchecked") final V oldV = (V) oldValue[0];
        return oldV;
    }

    @Override
    public V remove(final Object key) {
        @SuppressWarnings("unchecked") final K fullKey = (K) key;
        final Object[] oldValue = {null};
        fullMap.computeIfPresent(fullKey, (k, v) -> {
            deletePartial(k);
            oldValue[0] = v;
            return null;
        });

        @SuppressWarnings("unchecked") final V oldV = (V) oldValue[0];
        return oldV;
    }

    @Override
    public void putAll(final Map m) {
        for (final Map.Entry entry : Objects.requireNonNull(m).entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public void clear() {
        fullMap.clear();
        partMap.clear();
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        }

        if (!(o instanceof Map)) {
            return false;
        }

        return fullMap.equals(o);
    }

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

    private void putPartial(final K key) {
        for (final T subKey : key) {
            partMap.put(subKey, key);
        }
    }

    private void deletePartial(final K key) {
        for (final T subKey : key) {
            partMap.remove(subKey, key);
        }
    }

    @Override
    public Collection values() {
        if (values == null) {
            values = new Values();
        }

        return values;
    }

    @Override
    public Set keySet() {
        if (keySet == null) {
            keySet = new KeySet();
        }

        return keySet;
    }

    @Override
    public Set> entrySet() {
        if (entrySet == null) {
            entrySet = new EntrySet();
        }

        return entrySet;
    }

    final class Values extends AbstractCollection {

        @Override
        public Iterator iterator() {
            return new ValueIterator(fullMap.entrySet().iterator());
        }

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

        @Override
        public Spliterator spliterator() {
            return fullMap.values().spliterator();
        }

        @Override
        public void forEach(final Consumer action) {
            fullMap.values().forEach(action);
        }

        @Override
        public boolean contains(final Object o) {
            return fullMap.values().contains(o);
        }

        @Override
        public void clear() {
            BaseMultiKeyMap.this.clear();
        }
    }

    final class ValueIterator implements Iterator {
        private final Iterator> it;
        private Entry current;

        public ValueIterator(final Iterator> it) {
            super();
            this.it = it;
        }

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        @Override
        public V next() {
            current = it.next();
            return current.getValue();
        }

        @Override
        public void remove() {
            it.remove();
            deletePartial(current.getKey());
        }
    }

    final class KeySet extends AbstractSet {

        @Override
        public Iterator iterator() {
            return new KeySetIterator(fullMap.keySet().iterator());
        }

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

        @Override
        public Spliterator spliterator() {
            return fullMap.keySet().spliterator();
        }

        @Override
        public void forEach(final Consumer action) {
            fullMap.keySet().forEach(action);
        }

        @Override
        public boolean contains(final Object o) {
            return fullMap.keySet().contains(o);
        }

        @Override
        public boolean remove(final Object o) {
            if (fullMap.keySet().remove(o)) {
                @SuppressWarnings("unchecked") final K key = (K) o;
                deletePartial(key);
                return true;
            }

            return false;
        }

        @Override
        public void clear() {
            BaseMultiKeyMap.this.clear();
        }
    }

    final class KeySetIterator implements Iterator {
        private final Iterator it;
        private K current;

        public KeySetIterator(final Iterator it) {
            super();
            this.it = it;
        }

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        @Override
        public K next() {
            current = it.next();
            return current;
        }

        @Override
        public void remove() {
            it.remove();
            deletePartial(current);
        }
    }

    final class EntrySet extends AbstractSet> {

        @Override
        public Iterator> iterator() {
            return new EntrySetIterator(fullMap.entrySet().iterator());
        }

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

        @Override
        public Spliterator> spliterator() {
            return fullMap.entrySet().spliterator();
        }

        @Override
        public void forEach(final Consumer> action) {
            fullMap.entrySet().forEach(action);
        }

        @Override
        public boolean contains(final Object o) {
            return fullMap.entrySet().contains(o);
        }

        @Override
        public boolean remove(final Object o) {
            if (fullMap.entrySet().remove(o)) {
                @SuppressWarnings("unchecked") final Entry entry = (Entry) o;
                deletePartial(entry.getKey());
                return true;
            }

            return false;
        }

        @Override
        public void clear() {
            BaseMultiKeyMap.this.clear();
        }
    }

    final class EntrySetIterator implements Iterator> {
        private final Iterator> it;
        private Entry current;

        public EntrySetIterator(final Iterator> it) {
            super();
            this.it = it;
        }

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        @Override
        public Entry next() {
            current = it.next();
            return current;
        }

        @Override
        public void remove() {
            it.remove();
            deletePartial(current.getKey());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy