    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
    044package org.jfree.chart.plot.dial;
    045
    046import java.awt.BasicStroke;
    047import java.awt.Color;
    048import java.awt.Font;
    049import java.awt.FontMetrics;
    050import java.awt.Graphics2D;
    051import java.awt.Paint;
    052import java.awt.Shape;
    053import java.awt.Stroke;
    054import java.awt.geom.Arc2D;
    055import java.awt.geom.Point2D;
    056import java.awt.geom.Rectangle2D;
    057import java.io.IOException;
    058import java.io.ObjectInputStream;
    059import java.io.ObjectOutputStream;
    060import java.io.Serializable;
    061import java.text.DecimalFormat;
    062import java.text.NumberFormat;
    063
    064import org.jfree.chart.HashUtilities;
    065import org.jfree.io.SerialUtilities;
    066import org.jfree.text.TextUtilities;
    067import org.jfree.ui.RectangleAnchor;
    068import org.jfree.ui.RectangleInsets;
    069import org.jfree.ui.Size2D;
    070import org.jfree.ui.TextAnchor;
    071import org.jfree.util.ObjectUtilities;
    072import org.jfree.util.PaintUtilities;
    073import org.jfree.util.PublicCloneable;
    074
    075/**
    076 * A value indicator for a {@link DialPlot}.
    077 *
    078 * @since 1.0.7
    079 */
    080public 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}