    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2011, 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     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025     * Other names may be trademarks of their respective owners.]
    026     *
    027     * -----------------------
    028     * DialValueIndicator.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     * 03-Nov-2006 : Version 1 (DG);
    038     * 17-Oct-2007 : Updated equals() (DG);
    039     * 24-Oct-2007 : Added default constructor and missing event notification (DG);
    040     * 09-Jun-2009 : Improved indicator resizing, fixes bug 2802014 (DG);
    041     *
    042     */
    043
    044    package org.jfree.chart.plot.dial;
    045
    046    import java.awt.BasicStroke;
    047    import java.awt.Color;
    048    import java.awt.Font;
    049    import java.awt.FontMetrics;
    050    import java.awt.Graphics2D;
    051    import java.awt.Paint;
    052    import java.awt.Shape;
    053    import java.awt.Stroke;
    054    import java.awt.geom.Arc2D;
    055    import java.awt.geom.Point2D;
    056    import java.awt.geom.Rectangle2D;
    057    import java.io.IOException;
    058    import java.io.ObjectInputStream;
    059    import java.io.ObjectOutputStream;
    060    import java.io.Serializable;
    061    import java.text.DecimalFormat;
    062    import java.text.NumberFormat;
    063
    064    import org.jfree.chart.HashUtilities;
    065    import org.jfree.io.SerialUtilities;
    066    import org.jfree.text.TextUtilities;
    067    import org.jfree.ui.RectangleAnchor;
    068    import org.jfree.ui.RectangleInsets;
    069    import org.jfree.ui.Size2D;
    070    import org.jfree.ui.TextAnchor;
    071    import org.jfree.util.ObjectUtilities;
    072    import org.jfree.util.PaintUtilities;
    073    import org.jfree.util.PublicCloneable;
    074
    075    /**
    076     * A value indicator for a {@link DialPlot}.
    077     *
    078     * @since 1.0.7
    079     */
    080    public class DialValueIndicator extends AbstractDialLayer implements DialLayer,
    081            Cloneable, PublicCloneable, Serializable {
    082
    083        /** For serialization. */
    084        static final long serialVersionUID = 803094354130942585L;
    085
    086        /** The dataset index. */
    087        private int datasetIndex;
    088
    089        /** The angle that defines the anchor point. */
    090        private double angle;
    091
    092        /** The radius that defines the anchor point. */
    093        private double radius;
    094
    095        /** The frame anchor. */
    096        private RectangleAnchor frameAnchor;
    097
    098        /** The template value. */
    099        private Number templateValue;
    100
    101        /**
    102         * A data value that will be formatted to determine the maximum size of
    103         * the indicator bounds.  If this is null, the indicator bounds can grow
    104         * as large as necessary to contain the actual data value.
    105         *
    106         * @since 1.0.14
    107         */
    108        private Number maxTemplateValue;
    109
    110        /** The formatter. */
    111        private NumberFormat formatter;
    112
    113        /** The font. */
    114        private Font font;
    115
    116        /** The paint. */
    117        private transient Paint paint;
    118
    119        /** The background paint. */
    120        private transient Paint backgroundPaint;
    121
    122        /** The outline stroke. */
    123        private transient Stroke outlineStroke;
    124
    125        /** The outline paint. */
    126        private transient Paint outlinePaint;
    127
    128        /** The insets. */
    129        private RectangleInsets insets;
    130
    131        /** The value anchor. */
    132        private RectangleAnchor valueAnchor;
    133
    134        /** The text anchor for displaying the value. */
    135        private TextAnchor textAnchor;
    136
    137        /**
    138         * Creates a new instance of <code>DialValueIndicator</code>.
    139         */
    140        public DialValueIndicator() {
    141            this(0);
    142        }
    143
    144        /**
    145         * Creates a new instance of <code>DialValueIndicator</code>.
    146         *
    147         * @param datasetIndex  the dataset index.
    148         */
    149        public DialValueIndicator(int datasetIndex) {
    150            this.datasetIndex = datasetIndex;
    151            this.angle = -90.0;
    152            this.radius = 0.3;
    153            this.frameAnchor = RectangleAnchor.CENTER;
    154            this.templateValue = new Double(100.0);
    155            this.maxTemplateValue = null;
    156            this.formatter = new DecimalFormat("0.0");
    157            this.font = new Font("Dialog", Font.BOLD, 14);
    158            this.paint = Color.black;
    159            this.backgroundPaint = Color.white;
    160            this.outlineStroke = new BasicStroke(1.0f);
    161            this.outlinePaint = Color.blue;
    162            this.insets = new RectangleInsets(4, 4, 4, 4);
    163            this.valueAnchor = RectangleAnchor.RIGHT;
    164            this.textAnchor = TextAnchor.CENTER_RIGHT;
    165        }
    166
    167        /**
    168         * Returns the index of the dataset from which this indicator fetches its
    169         * current value.
    170         *
    171         * @return The dataset index.
    172         *
    173         * @see #setDatasetIndex(int)
    174         */
    175        public int getDatasetIndex() {
    176            return this.datasetIndex;
    177        }
    178
    179        /**
    180         * Sets the dataset index and sends a {@link DialLayerChangeEvent} to all
    181         * registered listeners.
    182         *
    183         * @param index  the index.
    184         *
    185         * @see #getDatasetIndex()
    186         */
    187        public void setDatasetIndex(int index) {
    188            this.datasetIndex = index;
    189            notifyListeners(new DialLayerChangeEvent(this));
    190        }
    191
    192        /**
    193         * Returns the angle for the anchor point.  The angle is specified in
    194         * degrees using the same orientation as Java's <code>Arc2D</code> class.
    195         *
    196         * @return The angle (in degrees).
    197         *
    198         * @see #setAngle(double)
    199         */
    200        public double getAngle() {
    201            return this.angle;
    202        }
    203
    204        /**
    205         * Sets the angle for the anchor point and sends a
    206         * {@link DialLayerChangeEvent} to all registered listeners.
    207         *
    208         * @param angle  the angle (in degrees).
    209         *
    210         * @see #getAngle()
    211         */
    212        public void setAngle(double angle) {
    213            this.angle = angle;
    214            notifyListeners(new DialLayerChangeEvent(this));
    215        }
    216
    217        /**
    218         * Returns the radius.
    219         *
    220         * @return The radius.
    221         *
    222         * @see #setRadius(double)
    223         */
    224        public double getRadius() {
    225            return this.radius;
    226        }
    227
    228        /**
    229         * Sets the radius and sends a {@link DialLayerChangeEvent} to all
    230         * registered listeners.
    231         *
    232         * @param radius  the radius.
    233         *
    234         * @see #getRadius()
    235         */
    236        public void setRadius(double radius) {
    237            this.radius = radius;
    238            notifyListeners(new DialLayerChangeEvent(this));
    239        }
    240
    241        /**
    242         * Returns the frame anchor.
    243         *
    244         * @return The frame anchor.
    245         *
    246         * @see #setFrameAnchor(RectangleAnchor)
    247         */
    248        public RectangleAnchor getFrameAnchor() {
    249            return this.frameAnchor;
    250        }
    251
    252        /**
    253         * Sets the frame anchor and sends a {@link DialLayerChangeEvent} to all
    254         * registered listeners.
    255         *
    256         * @param anchor  the anchor (<code>null</code> not permitted).
    257         *
    258         * @see #getFrameAnchor()
    259         */
    260        public void setFrameAnchor(RectangleAnchor anchor) {
    261            if (anchor == null) {
    262                throw new IllegalArgumentException("Null 'anchor' argument.");
    263            }
    264            this.frameAnchor = anchor;
    265            notifyListeners(new DialLayerChangeEvent(this));
    266        }
    267
    268        /**
    269         * Returns the template value.
    270         *
    271         * @return The template value (never <code>null</code>).
    272         *
    273         * @see #setTemplateValue(Number)
    274         */
    275        public Number getTemplateValue() {
    276            return this.templateValue;
    277        }
    278
    279        /**
    280         * Sets the template value and sends a {@link DialLayerChangeEvent} to
    281         * all registered listeners.
    282         *
    283         * @param value  the value (<code>null</code> not permitted).
    284         *
    285         * @see #setTemplateValue(Number)
    286         */
    287        public void setTemplateValue(Number value) {
    288            if (value == null) {
    289                throw new IllegalArgumentException("Null 'value' argument.");
    290            }
    291            this.templateValue = value;
    292            notifyListeners(new DialLayerChangeEvent(this));
    293        }
    294
    295        /**
    296         * Returns the template value for the maximum size of the indicator
    297         * bounds.
    298         *
    299         * @return The template value (possibly <code>null</code>).
    300         *
    301         * @since 1.0.14
    302         *
    303         * @see #setMaxTemplateValue(java.lang.Number)
    304         */
    305        public Number getMaxTemplateValue() {
    306            return this.maxTemplateValue;
    307        }
    308
    309        /**
    310         * Sets the template value for the maximum size of the indicator bounds
    311         * and sends a {@link DialLayerChangeEvent} to all registered listeners.
    312         *
    313         * @param value  the value (<code>null</code> permitted).
    314         *
    315         * @since 1.0.14
    316         *
    317         * @see #getMaxTemplateValue()
    318         */
    319        public void setMaxTemplateValue(Number value) {
    320            this.maxTemplateValue = value;
    321            notifyListeners(new DialLayerChangeEvent(this));
    322        }
    323
    324        /**
    325         * Returns the formatter used to format the value.
    326         *
    327         * @return The formatter (never <code>null</code>).
    328         *
    329         * @see #setNumberFormat(NumberFormat)
    330         */
    331        public NumberFormat getNumberFormat() {
    332            return this.formatter;
    333        }
    334
    335        /**
    336         * Sets the formatter used to format the value and sends a
    337         * {@link DialLayerChangeEvent} to all registered listeners.
    338         *
    339         * @param formatter  the formatter (<code>null</code> not permitted).
    340         *
    341         * @see #getNumberFormat()
    342         */
    343        public void setNumberFormat(NumberFormat formatter) {
    344            if (formatter == null) {
    345                throw new IllegalArgumentException("Null 'formatter' argument.");
    346            }
    347            this.formatter = formatter;
    348            notifyListeners(new DialLayerChangeEvent(this));
    349        }
    350
    351        /**
    352         * Returns the font.
    353         *
    354         * @return The font (never <code>null</code>).
    355         *
    356         * @see #getFont()
    357         */
    358        public Font getFont() {
    359            return this.font;
    360        }
    361
    362        /**
    363         * Sets the font and sends a {@link DialLayerChangeEvent} to all registered
    364         * listeners.
    365         *
    366         * @param font  the font (<code>null</code> not permitted).
    367         */
    368        public void setFont(Font font) {
    369            if (font == null) {
    370                throw new IllegalArgumentException("Null 'font' argument.");
    371            }
    372            this.font = font;
    373            notifyListeners(new DialLayerChangeEvent(this));
    374        }
    375
    376        /**
    377         * Returns the paint.
    378         *
    379         * @return The paint (never <code>null</code>).
    380         *
    381         * @see #setPaint(Paint)
    382         */
    383        public Paint getPaint() {
    384            return this.paint;
    385        }
    386
    387        /**
    388         * Sets the paint and sends a {@link DialLayerChangeEvent} to all
    389         * registered listeners.
    390         *
    391         * @param paint  the paint (<code>null</code> not permitted).
    392         *
    393         * @see #getPaint()
    394         */
    395        public void setPaint(Paint paint) {
    396            if (paint == null) {
    397                throw new IllegalArgumentException("Null 'paint' argument.");
    398            }
    399            this.paint = paint;
    400            notifyListeners(new DialLayerChangeEvent(this));
    401        }
    402
    403        /**
    404         * Returns the background paint.
    405         *
    406         * @return The background paint.
    407         *
    408         * @see #setBackgroundPaint(Paint)
    409         */
    410        public Paint getBackgroundPaint() {
    411            return this.backgroundPaint;
    412        }
    413
    414        /**
    415         * Sets the background paint and sends a {@link DialLayerChangeEvent} to
    416         * all registered listeners.
    417         *
    418         * @param paint  the paint (<code>null</code> not permitted).
    419         *
    420         * @see #getBackgroundPaint()
    421         */
    422        public void setBackgroundPaint(Paint paint) {
    423            if (paint == null) {
    424                throw new IllegalArgumentException("Null 'paint' argument.");
    425            }
    426            this.backgroundPaint = paint;
    427            notifyListeners(new DialLayerChangeEvent(this));
    428        }
    429
    430        /**
    431         * Returns the outline stroke.
    432         *
    433         * @return The outline stroke (never <code>null</code>).
    434         *
    435         * @see #setOutlineStroke(Stroke)
    436         */
    437        public Stroke getOutlineStroke() {
    438            return this.outlineStroke;
    439        }
    440
    441        /**
    442         * Sets the outline stroke and sends a {@link DialLayerChangeEvent} to
    443         * all registered listeners.
    444         *
    445         * @param stroke  the stroke (<code>null</code> not permitted).
    446         *
    447         * @see #getOutlineStroke()
    448         */
    449        public void setOutlineStroke(Stroke stroke) {
    450            if (stroke == null) {
    451                throw new IllegalArgumentException("Null 'stroke' argument.");
    452            }
    453            this.outlineStroke = stroke;
    454            notifyListeners(new DialLayerChangeEvent(this));
    455        }
    456
    457        /**
    458         * Returns the outline paint.
    459         *
    460         * @return The outline paint (never <code>null</code>).
    461         *
    462         * @see #setOutlinePaint(Paint)
    463         */
    464        public Paint getOutlinePaint() {
    465            return this.outlinePaint;
    466        }
    467
    468        /**
    469         * Sets the outline paint and sends a {@link DialLayerChangeEvent} to all
    470         * registered listeners.
    471         *
    472         * @param paint  the paint (<code>null</code> not permitted).
    473         *
    474         * @see #getOutlinePaint()
    475         */
    476        public void setOutlinePaint(Paint paint) {
    477            if (paint == null) {
    478                throw new IllegalArgumentException("Null 'paint' argument.");
    479            }
    480            this.outlinePaint = paint;
    481            notifyListeners(new DialLayerChangeEvent(this));
    482        }
    483
    484        /**
    485         * Returns the insets.
    486         *
    487         * @return The insets (never <code>null</code>).
    488         *
    489         * @see #setInsets(RectangleInsets)
    490         */
    491        public RectangleInsets getInsets() {
    492            return this.insets;
    493        }
    494
    495        /**
    496         * Sets the insets and sends a {@link DialLayerChangeEvent} to all
    497         * registered listeners.
    498         *
    499         * @param insets  the insets (<code>null</code> not permitted).
    500         *
    501         * @see #getInsets()
    502         */
    503        public void setInsets(RectangleInsets insets) {
    504            if (insets == null) {
    505                throw new IllegalArgumentException("Null 'insets' argument.");
    506            }
    507            this.insets = insets;
    508            notifyListeners(new DialLayerChangeEvent(this));
    509        }
    510
    511        /**
    512         * Returns the value anchor.
    513         *
    514         * @return The value anchor (never <code>null</code>).
    515         *
    516         * @see #setValueAnchor(RectangleAnchor)
    517         */
    518        public RectangleAnchor getValueAnchor() {
    519            return this.valueAnchor;
    520        }
    521
    522        /**
    523         * Sets the value anchor and sends a {@link DialLayerChangeEvent} to all
    524         * registered listeners.
    525         *
    526         * @param anchor  the anchor (<code>null</code> not permitted).
    527         *
    528         * @see #getValueAnchor()
    529         */
    530        public void setValueAnchor(RectangleAnchor anchor) {
    531            if (anchor == null) {
    532                throw new IllegalArgumentException("Null 'anchor' argument.");
    533            }
    534            this.valueAnchor = anchor;
    535            notifyListeners(new DialLayerChangeEvent(this));
    536        }
    537
    538        /**
    539         * Returns the text anchor.
    540         *
    541         * @return The text anchor (never <code>null</code>).
    542         *
    543         * @see #setTextAnchor(TextAnchor)
    544         */
    545        public TextAnchor getTextAnchor() {
    546            return this.textAnchor;
    547        }
    548
    549        /**
    550         * Sets the text anchor and sends a {@link DialLayerChangeEvent} to all
    551         * registered listeners.
    552         *
    553         * @param anchor  the anchor (<code>null</code> not permitted).
    554         *
    555         * @see #getTextAnchor()
    556         */
    557        public void setTextAnchor(TextAnchor anchor) {
    558            if (anchor == null) {
    559                throw new IllegalArgumentException("Null 'anchor' argument.");
    560            }
    561            this.textAnchor = anchor;
    562            notifyListeners(new DialLayerChangeEvent(this));
    563        }
    564
    565        /**
    566         * Returns <code>true</code> to indicate that this layer should be
    567         * clipped within the dial window.
    568         *
    569         * @return <code>true</code>.
    570         */
    571        public boolean isClippedToWindow() {
    572            return true;
    573        }
    574
    575        /**
    576         * Draws the background to the specified graphics device.  If the dial
    577         * frame specifies a window, the clipping region will already have been
    578         * set to this window before this method is called.
    579         *
    580         * @param g2  the graphics device (<code>null</code> not permitted).
    581         * @param plot  the plot (ignored here).
    582         * @param frame  the dial frame (ignored here).
    583         * @param view  the view rectangle (<code>null</code> not permitted).
    584         */
    585        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
    586                Rectangle2D view) {
    587
    588            // work out the anchor point
    589            Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius,
    590                    this.radius);
    591            Arc2D arc = new Arc2D.Double(f, this.angle, 0.0, Arc2D.OPEN);
    592            Point2D pt = arc.getStartPoint();
    593
    594            // the indicator bounds is calculated from the templateValue (which
    595            // determines the minimum size), the maxTemplateValue (which, if
    596            // specified, provides a maximum size) and the actual value
    597            FontMetrics fm = g2.getFontMetrics(this.font);
    598            double value = plot.getValue(this.datasetIndex);
    599            String valueStr = this.formatter.format(value);
    600            Rectangle2D valueBounds = TextUtilities.getTextBounds(valueStr, g2, fm);
    601
    602            // calculate the bounds of the template value
    603            String s = this.formatter.format(this.templateValue);
    604            Rectangle2D tb = TextUtilities.getTextBounds(s, g2, fm);
    605            double minW = tb.getWidth();
    606            double minH = tb.getHeight();
    607
    608            double maxW = Double.MAX_VALUE;
    609            double maxH = Double.MAX_VALUE;
    610            if (this.maxTemplateValue != null) {
    611                s = this.formatter.format(this.maxTemplateValue);
    612                tb = TextUtilities.getTextBounds(s, g2, fm);
    613                maxW = Math.max(tb.getWidth(), minW);
    614                maxH = Math.max(tb.getHeight(), minH);
    615            }
    616            double w = fixToRange(valueBounds.getWidth(), minW, maxW);
    617            double h = fixToRange(valueBounds.getHeight(), minH, maxH);
    618
    619            // align this rectangle to the frameAnchor
    620            Rectangle2D bounds = RectangleAnchor.createRectangle(new Size2D(w, h),
    621                    pt.getX(), pt.getY(), this.frameAnchor);
    622
    623            // add the insets
    624            Rectangle2D fb = this.insets.createOutsetRectangle(bounds);
    625
    626            // draw the background
    627            g2.setPaint(this.backgroundPaint);
    628            g2.fill(fb);
    629
    630            // draw the border
    631            g2.setStroke(this.outlineStroke);
    632            g2.setPaint(this.outlinePaint);
    633            g2.draw(fb);
    634
    635            // now find the text anchor point
    636            Shape savedClip = g2.getClip();
    637            g2.clip(fb);
    638
    639            Point2D pt2 = RectangleAnchor.coordinates(bounds, this.valueAnchor);
    640            g2.setPaint(this.paint);
    641            g2.setFont(this.font);
    642            TextUtilities.drawAlignedString(valueStr, g2, (float) pt2.getX(),
    643                    (float) pt2.getY(), this.textAnchor);
    644            g2.setClip(savedClip);
    645
    646        }
    647
    648        /**
    649         * A utility method that adjusts a value, if necessary, to be within a
    650         * specified range.
    651         *
    652         * @param x  the value.
    653         * @param minX  the minimum value in the range.
    654         * @param maxX  the maximum value in the range.
    655         *
    656         * @return The adjusted value.
    657         */
    658        private double fixToRange(double x, double minX, double maxX) {
    659            if (minX > maxX) {
    660                throw new IllegalArgumentException("Requires 'minX' <= 'maxX'.");
    661            }
    662            if (x < minX) {
    663                return minX;
    664            }
    665            else if (x > maxX) {
    666                return maxX;
    667            }
    668            else {
    669                return x;
    670            }
    671        }
    672
    673        /**
    674         * Tests this instance for equality with an arbitrary object.
    675         *
    676         * @param obj  the object (<code>null</code> permitted).
    677         *
    678         * @return A boolean.
    679         */
    680        public boolean equals(Object obj) {
    681            if (obj == this) {
    682                return true;
    683            }
    684            if (!(obj instanceof DialValueIndicator)) {
    685                return false;
    686            }
    687            DialValueIndicator that = (DialValueIndicator) obj;
    688            if (this.datasetIndex != that.datasetIndex) {
    689                return false;
    690            }
    691            if (this.angle != that.angle) {
    692                return false;
    693            }
    694            if (this.radius != that.radius) {
    695                return false;
    696            }
    697            if (!this.frameAnchor.equals(that.frameAnchor)) {
    698                return false;
    699            }
    700            if (!this.templateValue.equals(that.templateValue)) {
    701                return false;
    702            }
    703            if (!ObjectUtilities.equal(this.maxTemplateValue,
    704                    that.maxTemplateValue)) {
    705                return false;
    706            }
    707            if (!this.font.equals(that.font)) {
    708                return false;
    709            }
    710            if (!PaintUtilities.equal(this.paint, that.paint)) {
    711                return false;
    712            }
    713            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
    714                return false;
    715            }
    716            if (!this.outlineStroke.equals(that.outlineStroke)) {
    717                return false;
    718            }
    719            if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
    720                return false;
    721            }
    722            if (!this.insets.equals(that.insets)) {
    723                return false;
    724            }
    725            if (!this.valueAnchor.equals(that.valueAnchor)) {
    726                return false;
    727            }
    728            if (!this.textAnchor.equals(that.textAnchor)) {
    729                return false;
    730            }
    731            return super.equals(obj);
    732        }
    733
    734        /**
    735         * Returns a hash code for this instance.
    736         *
    737         * @return The hash code.
    738         */
    739        public int hashCode() {
    740            int result = 193;
    741            result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
    742            result = 37 * result + HashUtilities.hashCodeForPaint(
    743                    this.backgroundPaint);
    744            result = 37 * result + HashUtilities.hashCodeForPaint(
    745                    this.outlinePaint);
    746            result = 37 * result + this.outlineStroke.hashCode();
    747            return result;
    748        }
    749
    750        /**
    751         * Returns a clone of this instance.
    752         *
    753         * @return The clone.
    754         *
    755         * @throws CloneNotSupportedException if some attribute of this instance
    756         *     cannot be cloned.
    757         */
    758        public Object clone() throws CloneNotSupportedException {
    759            return super.clone();
    760        }
    761
    762        /**
    763         * Provides serialization support.
    764         *
    765         * @param stream  the output stream.
    766         *
    767         * @throws IOException  if there is an I/O error.
    768         */
    769        private void writeObject(ObjectOutputStream stream) throws IOException {
    770            stream.defaultWriteObject();
    771            SerialUtilities.writePaint(this.paint, stream);
    772            SerialUtilities.writePaint(this.backgroundPaint, stream);
    773            SerialUtilities.writePaint(this.outlinePaint, stream);
    774            SerialUtilities.writeStroke(this.outlineStroke, stream);
    775        }
    776
    777        /**
    778         * Provides serialization support.
    779         *
    780         * @param stream  the input stream.
    781         *
    782         * @throws IOException  if there is an I/O error.
    783         * @throws ClassNotFoundException  if there is a classpath problem.
    784         */
    785        private void readObject(ObjectInputStream stream)
    786                throws IOException, ClassNotFoundException {
    787            stream.defaultReadObject();
    788            this.paint = SerialUtilities.readPaint(stream);
    789            this.backgroundPaint = SerialUtilities.readPaint(stream);
    790            this.outlinePaint = SerialUtilities.readPaint(stream);
    791            this.outlineStroke = SerialUtilities.readStroke(stream);
    792        }
    793
    794    }