001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2010–2011 ApexIdentity Inc.
015 * Portions Copyright 2011-2014 ForgeRock AS.
016 */
017
018package org.forgerock.openig.util;
019
020import java.util.AbstractMap;
021import java.util.AbstractSet;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.Set;
026
027/**
028 * A {@link FieldMap} that can be extended with arbitrary keys. If the key maps
029 * to a key exposed by the field map, the field map is used, otherwise the key
030 * is handled in this implementation. The backing map is a {@link HashMap} with
031 * default initial capacity and load factor.
032 */
033public class ExtensibleFieldMap extends AbstractMap<String, Object> implements Map<String, Object> {
034
035    /** Map to store fields. */
036    private final FieldMap fields;
037
038    /** Map to store extended keys. */
039    private final Map<String, Object> extension = new HashMap<String, Object>();
040
041    /**
042     * Constructs a new extensible field map, using this object's field members
043     * as keys. This is only useful in the case where a class subclasses
044     * {@code ExtensibleFieldMap}.
045     */
046    public ExtensibleFieldMap() {
047        fields = new FieldMap(this);
048    }
049
050    /** The Map entrySet view. */
051    private final Set<Entry<String, Object>> entrySet = new AbstractSet<Entry<String, Object>>() {
052        @Override
053        public void clear() {
054            ExtensibleFieldMap.this.clear();
055        }
056
057        @Override
058        public boolean contains(final Object o) {
059            return (o instanceof Entry)
060                    && ExtensibleFieldMap.this.containsKey(((Entry<?, ?>) o).getKey());
061        }
062
063        @Override
064        public boolean isEmpty() {
065            return ExtensibleFieldMap.this.isEmpty();
066        }
067
068        @Override
069        public Iterator<Entry<String, Object>> iterator() {
070            return new Iterator<Entry<String, Object>>() {
071                final Iterator<Entry<String, Object>> fieldIterator = fields.entrySet().iterator();
072                final Iterator<Entry<String, Object>> extensionIterator = extension.entrySet()
073                        .iterator();
074                Iterator<Entry<String, Object>> currentIterator = fieldIterator;
075
076                @Override
077                public boolean hasNext() {
078                    return fieldIterator.hasNext() || extensionIterator.hasNext();
079                }
080
081                @Override
082                public Entry<String, Object> next() {
083                    if (!currentIterator.hasNext() && currentIterator != extensionIterator) {
084                        currentIterator = extensionIterator;
085                    }
086                    return currentIterator.next();
087                }
088
089                @Override
090                public void remove() {
091                    currentIterator.remove();
092                }
093            };
094        }
095
096        @Override
097        public boolean remove(final Object o) {
098            return (o instanceof Entry)
099                    && ExtensibleFieldMap.this.remove(((Entry<?, ?>) o).getKey()) != null;
100        }
101
102        @Override
103        public int size() {
104            return ExtensibleFieldMap.this.size();
105        }
106    };
107
108    /**
109     * Constructs a new extensible field map, using the specified object's field
110     * members as keys.
111     *
112     * @param object the object whose field members are to be exposed in the map.
113     */
114    public ExtensibleFieldMap(Object object) {
115        fields = new FieldMap(object);
116    }
117
118    @Override
119    public Object get(Object key) {
120        return fields.containsKey(key) ? fields.get(key) : extension.get(key);
121    }
122
123    @Override
124    public boolean containsKey(Object key) {
125        return fields.containsKey(key) || extension.containsKey(key);
126    }
127
128    @Override
129    public Object remove(Object key) {
130        return fields.containsKey(key) ? fields.remove(key) : extension.remove(key);
131    }
132
133    @Override
134    public void clear() {
135        fields.clear();
136        extension.clear();
137    }
138
139    @Override
140    public int size() {
141        return fields.size() + extension.size();
142    }
143
144    @Override
145    public Set<Map.Entry<String, Object>> entrySet() {
146        return entrySet;
147    }
148
149    @Override
150    public boolean isEmpty() {
151        return fields.isEmpty() && extension.isEmpty();
152    }
153
154    @Override
155    public Object put(String key, Object value) {
156        return fields.containsKey(key) ? fields.put(key, value) : extension.put(key, value);
157    }
158}