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