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    }