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