    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited 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-2008, by Object Refinery Limited and Contributors.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * Changes
    036 * -------
    037 * 29-Apr-2004 : Version 1 (DG);
    038 * 07-Jul-2004 : Added a group list to ensure group index is consistent, fixed
    039 *               cloning problem (DG);
    040 * 18-Aug-2005 : Added casts in clone() method to suppress 1.5 compiler
    041 *               warnings - see patch 1260587 (DG);
    042 *
    043 */
    044
    045package org.jfree.data;
    046
    047import java.io.Serializable;
    048import java.lang.reflect.Method;
    049import java.lang.reflect.Modifier;
    050import java.util.ArrayList;
    051import java.util.Collection;
    052import java.util.HashMap;
    053import java.util.Iterator;
    054import java.util.List;
    055import java.util.Map;
    056
    057import org.jfree.util.ObjectUtilities;
    058import org.jfree.util.PublicCloneable;
    059
    060/**
    061 * A class that maps keys (instances of <code>Comparable</code>) to groups.
    062 */
    063public class KeyToGroupMap implements Cloneable, PublicCloneable, Serializable {
    064
    065    /** For serialization. */
    066    private static final long serialVersionUID = -2228169345475318082L;
    067
    068    /** The default group. */
    069    private Comparable defaultGroup;
    070
    071    /** The groups. */
    072    private List groups;
    073
    074    /** A mapping between keys and groups. */
    075    private Map keyToGroupMap;
    076
    077    /**
    078     * Creates a new map with a default group named 'Default Group'.
    079     */
    080    public KeyToGroupMap() {
    081        this("Default Group");
    082    }
    083
    084    /**
    085     * Creates a new map with the specified default group.
    086     *
    087     * @param defaultGroup  the default group (<code>null</code> not permitted).
    088     */
    089    public KeyToGroupMap(Comparable defaultGroup) {
    090        if (defaultGroup == null) {
    091            throw new IllegalArgumentException("Null 'defaultGroup' argument.");
    092        }
    093        this.defaultGroup = defaultGroup;
    094        this.groups = new ArrayList();
    095        this.keyToGroupMap = new HashMap();
    096    }
    097
    098    /**
    099     * Returns the number of groups in the map.
    100     *
    101     * @return The number of groups in the map.
    102     */
    103    public int getGroupCount() {
    104        return this.groups.size() + 1;
    105    }
    106
    107    /**
    108     * Returns a list of the groups (always including the default group) in the
    109     * map.  The returned list is independent of the map, so altering the list
    110     * will have no effect.
    111     *
    112     * @return The groups (never <code>null</code>).
    113     */
    114    public List getGroups() {
    115        List result = new ArrayList();
    116        result.add(this.defaultGroup);
    117        Iterator iterator = this.groups.iterator();
    118        while (iterator.hasNext()) {
    119            Comparable group = (Comparable) iterator.next();
    120            if (!result.contains(group)) {
    121                result.add(group);
    122            }
    123        }
    124        return result;
    125    }
    126
    127    /**
    128     * Returns the index for the group.
    129     *
    130     * @param group  the group.
    131     *
    132     * @return The group index (or -1 if the group is not represented within
    133     *         the map).
    134     */
    135    public int getGroupIndex(Comparable group) {
    136        int result = this.groups.indexOf(group);
    137        if (result < 0) {
    138            if (this.defaultGroup.equals(group)) {
    139                result = 0;
    140            }
    141        }
    142        else {
    143            result = result + 1;
    144        }
    145        return result;
    146    }
    147
    148    /**
    149     * Returns the group that a key is mapped to.
    150     *
    151     * @param key  the key (<code>null</code> not permitted).
    152     *
    153     * @return The group (never <code>null</code>, returns the default group if
    154     *         there is no mapping for the specified key).
    155     */
    156    public Comparable getGroup(Comparable key) {
    157        if (key == null) {
    158            throw new IllegalArgumentException("Null 'key' argument.");
    159        }
    160        Comparable result = this.defaultGroup;
    161        Comparable group = (Comparable) this.keyToGroupMap.get(key);
    162        if (group != null) {
    163            result = group;
    164        }
    165        return result;
    166    }
    167
    168    /**
    169     * Maps a key to a group.
    170     *
    171     * @param key  the key (<code>null</code> not permitted).
    172     * @param group  the group (<code>null</code> permitted, clears any
    173     *               existing mapping).
    174     */
    175    public void mapKeyToGroup(Comparable key, Comparable group) {
    176        if (key == null) {
    177            throw new IllegalArgumentException("Null 'key' argument.");
    178        }
    179        Comparable currentGroup = getGroup(key);
    180        if (!currentGroup.equals(this.defaultGroup)) {
    181            if (!currentGroup.equals(group)) {
    182                int count = getKeyCount(currentGroup);
    183                if (count == 1) {
    184                    this.groups.remove(currentGroup);
    185                }
    186            }
    187        }
    188        if (group == null) {
    189            this.keyToGroupMap.remove(key);
    190        }
    191        else {
    192            if (!this.groups.contains(group)) {
    193                if (!this.defaultGroup.equals(group)) {
    194                    this.groups.add(group);
    195                }
    196            }
    197            this.keyToGroupMap.put(key, group);
    198        }
    199    }
    200
    201    /**
    202     * Returns the number of keys mapped to the specified group.  This method
    203     * won't always return an accurate result for the default group, since
    204     * explicit mappings are not required for this group.
    205     *
    206     * @param group  the group (<code>null</code> not permitted).
    207     *
    208     * @return The key count.
    209     */
    210    public int getKeyCount(Comparable group) {
    211        if (group == null) {
    212            throw new IllegalArgumentException("Null 'group' argument.");
    213        }
    214        int result = 0;
    215        Iterator iterator = this.keyToGroupMap.values().iterator();
    216        while (iterator.hasNext()) {
    217            Comparable g = (Comparable) iterator.next();
    218            if (group.equals(g)) {
    219                result++;
    220            }
    221        }
    222        return result;
    223    }
    224
    225    /**
    226     * Tests the map for equality against an arbitrary object.
    227     *
    228     * @param obj  the object to test against (<code>null</code> permitted).
    229     *
    230     * @return A boolean.
    231     */
    232    public boolean equals(Object obj) {
    233        if (obj == this) {
    234            return true;
    235        }
    236        if (!(obj instanceof KeyToGroupMap)) {
    237            return false;
    238        }
    239        KeyToGroupMap that = (KeyToGroupMap) obj;
    240        if (!ObjectUtilities.equal(this.defaultGroup, that.defaultGroup)) {
    241            return false;
    242        }
    243        if (!this.keyToGroupMap.equals(that.keyToGroupMap)) {
    244            return false;
    245        }
    246        return true;
    247    }
    248
    249    /**
    250     * Returns a clone of the map.
    251     *
    252     * @return A clone.
    253     *
    254     * @throws CloneNotSupportedException  if there is a problem cloning the
    255     *                                     map.
    256     */
    257    public Object clone() throws CloneNotSupportedException {
    258        KeyToGroupMap result = (KeyToGroupMap) super.clone();
    259        result.defaultGroup
    260            = (Comparable) KeyToGroupMap.clone(this.defaultGroup);
    261        result.groups = (List) KeyToGroupMap.clone(this.groups);
    262        result.keyToGroupMap = (Map) KeyToGroupMap.clone(this.keyToGroupMap);
    263        return result;
    264    }
    265
    266    /**
    267     * Attempts to clone the specified object using reflection.
    268     *
    269     * @param object  the object (<code>null</code> permitted).
    270     *
    271     * @return The cloned object, or the original object if cloning failed.
    272     */
    273    private static Object clone(Object object) {
    274        if (object == null) {
    275            return null;
    276        }
    277        Class c = object.getClass();
    278        Object result = null;
    279        try {
    280            Method m = c.getMethod("clone", (Class[]) null);
    281            if (Modifier.isPublic(m.getModifiers())) {
    282                try {
    283                    result = m.invoke(object, (Object[]) null);
    284                }
    285                catch (Exception e) {
    286                    e.printStackTrace();
    287                }
    288            }
    289        }
    290        catch (NoSuchMethodException e) {
    291            result = object;
    292        }
    293        return result;
    294    }
    295
    296    /**
    297     * Returns a clone of the list.
    298     *
    299     * @param list  the list.
    300     *
    301     * @return A clone of the list.
    302     *
    303     * @throws CloneNotSupportedException if the list could not be cloned.
    304     */
    305    private static Collection clone(Collection list)
    306        throws CloneNotSupportedException {
    307        Collection result = null;
    308        if (list != null) {
    309            try {
    310                List clone = (List) list.getClass().newInstance();
    311                Iterator iterator = list.iterator();
    312                while (iterator.hasNext()) {
    313                    clone.add(KeyToGroupMap.clone(iterator.next()));
    314                }
    315                result = clone;
    316            }
    317            catch (Exception e) {
    318                throw new CloneNotSupportedException("Exception.");
    319            }
    320        }
    321        return result;
    322    }
    323
    324}