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