    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    }