    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    }