001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2021, 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 * [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 2021, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 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.get(nodeKey); 160 if (props == null) { 161 props = new HashMap<>(); 162 this.nodeProperties.put(nodeKey, props); 163 } 164 props.put(propertyKey, value); 165 fireDatasetChanged(); 166 } 167 168 /** 169 * Returns the flow between a source node and a destination node at a 170 * specified stage. This must be 0 or greater. The dataset can return 171 * {@code null} to represent an unknown value. 172 * 173 * @param stage the stage index (0 to {@code getStageCount()} - 1). 174 * @param source the source ({@code null} not permitted). 175 * @param destination the destination ({@code null} not permitted). 176 * 177 * @return The flow (zero or greater, possibly {@code null}). 178 */ 179 @Override 180 public Number getFlow(int stage, K source, K destination) { 181 return this.flows.get(new FlowKey<>(stage, source, destination)); 182 } 183 184 /** 185 * Sets the flow between a source node and a destination node at the 186 * specified stage. A new stage will be added if {@code stage} is equal 187 * to {@code getStageCount()}. 188 * 189 * @param stage the stage (0 to {@code getStageCount()}. 190 * @param source the source ({@code null} not permitted). 191 * @param destination the destination ({@code null} not permitted). 192 * @param flow the flow (0 or greater). 193 */ 194 public void setFlow(int stage, K source, K destination, double flow) { 195 Args.requireInRange(stage, "stage", 0, getStageCount()); 196 Args.nullNotPermitted(source, "source"); 197 Args.nullNotPermitted(destination, "destination"); 198 if (stage > this.nodes.size() - 2) { 199 this.nodes.add(new ArrayList<>()); 200 } 201 if (!getSources(stage).contains(source)) { 202 this.nodes.get(stage).add(source); 203 } 204 if (!getDestinations(stage).contains(destination)) { 205 this.nodes.get(stage + 1).add(destination); 206 } 207 this.flows.put(new FlowKey<>(stage, source, destination), flow); 208 fireDatasetChanged(); 209 } 210 211 /** 212 * Returns the value of a property, if specified, for the specified flow. 213 * 214 * @param flowKey flowKey ({@code null} not permitted). 215 * 216 * @return The property value, or {@code null}. 217 */ 218 @Override 219 public Object getFlowProperty(FlowKey<K> flowKey, String propertyKey) { 220 Map<String, Object> props = this.flowProperties.get(flowKey); 221 if (props != null) { 222 return props.get(propertyKey); 223 } 224 return null; 225 } 226 227 /** 228 * Sets a property for the specified flow and notifies registered listeners 229 * that the dataset has changed. 230 * 231 * @param flowKey the node key ({@code null} not permitted). 232 * @param propertyKey the property key ({@code null} not permitted). 233 * @param value the property value. 234 */ 235 236 public void setFlowProperty(FlowKey<K> flowKey, String propertyKey, Object value) { 237 Map<String, Object> props = this.flowProperties.get(flowKey); 238 if (props == null) { 239 props = new HashMap<>(); 240 this.flowProperties.put(flowKey, props); 241 } 242 props.put(propertyKey, value); 243 fireDatasetChanged(); 244 } 245 246 /** 247 * Returns the number of flow stages. A flow dataset always has one or 248 * more stages, so this method will return {@code 1} even for an empty 249 * dataset (one with no sources, destinations or flows defined). 250 * 251 * @return The number of flow stages. 252 */ 253 @Override 254 public int getStageCount() { 255 return this.nodes.size() - 1; 256 } 257 258 /** 259 * Returns a set of keys for all the flows in the dataset. 260 * 261 * @return A set. 262 */ 263 @Override 264 public Set<FlowKey<K>> getAllFlows() { 265 return new HashSet<>(this.flows.keySet()); 266 } 267 268 /** 269 * Returns a list of flow keys for all the flows coming into this node. 270 * 271 * @param nodeKey the node key ({@code null} not permitted). 272 * 273 * @return A list of flow keys (possibly empty but never {@code null}). 274 */ 275 public List<FlowKey<K>> getInFlows(NodeKey nodeKey) { 276 Args.nullNotPermitted(nodeKey, "nodeKey"); 277 if (nodeKey.getStage() == 0) { 278 return Collections.EMPTY_LIST; 279 } 280 List<FlowKey<K>> result = new ArrayList<>(); 281 for (FlowKey<K> flowKey : this.flows.keySet()) { 282 if (flowKey.getStage() == nodeKey.getStage() - 1 && flowKey.getDestination().equals(nodeKey.getNode())) { 283 result.add(flowKey); 284 } 285 } 286 return result; 287 } 288 289 /** 290 * Returns a list of flow keys for all the flows going out of this node. 291 * 292 * @param nodeKey the node key ({@code null} not permitted). 293 * 294 * @return A list of flow keys (possibly empty but never {@code null}). 295 */ 296 public List<FlowKey> getOutFlows(NodeKey nodeKey) { 297 Args.nullNotPermitted(nodeKey, "nodeKey"); 298 if (nodeKey.getStage() == this.getStageCount()) { 299 return Collections.EMPTY_LIST; 300 } 301 List<FlowKey> result = new ArrayList<>(); 302 for (FlowKey flowKey : this.flows.keySet()) { 303 if (flowKey.getStage() == nodeKey.getStage() && flowKey.getSource().equals(nodeKey.getNode())) { 304 result.add(flowKey); 305 } 306 } 307 return result; 308 } 309 310 /** 311 * Returns a clone of the dataset. 312 * 313 * @return A clone of the dataset. 314 * 315 * @throws CloneNotSupportedException if there is a problem with cloning. 316 */ 317 @Override 318 public Object clone() throws CloneNotSupportedException { 319 DefaultFlowDataset<K> clone = (DefaultFlowDataset) super.clone(); 320 clone.flows = new HashMap<>(this.flows); 321 clone.nodes = new ArrayList<>(); 322 for (List<?> list : nodes) { 323 clone.nodes.add((List<K>) CloneUtils.cloneList(list)); 324 } 325 return clone; 326 } 327 328 /** 329 * Tests this dataset for equality with an arbitrary object. This method 330 * will return {@code true} if the object implements the 331 * {@link FlowDataset} and defines the exact same set of nodes and flows 332 * as this dataset. 333 * 334 * @param obj the object to test equality against ({@code null} permitted). 335 * 336 * @return A boolean. 337 */ 338 @Override 339 public boolean equals(Object obj) { 340 if (this == obj) { 341 return true; 342 } 343 if (!(obj instanceof FlowDataset)) { 344 return false; 345 } 346 final FlowDataset other = (FlowDataset) obj; 347 if (other.getStageCount() != getStageCount()) { 348 return false; 349 } 350 for (int stage = 0; stage < getStageCount(); stage++) { 351 if (!Objects.equals(other.getSources(stage), getSources(stage))) { 352 return false; 353 } 354 if (!Objects.equals(other.getDestinations(stage), getDestinations(stage))) { 355 return false; 356 } 357 for (K source : getSources(stage)) { 358 for (K destination : getDestinations(stage)) { 359 if (!Objects.equals(other.getFlow(stage, source, destination), getFlow(stage, source, destination))) { 360 return false; 361 } 362 } 363 } 364 } 365 return true; 366 } 367 368 @Override 369 public int hashCode() { 370 int hash = 3; 371 hash = 89 * hash + Objects.hashCode(getSources(0)); 372 hash = 89 * hash + Objects.hashCode(getDestinations(getStageCount() - 1)); 373 return hash; 374 } 375 376}