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}