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.3 2007/03/09 15:59:21 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     * 
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        class PaintItem implements Comparable, Serializable {
073            
074            /** The value. */
075            Number value;
076            
077            /** The paint. */
078            transient Paint paint;
079            
080            /**
081             * Creates a new instance.
082             * 
083             * @param value  the value.
084             * @param paint  the paint.
085             */
086            public PaintItem(Number value, Paint paint) {
087                this.value = value;
088                this.paint = paint;
089            }
090            
091            /* (non-Javadoc)
092             * @see java.lang.Comparable#compareTo(java.lang.Object)
093             */
094            public int compareTo(Object obj) {
095                PaintItem that = (PaintItem) obj;
096                double d1 = this.value.doubleValue();
097                double d2 = that.value.doubleValue();
098                if (d1 > d2) {
099                    return 1;
100                }
101                if (d1 < d2) {
102                    return -1;
103                }
104                return 0;
105            }
106    
107            /**
108             * Tests this item for equality with an arbitrary object.
109             * 
110             * @param obj  the object (<code>null</code> permitted).
111             * 
112             * @return A boolean.
113             */
114            public boolean equals(Object obj) {
115                if (obj == this) {
116                    return true;
117                }
118                if (!(obj instanceof PaintItem)) {
119                    return false;
120                }
121                PaintItem that = (PaintItem) obj;
122                if (!this.value.equals(that.value)) {
123                    return false;
124                }
125                if (!PaintUtilities.equal(this.paint, that.paint)) {
126                    return false;
127                }
128                return true;
129            }
130            
131            /**
132             * Provides serialization support.
133             *
134             * @param stream  the output stream.
135             *
136             * @throws IOException  if there is an I/O error.
137             */
138            private void writeObject(ObjectOutputStream stream) throws IOException {
139                stream.defaultWriteObject();
140                SerialUtilities.writePaint(this.paint, stream);
141            }
142    
143            /**
144             * Provides serialization support.
145             *
146             * @param stream  the input stream.
147             *
148             * @throws IOException  if there is an I/O error.
149             * @throws ClassNotFoundException  if there is a classpath problem.
150             */
151            private void readObject(ObjectInputStream stream) 
152                    throws IOException, ClassNotFoundException {
153                stream.defaultReadObject();
154                this.paint = SerialUtilities.readPaint(stream);
155            }
156            
157        }
158        
159        /** The lower bound. */
160        private double lowerBound;
161        
162        /** The upper bound. */
163        private double upperBound;
164        
165        /** The default paint. */
166        private transient Paint defaultPaint; 
167        
168        /** The lookup table. */
169        private List lookupTable;
170        
171        /**
172         * Creates a new paint scale.
173         */
174        public LookupPaintScale() {
175            this(0.0, 1.0, Color.lightGray);    
176        }
177        
178        /**
179         * Creates a new paint scale with the specified default paint.
180         * 
181         * @param lowerBound  the lower bound.
182         * @param upperBound  the upper bound.
183         * @param defaultPaint  the default paint (<code>null</code> not 
184         *     permitted).
185         */
186        public LookupPaintScale(double lowerBound, double upperBound, 
187                Paint defaultPaint) {
188            if (lowerBound >= upperBound) {
189                throw new IllegalArgumentException(
190                        "Requires lowerBound < upperBound.");
191            }
192            if (defaultPaint == null) {
193                throw new IllegalArgumentException("Null 'paint' argument.");
194            }
195            this.lowerBound = lowerBound;
196            this.upperBound = upperBound;
197            this.defaultPaint = defaultPaint;
198            this.lookupTable = new java.util.ArrayList();
199        }
200        
201        /**
202         * Returns the default paint (never <code>null</code>).
203         * 
204         * @return The default paint.
205         */
206        public Paint getDefaultPaint() {
207            return this.defaultPaint;
208        }
209        
210        /**
211         * Returns the lower bound.
212         * 
213         * @return The lower bound.
214         * 
215         * @see #getUpperBound()
216         */
217        public double getLowerBound() {
218            return this.lowerBound;
219        }
220    
221        /**
222         * Returns the upper bound.
223         * 
224         * @return The upper bound.
225         * 
226         * @see #getLowerBound()
227         */
228        public double getUpperBound() {
229            return this.upperBound;
230        }
231    
232        /**
233         * Adds an entry to the lookup table.  Any values from <code>n</code> up
234         * to but not including the next value in the table take on the specified
235         * <code>paint</code>.
236         * 
237         * @param value  the data value.
238         * @param paint  the paint.
239         */
240        public void add(Number value, Paint paint) {
241            PaintItem item = new PaintItem(value, paint);
242            int index = Collections.binarySearch(this.lookupTable, item);
243            if (index >= 0) {
244                this.lookupTable.set(index, item);
245            }
246            else {
247                this.lookupTable.add(-(index + 1), item);
248            }
249        }
250        
251        /**
252         * Returns the paint associated with the specified value.
253         * 
254         * @param value  the value.
255         * 
256         * @return The paint.
257         * 
258         * @see #getDefaultPaint()
259         */
260        public Paint getPaint(double value) {
261            
262            // handle value outside bounds...
263            if (value < this.lowerBound) {
264                return this.defaultPaint;
265            }
266            if (value > this.upperBound) {
267                return this.defaultPaint;
268            }
269    
270            // for value in bounds, do the lookup...
271            Paint result = this.defaultPaint;
272            int index = this.lookupTable.size();
273            boolean done = false;
274            while (index > 0 && !done) {
275                PaintItem item = (PaintItem) this.lookupTable.get(--index);
276                if (value >= item.value.doubleValue()) {
277                    result = item.paint;
278                    done = true;
279                }
280            }
281            return result;
282        }
283        
284        /**
285         * Tests this instance for equality with an arbitrary object.
286         * 
287         * @param obj  the object (<code>null</code> permitted).
288         * 
289         * @return A boolean.
290         */
291        public boolean equals(Object obj) {
292            if (obj == this) {
293                return true;
294            }
295            if (!(obj instanceof LookupPaintScale)) {
296                return false;
297            }
298            LookupPaintScale that = (LookupPaintScale) obj;
299            if (this.lowerBound != that.lowerBound) {
300                return false;
301            }
302            if (this.upperBound != that.upperBound) {
303                return false;
304            }
305            if (!PaintUtilities.equal(this.defaultPaint, that.defaultPaint)) {
306                return false;
307            }
308            if (!this.lookupTable.equals(that.lookupTable)) {
309                return false;
310            }
311            return true;
312        }
313        
314        /**
315         * Returns a clone of the instance.
316         * 
317         * @return A clone.
318         * 
319         * @throws CloneNotSupportedException if there is a problem cloning the
320         *     instance.
321         */
322        public Object clone() throws CloneNotSupportedException {
323            LookupPaintScale clone = (LookupPaintScale) super.clone();
324            clone.lookupTable = new java.util.ArrayList(this.lookupTable);
325            return clone;
326        }
327    
328        /**
329         * Provides serialization support.
330         *
331         * @param stream  the output stream.
332         *
333         * @throws IOException  if there is an I/O error.
334         */
335        private void writeObject(ObjectOutputStream stream) throws IOException {
336            stream.defaultWriteObject();
337            SerialUtilities.writePaint(this.defaultPaint, stream);
338        }
339    
340        /**
341         * Provides serialization support.
342         *
343         * @param stream  the input stream.
344         *
345         * @throws IOException  if there is an I/O error.
346         * @throws ClassNotFoundException  if there is a classpath problem.
347         */
348        private void readObject(ObjectInputStream stream) 
349                throws IOException, ClassNotFoundException {
350            stream.defaultReadObject();
351            this.defaultPaint = SerialUtilities.readPaint(stream);
352        }
353    
354    }