    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    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
    045    package org.jfree.data;
    046
    047    import java.io.Serializable;
    048    import java.lang.reflect.Method;
    049    import java.lang.reflect.Modifier;
    050    import java.util.ArrayList;
    051    import java.util.Collection;
    052    import java.util.HashMap;
    053    import java.util.Iterator;
    054    import java.util.List;
    055    import java.util.Map;
    056
    057    import org.jfree.util.ObjectUtilities;
    058    import org.jfree.util.PublicCloneable;
    059
    060    /**
    061     * A class that maps keys (instances of <code>Comparable</code>) to groups.
    062     */
    063    public 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    }