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