001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ------------------
028 * KeyToGroupMap.java
029 * ------------------
030 * (C) Copyright 2004-present, by David Gilbert and Contributors.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.data;
038
039import java.io.Serializable;
040import java.lang.reflect.Method;
041import java.lang.reflect.Modifier;
042import java.util.ArrayList;
043import java.util.Collection;
044import java.util.HashMap;
045import java.util.Iterator;
046import java.util.List;
047import java.util.Map;
048import java.util.Objects;
049import org.jfree.chart.util.Args;
050import org.jfree.chart.util.PublicCloneable;
051
052/**
053 * A class that maps keys (instances of {@code Comparable}) to groups.
054 */
055public class KeyToGroupMap implements Cloneable, PublicCloneable, Serializable {
056
057    /** For serialization. */
058    private static final long serialVersionUID = -2228169345475318082L;
059
060    /** The default group. */
061    private Comparable defaultGroup;
062
063    /** The groups. */
064    private List groups;
065
066    /** A mapping between keys and groups. */
067    private Map keyToGroupMap;
068
069    /**
070     * Creates a new map with a default group named 'Default Group'.
071     */
072    public KeyToGroupMap() {
073        this("Default Group");
074    }
075
076    /**
077     * Creates a new map with the specified default group.
078     *
079     * @param defaultGroup  the default group ({@code null} not permitted).
080     */
081    public KeyToGroupMap(Comparable defaultGroup) {
082        Args.nullNotPermitted(defaultGroup, "defaultGroup");
083        this.defaultGroup = defaultGroup;
084        this.groups = new ArrayList();
085        this.keyToGroupMap = new HashMap();
086    }
087
088    /**
089     * Returns the number of groups in the map.
090     *
091     * @return The number of groups in the map.
092     */
093    public int getGroupCount() {
094        return this.groups.size() + 1;
095    }
096
097    /**
098     * Returns a list of the groups (always including the default group) in the
099     * map.  The returned list is independent of the map, so altering the list
100     * will have no effect.
101     *
102     * @return The groups (never {@code null}).
103     */
104    public List getGroups() {
105        List result = new ArrayList();
106        result.add(this.defaultGroup);
107        Iterator iterator = this.groups.iterator();
108        while (iterator.hasNext()) {
109            Comparable group = (Comparable) iterator.next();
110            if (!result.contains(group)) {
111                result.add(group);
112            }
113        }
114        return result;
115    }
116
117    /**
118     * Returns the index for the group.
119     *
120     * @param group  the group.
121     *
122     * @return The group index (or -1 if the group is not represented within
123     *         the map).
124     */
125    public int getGroupIndex(Comparable group) {
126        int result = this.groups.indexOf(group);
127        if (result < 0) {
128            if (this.defaultGroup.equals(group)) {
129                result = 0;
130            }
131        }
132        else {
133            result = result + 1;
134        }
135        return result;
136    }
137
138    /**
139     * Returns the group that a key is mapped to.
140     *
141     * @param key  the key ({@code null} not permitted).
142     *
143     * @return The group (never {@code null}, returns the default group if
144     *         there is no mapping for the specified key).
145     */
146    public Comparable getGroup(Comparable key) {
147        Args.nullNotPermitted(key, "key");
148        Comparable result = this.defaultGroup;
149        Comparable group = (Comparable) this.keyToGroupMap.get(key);
150        if (group != null) {
151            result = group;
152        }
153        return result;
154    }
155
156    /**
157     * Maps a key to a group.
158     *
159     * @param key  the key ({@code null} not permitted).
160     * @param group  the group ({@code null} permitted, clears any
161     *               existing mapping).
162     */
163    public void mapKeyToGroup(Comparable key, Comparable group) {
164        Args.nullNotPermitted(key, "key");
165        Comparable currentGroup = getGroup(key);
166        if (!currentGroup.equals(this.defaultGroup)) {
167            if (!currentGroup.equals(group)) {
168                int count = getKeyCount(currentGroup);
169                if (count == 1) {
170                    this.groups.remove(currentGroup);
171                }
172            }
173        }
174        if (group == null) {
175            this.keyToGroupMap.remove(key);
176        }
177        else {
178            if (!this.groups.contains(group)) {
179                if (!this.defaultGroup.equals(group)) {
180                    this.groups.add(group);
181                }
182            }
183            this.keyToGroupMap.put(key, group);
184        }
185    }
186
187    /**
188     * Returns the number of keys mapped to the specified group.  This method
189     * won't always return an accurate result for the default group, since
190     * explicit mappings are not required for this group.
191     *
192     * @param group  the group ({@code null} not permitted).
193     *
194     * @return The key count.
195     */
196    public int getKeyCount(Comparable group) {
197        Args.nullNotPermitted(group, "group");
198        int result = 0;
199        Iterator iterator = this.keyToGroupMap.values().iterator();
200        while (iterator.hasNext()) {
201            Comparable g = (Comparable) iterator.next();
202            if (group.equals(g)) {
203                result++;
204            }
205        }
206        return result;
207    }
208
209    /**
210     * Tests the map for equality against an arbitrary object.
211     *
212     * @param obj  the object to test against ({@code null} permitted).
213     *
214     * @return A boolean.
215     */
216    @Override
217    public boolean equals(Object obj) {
218        if (obj == this) {
219            return true;
220        }
221        if (!(obj instanceof KeyToGroupMap)) {
222            return false;
223        }
224        KeyToGroupMap that = (KeyToGroupMap) obj;
225        if (!Objects.equals(this.defaultGroup, that.defaultGroup)) {
226            return false;
227        }
228        if (!this.keyToGroupMap.equals(that.keyToGroupMap)) {
229            return false;
230        }
231        return true;
232    }
233
234    /**
235     * Returns a clone of the map.
236     *
237     * @return A clone.
238     *
239     * @throws CloneNotSupportedException  if there is a problem cloning the
240     *                                     map.
241     */
242    @Override
243    public Object clone() throws CloneNotSupportedException {
244        KeyToGroupMap result = (KeyToGroupMap) super.clone();
245        result.defaultGroup
246            = (Comparable) KeyToGroupMap.clone(this.defaultGroup);
247        result.groups = (List) KeyToGroupMap.clone(this.groups);
248        result.keyToGroupMap = (Map) KeyToGroupMap.clone(this.keyToGroupMap);
249        return result;
250    }
251
252    /**
253     * Attempts to clone the specified object using reflection.
254     *
255     * @param object  the object ({@code null} permitted).
256     *
257     * @return The cloned object, or the original object if cloning failed.
258     */
259    private static Object clone(Object object) {
260        if (object == null) {
261            return null;
262        }
263        Class c = object.getClass();
264        Object result = null;
265        try {
266            Method m = c.getMethod("clone", (Class[]) null);
267            if (Modifier.isPublic(m.getModifiers())) {
268                try {
269                    result = m.invoke(object, (Object[]) null);
270                }
271                catch (Exception e) {
272                    e.printStackTrace();
273                }
274            }
275        }
276        catch (NoSuchMethodException e) {
277            result = object;
278        }
279        return result;
280    }
281
282    /**
283     * Returns a clone of the list.
284     *
285     * @param list  the list.
286     *
287     * @return A clone of the list.
288     *
289     * @throws CloneNotSupportedException if the list could not be cloned.
290     */
291    private static Collection clone(Collection list)
292        throws CloneNotSupportedException {
293        Collection result = null;
294        if (list != null) {
295            try {
296                List clone = (List) list.getClass().newInstance();
297                Iterator iterator = list.iterator();
298                while (iterator.hasNext()) {
299                    clone.add(KeyToGroupMap.clone(iterator.next()));
300                }
301                result = clone;
302            }
303            catch (Exception e) {
304                throw new CloneNotSupportedException("Exception.");
305            }
306        }
307        return result;
308    }
309
310}