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 * StrokeMap.java
029 * --------------
030 * (C) Copyright 2006-2021, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart;
038
039import java.awt.Stroke;
040import java.io.IOException;
041import java.io.ObjectInputStream;
042import java.io.ObjectOutputStream;
043import java.io.Serializable;
044import java.util.Iterator;
045import java.util.Map;
046import java.util.Objects;
047import java.util.Set;
048import java.util.TreeMap;
049import org.jfree.chart.util.Args;
050import org.jfree.chart.util.SerialUtils;
051
052/**
053 * A storage structure that maps {@code Comparable} instances with
054 * {@code Stroke} instances.
055 * <br><br>
056 * To support cloning and serialization, you should only use keys that are
057 * cloneable and serializable.  Special handling for the {@code Stroke}
058 * instances is included in this class.
059 */
060public class StrokeMap implements Cloneable, Serializable {
061
062    /** For serialization. */
063    static final long serialVersionUID = -8148916785963525169L;
064
065    /** Storage for the keys and values. */
066    private transient Map store;
067
068    /**
069     * Creates a new (empty) map.
070     */
071    public StrokeMap() {
072        this.store = new TreeMap();
073    }
074
075    /**
076     * Returns the stroke associated with the specified key, or
077     * {@code null}.
078     *
079     * @param key  the key ({@code null} not permitted).
080     *
081     * @return The stroke, or {@code null}.
082     *
083     * @throws IllegalArgumentException if {@code key} is
084     *     {@code null}.
085     */
086    public Stroke getStroke(Comparable key) {
087        Args.nullNotPermitted(key, "key");
088        return (Stroke) this.store.get(key);
089    }
090
091    /**
092     * Returns {@code true} if the map contains the specified key, and
093     * {@code false} otherwise.
094     *
095     * @param key  the key.
096     *
097     * @return {@code true} if the map contains the specified key, and
098     * {@code false} otherwise.
099     */
100    public boolean containsKey(Comparable key) {
101        return this.store.containsKey(key);
102    }
103
104    /**
105     * Adds a mapping between the specified {@code key} and
106     * {@code stroke} values.
107     *
108     * @param key  the key ({@code null} not permitted).
109     * @param stroke  the stroke.
110     */
111    public void put(Comparable key, Stroke stroke) {
112        Args.nullNotPermitted(key, "key");
113        this.store.put(key, stroke);
114    }
115
116    /**
117     * Resets the map to empty.
118     */
119    public void clear() {
120        this.store.clear();
121    }
122
123    /**
124     * Tests this map for equality with an arbitrary object.
125     *
126     * @param obj  the object ({@code null} permitted).
127     *
128     * @return A boolean.
129     */
130    @Override
131    public boolean equals(Object obj) {
132        if (obj == this) {
133            return true;
134        }
135        if (!(obj instanceof StrokeMap)) {
136            return false;
137        }
138        StrokeMap that = (StrokeMap) obj;
139        if (this.store.size() != that.store.size()) {
140            return false;
141        }
142        Set keys = this.store.keySet();
143        Iterator iterator = keys.iterator();
144        while (iterator.hasNext()) {
145            Comparable key = (Comparable) iterator.next();
146            Stroke s1 = getStroke(key);
147            Stroke s2 = that.getStroke(key);
148            if (!Objects.equals(s1, s2)) {
149                return false;
150            }
151        }
152        return true;
153    }
154
155    /**
156     * Returns a clone of this {@code StrokeMap}.
157     *
158     * @return A clone of this instance.
159     *
160     * @throws CloneNotSupportedException if any key is not cloneable.
161     */
162    @Override
163    public Object clone() throws CloneNotSupportedException {
164        StrokeMap clone = (StrokeMap) super.clone();
165        clone.store = new TreeMap();
166        clone.store.putAll(this.store);
167        // TODO: I think we need to make sure the keys are actually cloned,
168        // whereas the stroke instances are always immutable so they're OK
169        return clone;
170    }
171
172    /**
173     * Provides serialization support.
174     *
175     * @param stream  the output stream.
176     *
177     * @throws IOException  if there is an I/O error.
178     */
179    private void writeObject(ObjectOutputStream stream) throws IOException {
180        stream.defaultWriteObject();
181        stream.writeInt(this.store.size());
182        Set keys = this.store.keySet();
183        Iterator iterator = keys.iterator();
184        while (iterator.hasNext()) {
185            Comparable key = (Comparable) iterator.next();
186            stream.writeObject(key);
187            Stroke stroke = getStroke(key);
188            SerialUtils.writeStroke(stroke, stream);
189        }
190    }
191
192    /**
193     * Provides serialization support.
194     *
195     * @param stream  the input stream.
196     *
197     * @throws IOException  if there is an I/O error.
198     * @throws ClassNotFoundException  if there is a classpath problem.
199     */
200    private void readObject(ObjectInputStream stream)
201            throws IOException, ClassNotFoundException {
202        stream.defaultReadObject();
203        this.store = new TreeMap();
204        int keyCount = stream.readInt();
205        for (int i = 0; i < keyCount; i++) {
206            Comparable key = (Comparable) stream.readObject();
207            Stroke stroke = SerialUtils.readStroke(stream);
208            this.store.put(key, stroke);
209        }
210    }
211
212}