001package ball.util;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import java.io.Serializable;
022import java.util.AbstractMap;
023import java.util.ArrayList;
024import java.util.Iterator;
025import java.util.LinkedHashSet;
026import java.util.Map;
027import java.util.Set;
028
029/**
030 * {@link Map} implementation base class to provide a view of a wrapped
031 * {@link Map}.  Subclass designers should override {@link #entrySet()}
032 * first.  All implemented methods simply pass the call to the wrapped
033 * {@link Map} (all other methods are implemented by {@link AbstractMap}).
034 *
035 * @param       <K>             The key type.
036 * @param       <V>             The value type.
037 *
038 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
039 */
040public class MapView<K,V> extends AbstractMap<K,V> implements Serializable {
041    private static final long serialVersionUID = -1914866590078121842L;
042
043    /** @serial */ private final Map<K,V> map;
044    /** @serial */ protected final EntrySet entrySet = new EntrySet();
045
046    /**
047     * Sole constructor.
048     *
049     * @param   map             The {@link Map}.
050     */
051    public MapView(Map<K,V> map) {
052        super();
053
054        this.map = map;
055    }
056
057    /**
058     * @return  The "wrapped" {@link Map}.
059     */
060    protected Map<K,V> map() { return map; }
061
062    @Override
063    public V get(Object key) { return map().get(key); }
064
065    @Override
066    public V put(K key, V value) { return map().put(key, value); }
067
068    @Override
069    public V remove(Object key) { return map().remove(key); }
070
071    @Override
072    public void clear() { map().clear(); }
073
074    /**
075     * Method returns a single {@link Set} backed by {@link #map()}.
076     * Subclass implementations should update {@link #entrySet}.
077     *
078     * @return  {@link #entrySet}
079     */
080    @Override
081    public Set<Entry<K,V>> entrySet() {
082        entrySet.clear();
083        entrySet.addAll(map().entrySet());
084
085        return entrySet;
086    }
087
088    /**
089     * {@link #entrySet} implementation class.
090     */
091    protected class EntrySet extends LinkedHashSet<Entry<K,V>> {
092        private static final long serialVersionUID = 5015923978722180032L;
093
094        /**
095         * Sole constructor.
096         */
097        public EntrySet() { super(); }
098
099        @Override
100        public boolean remove(Object object) {
101            boolean changed = super.remove(object);
102
103            if (changed) {
104                MapView.this.remove(((Entry<?,?>) object).getKey());
105            }
106
107            return changed;
108        }
109
110        @Override
111        public Iterator<Entry<K,V>> iterator() {
112            ArrayList<Entry<K,V>> list = new ArrayList<>();
113            Iterator<Entry<K,V>> iterator = super.iterator();
114
115            while (iterator.hasNext()) {
116                list.add(iterator.next());
117            }
118
119            return new EntryIterator(list.iterator());
120        }
121    }
122
123    private class EntryIterator implements Iterator<Entry<K,V>> {
124        private final Iterator<Entry<K,V>> iterator;
125        private Entry<K,V> current = null;
126
127        public EntryIterator(Iterator<Entry<K,V>> iterator) {
128            this.iterator = iterator;
129        }
130
131        @Override
132        public boolean hasNext() { return iterator.hasNext(); }
133
134        @Override
135        public Entry<K,V> next() {
136            current = iterator.next();
137
138            return current;
139        }
140
141        @Override
142        public void remove() {
143            iterator.remove();
144            MapView.this.remove(current.getKey());
145        }
146
147        @Override
148        public String toString() { return iterator.toString(); }
149    }
150}