001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2009, 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     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025     * in the United States and other countries.]
026     *
027     * ---------------------
028     * LookupPaintScale.java
029     * ---------------------
030     * (C) Copyright 2006-2009, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 05-Jul-2006 : Version 1 (DG);
038     * 31-Jan-2007 : Fixed serialization support (DG);
039     * 09-Mar-2007 : Fixed cloning (DG);
040     * 14-Jun-2007 : Use double primitive in PaintItem (DG);
041     * 28-Mar-2009 : Made PaintItem inner class static (DG);
042     *
043     */
044    
045    package org.jfree.chart.renderer;
046    
047    import java.awt.Color;
048    import java.awt.Paint;
049    import java.io.IOException;
050    import java.io.ObjectInputStream;
051    import java.io.ObjectOutputStream;
052    import java.io.Serializable;
053    import java.util.Collections;
054    import java.util.List;
055    
056    import org.jfree.io.SerialUtilities;
057    import org.jfree.util.PaintUtilities;
058    import org.jfree.util.PublicCloneable;
059    
060    /**
061     * A paint scale that uses a lookup table to associate paint instances
062     * with data value ranges.
063     *
064     * @since 1.0.4
065     */
066    public class LookupPaintScale
067            implements PaintScale, PublicCloneable, Serializable {
068    
069        /**
070         * Stores the paint for a value.
071         */
072        static class PaintItem implements Comparable, Serializable {
073    
074            /** For serialization. */
075            static final long serialVersionUID = 698920578512361570L;
076    
077            /** The value. */
078            double value;
079    
080            /** The paint. */
081            transient Paint paint;
082    
083            /**
084             * Creates a new instance.
085             *
086             * @param value  the value.
087             * @param paint  the paint.
088             */
089            public PaintItem(double value, Paint paint) {
090                this.value = value;
091                this.paint = paint;
092            }
093    
094            /* (non-Javadoc)
095             * @see java.lang.Comparable#compareTo(java.lang.Object)
096             */
097            public int compareTo(Object obj) {
098                PaintItem that = (PaintItem) obj;
099                double d1 = this.value;
100                double d2 = that.value;
101                if (d1 > d2) {
102                    return 1;
103                }
104                if (d1 < d2) {
105                    return -1;
106                }
107                return 0;
108            }
109    
110            /**
111             * Tests this item for equality with an arbitrary object.
112             *
113             * @param obj  the object (<code>null</code> permitted).
114             *
115             * @return A boolean.
116             */
117            public boolean equals(Object obj) {
118                if (obj == this) {
119                    return true;
120                }
121                if (!(obj instanceof PaintItem)) {
122                    return false;
123                }
124                PaintItem that = (PaintItem) obj;
125                if (this.value != that.value) {
126                    return false;
127                }
128                if (!PaintUtilities.equal(this.paint, that.paint)) {
129                    return false;
130                }
131                return true;
132            }
133    
134            /**
135             * Provides serialization support.
136             *
137             * @param stream  the output stream.
138             *
139             * @throws IOException  if there is an I/O error.
140             */
141            private void writeObject(ObjectOutputStream stream) throws IOException {
142                stream.defaultWriteObject();
143                SerialUtilities.writePaint(this.paint, stream);
144            }
145    
146            /**
147             * Provides serialization support.
148             *
149             * @param stream  the input stream.
150             *
151             * @throws IOException  if there is an I/O error.
152             * @throws ClassNotFoundException  if there is a classpath problem.
153             */
154            private void readObject(ObjectInputStream stream)
155                    throws IOException, ClassNotFoundException {
156                stream.defaultReadObject();
157                this.paint = SerialUtilities.readPaint(stream);
158            }
159    
160        }
161    
162        /** For serialization. */
163        static final long serialVersionUID = -5239384246251042006L;
164    
165        /** The lower bound. */
166        private double lowerBound;
167    
168        /** The upper bound. */
169        private double upperBound;
170    
171        /** The default paint. */
172        private transient Paint defaultPaint;
173    
174        /** The lookup table. */
175        private List lookupTable;
176    
177        /**
178         * Creates a new paint scale.
179         */
180        public LookupPaintScale() {
181            this(0.0, 1.0, Color.lightGray);
182        }
183    
184        /**
185         * Creates a new paint scale with the specified default paint.
186         *
187         * @param lowerBound  the lower bound.
188         * @param upperBound  the upper bound.
189         * @param defaultPaint  the default paint (<code>null</code> not
190         *     permitted).
191         */
192        public LookupPaintScale(double lowerBound, double upperBound,
193                Paint defaultPaint) {
194            if (lowerBound >= upperBound) {
195                throw new IllegalArgumentException(
196                        "Requires lowerBound < upperBound.");
197            }
198            if (defaultPaint == null) {
199                throw new IllegalArgumentException("Null 'paint' argument.");
200            }
201            this.lowerBound = lowerBound;
202            this.upperBound = upperBound;
203            this.defaultPaint = defaultPaint;
204            this.lookupTable = new java.util.ArrayList();
205        }
206    
207        /**
208         * Returns the default paint (never <code>null</code>).
209         *
210         * @return The default paint.
211         */
212        public Paint getDefaultPaint() {
213            return this.defaultPaint;
214        }
215    
216        /**
217         * Returns the lower bound.
218         *
219         * @return The lower bound.
220         *
221         * @see #getUpperBound()
222         */
223        public double getLowerBound() {
224            return this.lowerBound;
225        }
226    
227        /**
228         * Returns the upper bound.
229         *
230         * @return The upper bound.
231         *
232         * @see #getLowerBound()
233         */
234        public double getUpperBound() {
235            return this.upperBound;
236        }
237    
238        /**
239         * Adds an entry to the lookup table.  Any values from <code>n</code> up
240         * to but not including the next value in the table take on the specified
241         * <code>paint</code>.
242         *
243         * @param value  the data value (<code>null</code> not permitted).
244         * @param paint  the paint.
245         *
246         * @deprecated Use {@link #add(double, Paint)}.
247         */
248        public void add(Number value, Paint paint) {
249            add(value.doubleValue(), paint);
250        }
251    
252        /**
253         * Adds an entry to the lookup table.  Any values from <code>n</code> up
254         * to but not including the next value in the table take on the specified
255         * <code>paint</code>.
256         *
257         * @param value  the data value.
258         * @param paint  the paint.
259         *
260         * @since 1.0.6
261         */
262        public void add(double value, Paint paint) {
263            PaintItem item = new PaintItem(value, paint);
264            int index = Collections.binarySearch(this.lookupTable, item);
265            if (index >= 0) {
266                this.lookupTable.set(index, item);
267            }
268            else {
269                this.lookupTable.add(-(index + 1), item);
270            }
271        }
272    
273        /**
274         * Returns the paint associated with the specified value.
275         *
276         * @param value  the value.
277         *
278         * @return The paint.
279         *
280         * @see #getDefaultPaint()
281         */
282        public Paint getPaint(double value) {
283    
284            // handle value outside bounds...
285            if (value < this.lowerBound) {
286                return this.defaultPaint;
287            }
288            if (value > this.upperBound) {
289                return this.defaultPaint;
290            }
291    
292            int count = this.lookupTable.size();
293            if (count == 0) {
294                return this.defaultPaint;
295            }
296    
297            // handle special case where value is less that item zero
298            PaintItem item = (PaintItem) this.lookupTable.get(0);
299            if (value < item.value) {
300                return this.defaultPaint;
301            }
302    
303            // for value in bounds, do the lookup...
304            int low = 0;
305            int high = this.lookupTable.size() - 1;
306            while (high - low > 1) {
307                int current = (low + high) / 2;
308                item = (PaintItem) this.lookupTable.get(current);
309                if (value >= item.value) {
310                    low = current;
311                }
312                else {
313                    high = current;
314                }
315            }
316            if (high > low) {
317                item = (PaintItem) this.lookupTable.get(high);
318                if (value < item.value) {
319                    item = (PaintItem) this.lookupTable.get(low);
320                }
321            }
322            return (item != null ? item.paint : this.defaultPaint);
323        }
324    
325    
326        /**
327         * Tests this instance for equality with an arbitrary object.
328         *
329         * @param obj  the object (<code>null</code> permitted).
330         *
331         * @return A boolean.
332         */
333        public boolean equals(Object obj) {
334            if (obj == this) {
335                return true;
336            }
337            if (!(obj instanceof LookupPaintScale)) {
338                return false;
339            }
340            LookupPaintScale that = (LookupPaintScale) obj;
341            if (this.lowerBound != that.lowerBound) {
342                return false;
343            }
344            if (this.upperBound != that.upperBound) {
345                return false;
346            }
347            if (!PaintUtilities.equal(this.defaultPaint, that.defaultPaint)) {
348                return false;
349            }
350            if (!this.lookupTable.equals(that.lookupTable)) {
351                return false;
352            }
353            return true;
354        }
355    
356        /**
357         * Returns a clone of the instance.
358         *
359         * @return A clone.
360         *
361         * @throws CloneNotSupportedException if there is a problem cloning the
362         *     instance.
363         */
364        public Object clone() throws CloneNotSupportedException {
365            LookupPaintScale clone = (LookupPaintScale) super.clone();
366            clone.lookupTable = new java.util.ArrayList(this.lookupTable);
367            return clone;
368        }
369    
370        /**
371         * Provides serialization support.
372         *
373         * @param stream  the output stream.
374         *
375         * @throws IOException  if there is an I/O error.
376         */
377        private void writeObject(ObjectOutputStream stream) throws IOException {
378            stream.defaultWriteObject();
379            SerialUtilities.writePaint(this.defaultPaint, stream);
380        }
381    
382        /**
383         * Provides serialization support.
384         *
385         * @param stream  the input stream.
386         *
387         * @throws IOException  if there is an I/O error.
388         * @throws ClassNotFoundException  if there is a classpath problem.
389         */
390        private void readObject(ObjectInputStream stream)
391                throws IOException, ClassNotFoundException {
392            stream.defaultReadObject();
393            this.defaultPaint = SerialUtilities.readPaint(stream);
394        }
395    
396    }