001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.utils;
023
024import java.util.AbstractMap;
025import java.util.AbstractSet;
026import java.util.HashMap;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.NoSuchElementException;
030import java.util.Set;
031
032/**
033 * <p>
034 * Overlap one map onto another. This allows you to have a map with local values
035 * and default values. The local and default values are provided by a child and
036 * parent map.
037 * </p>
038 *
039 * @author Thomas Down
040 * @author Matthew Pocock
041 */
042
043public class OverlayMap extends AbstractMap {
044    private Map parent;
045    private Map overlay;
046
047    /**
048     * Build a new map with default key-value pairs.
049     *
050     * @param parent  the default fall-through Map
051     * @param overlay the overriding Map
052     */
053    public OverlayMap(Map parent, Map overlay) {
054        super();
055        this.parent = parent;
056        this.overlay = overlay;
057    }
058
059    /**
060     * Build a new map with default key-value pairs.
061     *
062     * @param parent  the default fall-through Map
063     */
064    public OverlayMap(Map parent) {
065        super();
066        this.parent = parent;
067        this.overlay = new HashMap();
068    }
069
070    /**
071     * Return the object containing the fallback mappings.
072     * This is the actual parent map, not a copy.
073     *
074     * @return the parent map
075     */
076
077    public Map getParentMap() {
078        return parent;
079    }
080
081    /**
082     * Return the object containing the overlay mappings.
083     * This is the actual child map, not a copy.
084     *
085     * @return the child map
086     */
087
088    public Map getOverlayMap() {
089        return overlay;
090    }
091
092    //
093    // Basic map operations, done explicitly
094    //
095
096    public Object get(Object key) {
097        Object value = overlay.get(key);
098        if (value == null)
099            value = parent.get(key);
100        return value;
101    }
102
103    public Set entrySet() {
104        return new OEntrySet();
105    }
106
107    public Set keySet() {
108        return new OKeySet();
109    }
110
111    public boolean containsKey(Object key) {
112        return overlay.containsKey(key) || parent.containsKey(key);
113    }
114
115    public Object put(Object key, Object value) {
116        Object old = get(key);
117        overlay.put(key, value);
118        return old;
119    }
120
121    private class OKeySet extends AbstractSet {
122        private Set parentKeys;
123        
124        private OKeySet() {
125            super();
126            parentKeys = parent.keySet();
127        }
128     
129        public Iterator iterator() {
130            return new Iterator() {
131                    Iterator oi = overlay.keySet().iterator();
132                    Iterator pi = parentKeys.iterator();
133                    Object peek = null;
134                    
135                    public boolean hasNext() {
136                        if (peek == null)
137                            peek = nextObject();
138                        return (peek != null);
139                    }
140         
141                    public Object next() {
142                        if (peek == null) {
143                            peek = nextObject();
144                        }
145                        if (peek == null) {
146                            throw new NoSuchElementException();
147                        }
148                        Object o = peek;
149                        peek = null;
150                        return o;
151                    }
152         
153                    private Object nextObject() {
154                        if (oi.hasNext()) {
155                            return oi.next();
156                        }
157                        Object po = null;
158                        while (po == null && pi.hasNext()) {
159                            po = pi.next();
160                            if (overlay.containsKey(po)) {
161                                po = null;
162                            }
163                        }
164                        return po;
165                    }
166                    
167                    public void remove() {
168                        throw new UnsupportedOperationException();
169                    }
170                };
171        }
172     
173        public int size() {
174            int i = 0;
175            Iterator keys = iterator();
176            while(keys.hasNext()) {
177                keys.next();
178                ++i;
179            }
180            return i;
181        }
182     
183        public boolean contains(Object o) {
184            return overlay.containsKey(o) || parentKeys.contains(o);
185        }
186    }
187
188    private class OEntrySet extends AbstractSet {
189        OKeySet ks;
190        
191        private OEntrySet() {
192            super();
193            ks = new OKeySet();
194        }
195    
196        public Iterator iterator() {
197            return new Iterator() {
198                Iterator ksi = ks.iterator();
199                    
200                public boolean hasNext() {
201                    return ksi.hasNext();
202                }
203                    
204                public Object next() {
205                    Object k = ksi.next();
206                    Object v = get(k);
207                    return new OMapEntry(k, v);
208                }
209        
210                public void remove() {
211                    throw new UnsupportedOperationException();
212                }
213            };
214        }
215    
216        public int size() {
217            return ks.size();
218        }
219    }
220    
221    private static class OMapEntry implements Map.Entry {
222        private Object key;
223        private Object value;
224    
225        private OMapEntry(Object key, Object value) {
226            this.key = key;
227            this.value = value;
228        }
229    
230        public Object getKey() {
231            return key;
232        }
233    
234        public Object getValue() {
235            return value;
236        }
237    
238        public Object setValue(Object v) {
239            throw new UnsupportedOperationException();
240        }
241    
242        public boolean equals(Object o) {
243            if (! (o instanceof Map.Entry)) {
244                return false;
245            }
246      
247            Map.Entry mo = (Map.Entry) o;
248            return ((key == null ? mo.getKey() == null : key.equals(mo.getKey())) &&
249                    (value == null ? mo.getValue() == null : value.equals(mo.getValue())));
250        }
251    
252        public int hashCode() {
253            return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
254        }
255    }
256}