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 }