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}