001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, 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 * DefaultFlowDataset.java 029 * ----------------------- 030 * (C) Copyright 2022-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.data.flow; 038 039import java.io.Serializable; 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.HashMap; 043import java.util.HashSet; 044import java.util.List; 045import java.util.Map; 046import java.util.Objects; 047import java.util.Set; 048import org.jfree.chart.util.Args; 049import org.jfree.chart.util.CloneUtils; 050import org.jfree.chart.util.PublicCloneable; 051import org.jfree.data.general.AbstractDataset; 052 053/** 054 * A dataset representing flows between source and destination nodes. 055 * 056 * @param <K> the type for the keys used to identify sources and destinations 057 * (instances should be immutable, {@code String} is a good default choice). 058 * 059 * @since 1.5.3 060 */ 061public class DefaultFlowDataset<K extends Comparable<K>> extends AbstractDataset 062 implements FlowDataset<K>, PublicCloneable, Serializable { 063 064 /** 065 * The nodes at each stage. The list will have N+1 entries, where N is 066 * the number of stages - the last entry contains the destination nodes for 067 * the final stage. 068 */ 069 private List<List<K>> nodes; 070 071 /** Node properties. */ 072 private Map<NodeKey, Map<String, Object>> nodeProperties; 073 074 /** Storage for the flows. */ 075 private Map<FlowKey<K>, Number> flows; 076 077 /** Flow properties. */ 078 private Map<FlowKey, Map<String, Object>> flowProperties; 079 080 /** 081 * Creates a new dataset that is initially empty. 082 */ 083 public DefaultFlowDataset() { 084 this.nodes = new ArrayList<>(); 085 this.nodes.add(new ArrayList<>()); 086 this.nodes.add(new ArrayList<>()); 087 this.nodeProperties = new HashMap<>(); 088 this.flows = new HashMap<>(); 089 this.flowProperties = new HashMap<>(); 090 } 091 092 /** 093 * Returns a list of the source nodes for the specified stage. 094 * 095 * @param stage the stage (0 to {@code getStageCount() - 1}). 096 * 097 * @return A list of source nodes (possibly empty but never {@code null}). 098 */ 099 @Override 100 public List<K> getSources(int stage) { 101 return new ArrayList<>(this.nodes.get(stage)); 102 } 103 104 /** 105 * Returns a list of the destination nodes for the specified stage. 106 * 107 * @param stage the stage (0 to {@code getStageCount() - 1}). 108 * 109 * @return A list of destination nodes (possibly empty but never {@code null}). 110 */ 111 @Override 112 public List<K> getDestinations(int stage) { 113 return new ArrayList<>(this.nodes.get(stage + 1)); 114 } 115 116 /** 117 * Returns the set of keys for all the nodes in the dataset. 118 * 119 * @return The set of keys for all the nodes in the dataset (possibly empty 120 * but never {@code null}). 121 */ 122 @Override 123 public Set<NodeKey<K>> getAllNodes() { 124 Set<NodeKey<K>> result = new HashSet<>(); 125 for (int s = 0; s <= this.getStageCount(); s++) { 126 for (K key : this.getSources(s)) { 127 result.add(new NodeKey<>(s, key)); 128 } 129 } 130 return result; 131 } 132 133 /** 134 * Returns the value of a property, if specified, for the specified node. 135 * 136 * @param nodeKey the node key ({@code null} not permitted). 137 * @param propertyKey the node key ({@code null} not permitted). 138 * 139 * @return The property value, or {@code null}. 140 */ 141 @Override 142 public Object getNodeProperty(NodeKey<K> nodeKey, String propertyKey) { 143 Map<String, Object> props = this.nodeProperties.get(nodeKey); 144 if (props != null) { 145 return props.get(propertyKey); 146 } 147 return null; 148 } 149 150 /** 151 * Sets a property for the specified node and notifies registered listeners 152 * that the dataset has changed. 153 * 154 * @param nodeKey the node key ({@code null} not permitted). 155 * @param propertyKey the property key ({@code null} not permitted). 156 * @param value the property value. 157 */ 158 public void setNodeProperty(NodeKey<K> nodeKey, String propertyKey, Object value) { 159 Map<String, Object> props = this.nodeProperties.computeIfAbsent(nodeKey, k -> new HashMap<>()); 160 props.put(propertyKey, value); 161 fireDatasetChanged(); 162 } 163 164 /** 165 * Returns the flow between a source node and a destination node at a 166 * specified stage. This must be 0 or greater. The dataset can return 167 * {@code null} to represent an unknown value. 168 * 169 * @param stage the stage index (0 to {@code getStageCount()} - 1). 170 * @param source the source ({@code null} not permitted). 171 * @param destination the destination ({@code null} not permitted). 172 * 173 * @return The flow (zero or greater, possibly {@code null}). 174 */ 175 @Override 176 public Number getFlow(int stage, K source, K destination) { 177 return this.flows.get(new FlowKey<>(stage, source, destination)); 178 } 179 180 /** 181 * Sets the flow between a source node and a destination node at the 182 * specified stage. A new stage will be added if {@code stage} is equal 183 * to {@code getStageCount()}. 184 * 185 * @param stage the stage (0 to {@code getStageCount()}. 186 * @param source the source ({@code null} not permitted). 187 * @param destination the destination ({@code null} not permitted). 188 * @param flow the flow (0 or greater). 189 */ 190 public void setFlow(int stage, K source, K destination, double flow) { 191 Args.requireInRange(stage, "stage", 0, getStageCount()); 192 Args.nullNotPermitted(source, "source"); 193 Args.nullNotPermitted(destination, "destination"); 194 if (stage > this.nodes.size() - 2) { 195 this.nodes.add(new ArrayList<>()); 196 } 197 if (!getSources(stage).contains(source)) { 198 this.nodes.get(stage).add(source); 199 } 200 if (!getDestinations(stage).contains(destination)) { 201 this.nodes.get(stage + 1).add(destination); 202 } 203 this.flows.put(new FlowKey<>(stage, source, destination), flow); 204 fireDatasetChanged(); 205 } 206 207 /** 208 * Returns the value of a property, if specified, for the specified flow. 209 * 210 * @param flowKey flowKey ({@code null} not permitted). 211 * 212 * @return The property value, or {@code null}. 213 */ 214 @Override 215 public Object getFlowProperty(FlowKey<K> flowKey, String propertyKey) { 216 Map<String, Object> props = this.flowProperties.get(flowKey); 217 if (props != null) { 218 return props.get(propertyKey); 219 } 220 return null; 221 } 222 223 /** 224 * Sets a property for the specified flow and notifies registered listeners 225 * that the dataset has changed. 226 * 227 * @param flowKey the node key ({@code null} not permitted). 228 * @param propertyKey the property key ({@code null} not permitted). 229 * @param value the property value. 230 */ 231 public void setFlowProperty(FlowKey<K> flowKey, String propertyKey, Object value) { 232 Map<String, Object> props = this.flowProperties.computeIfAbsent(flowKey, k -> new HashMap<>()); 233 props.put(propertyKey, value); 234 fireDatasetChanged(); 235 } 236 237 /** 238 * Returns the number of flow stages. A flow dataset always has one or 239 * more stages, so this method will return {@code 1} even for an empty 240 * dataset (one with no sources, destinations or flows defined). 241 * 242 * @return The number of flow stages. 243 */ 244 @Override 245 public int getStageCount() { 246 return this.nodes.size() - 1; 247 } 248 249 /** 250 * Returns a set of keys for all the flows in the dataset. 251 * 252 * @return A set. 253 */ 254 @Override 255 public Set<FlowKey<K>> getAllFlows() { 256 return new HashSet<>(this.flows.keySet()); 257 } 258 259 /** 260 * Returns a list of flow keys for all the flows coming into this node. 261 * 262 * @param nodeKey the node key ({@code null} not permitted). 263 * 264 * @return A list of flow keys (possibly empty but never {@code null}). 265 */ 266 public List<FlowKey<K>> getInFlows(NodeKey nodeKey) { 267 Args.nullNotPermitted(nodeKey, "nodeKey"); 268 if (nodeKey.getStage() == 0) { 269 return Collections.EMPTY_LIST; 270 } 271 List<FlowKey<K>> result = new ArrayList<>(); 272 for (FlowKey<K> flowKey : this.flows.keySet()) { 273 if (flowKey.getStage() == nodeKey.getStage() - 1 && flowKey.getDestination().equals(nodeKey.getNode())) { 274 result.add(flowKey); 275 } 276 } 277 return result; 278 } 279 280 /** 281 * Returns a list of flow keys for all the flows going out of this node. 282 * 283 * @param nodeKey the node key ({@code null} not permitted). 284 * 285 * @return A list of flow keys (possibly empty but never {@code null}). 286 */ 287 public List<FlowKey> getOutFlows(NodeKey nodeKey) { 288 Args.nullNotPermitted(nodeKey, "nodeKey"); 289 if (nodeKey.getStage() == this.getStageCount()) { 290 return Collections.EMPTY_LIST; 291 } 292 List<FlowKey> result = new ArrayList<>(); 293 for (FlowKey flowKey : this.flows.keySet()) { 294 if (flowKey.getStage() == nodeKey.getStage() && flowKey.getSource().equals(nodeKey.getNode())) { 295 result.add(flowKey); 296 } 297 } 298 return result; 299 } 300 301 /** 302 * Returns a clone of the dataset. 303 * 304 * @return A clone of the dataset. 305 * 306 * @throws CloneNotSupportedException if there is a problem with cloning. 307 */ 308 @Override 309 public Object clone() throws CloneNotSupportedException { 310 DefaultFlowDataset<K> clone = (DefaultFlowDataset) super.clone(); 311 clone.flows = new HashMap<>(this.flows); 312 clone.nodes = new ArrayList<>(); 313 for (List<?> list : nodes) { 314 clone.nodes.add((List<K>) CloneUtils.cloneList(list)); 315 } 316 return clone; 317 } 318 319 /** 320 * Tests this dataset for equality with an arbitrary object. This method 321 * will return {@code true} if the object implements the 322 * {@link FlowDataset} and defines the exact same set of nodes and flows 323 * as this dataset. 324 * 325 * @param obj the object to test equality against ({@code null} permitted). 326 * 327 * @return A boolean. 328 */ 329 @Override 330 public boolean equals(Object obj) { 331 if (this == obj) { 332 return true; 333 } 334 if (!(obj instanceof FlowDataset)) { 335 return false; 336 } 337 final FlowDataset other = (FlowDataset) obj; 338 if (other.getStageCount() != getStageCount()) { 339 return false; 340 } 341 for (int stage = 0; stage < getStageCount(); stage++) { 342 if (!Objects.equals(other.getSources(stage), getSources(stage))) { 343 return false; 344 } 345 if (!Objects.equals(other.getDestinations(stage), getDestinations(stage))) { 346 return false; 347 } 348 for (K source : getSources(stage)) { 349 for (K destination : getDestinations(stage)) { 350 if (!Objects.equals(other.getFlow(stage, source, destination), getFlow(stage, source, destination))) { 351 return false; 352 } 353 } 354 } 355 } 356 return true; 357 } 358 359 @Override 360 public int hashCode() { 361 int hash = 3; 362 hash = 89 * hash + Objects.hashCode(getSources(0)); 363 hash = 89 * hash + Objects.hashCode(getDestinations(getStageCount() - 1)); 364 return hash; 365 } 366 367}