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