    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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     * LineRenderer3D.java
    029     * -------------------
    030     * (C) Copyright 2004-2008, by Tobias Selb and Contributors.
    031     *
    032     * Original Author:  Tobias Selb (http://www.uepselon.com);
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * Changes
    036     * -------
    037     * 15-Oct-2004 : Version 1 (TS);
    038     * 05-Nov-2004 : Modified drawItem() signature (DG);
    039     * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
    040     * 26-Jan-2005 : Update for changes in super class (DG);
    041     * 13-Apr-2005 : Check item visibility in drawItem() method (DG);
    042     * 09-Jun-2005 : Use addItemEntity() in drawItem() method (DG);
    043     * 10-Jun-2005 : Fixed capitalisation of setXOffset() and setYOffset() (DG);
    044     * ------------- JFREECHART 1.0.x ---------------------------------------------
    045     * 01-Dec-2006 : Fixed equals() and serialization (DG);
    046     * 17-Jan-2007 : Fixed bug in drawDomainGridline() method and added
    047     *               argument check to setWallPaint() (DG);
    048     * 03-Apr-2007 : Fixed bugs in drawBackground() method (DG);
    049     * 16-Oct-2007 : Fixed bug in range marker drawing (DG);
    050     *
    051     */
    052
    053    package org.jfree.chart.renderer.category;
    054
    055    import java.awt.AlphaComposite;
    056    import java.awt.Color;
    057    import java.awt.Composite;
    058    import java.awt.Graphics2D;
    059    import java.awt.Image;
    060    import java.awt.Paint;
    061    import java.awt.Shape;
    062    import java.awt.Stroke;
    063    import java.awt.geom.GeneralPath;
    064    import java.awt.geom.Line2D;
    065    import java.awt.geom.Rectangle2D;
    066    import java.io.IOException;
    067    import java.io.ObjectInputStream;
    068    import java.io.ObjectOutputStream;
    069    import java.io.Serializable;
    070
    071    import org.jfree.chart.Effect3D;
    072    import org.jfree.chart.axis.CategoryAxis;
    073    import org.jfree.chart.axis.ValueAxis;
    074    import org.jfree.chart.entity.EntityCollection;
    075    import org.jfree.chart.event.RendererChangeEvent;
    076    import org.jfree.chart.plot.CategoryPlot;
    077    import org.jfree.chart.plot.Marker;
    078    import org.jfree.chart.plot.PlotOrientation;
    079    import org.jfree.chart.plot.ValueMarker;
    080    import org.jfree.data.Range;
    081    import org.jfree.data.category.CategoryDataset;
    082    import org.jfree.io.SerialUtilities;
    083    import org.jfree.util.PaintUtilities;
    084    import org.jfree.util.ShapeUtilities;
    085
    086    /**
    087     * A line renderer with a 3D effect.
    088     */
    089    public class LineRenderer3D extends LineAndShapeRenderer
    090                                implements Effect3D, Serializable {
    091
    092        /** For serialization. */
    093        private static final long serialVersionUID = 5467931468380928736L;
    094
    095        /** The default x-offset for the 3D effect. */
    096        public static final double DEFAULT_X_OFFSET = 12.0;
    097
    098        /** The default y-offset for the 3D effect. */
    099        public static final double DEFAULT_Y_OFFSET = 8.0;
    100
    101        /** The default wall paint. */
    102        public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
    103
    104        /** The size of x-offset for the 3D effect. */
    105        private double xOffset;
    106
    107        /** The size of y-offset for the 3D effect. */
    108        private double yOffset;
    109
    110        /** The paint used to shade the left and lower 3D wall. */
    111        private transient Paint wallPaint;
    112
    113        /**
    114         * Creates a new renderer.
    115         */
    116        public LineRenderer3D() {
    117            super(true, false);  //Create a line renderer only
    118            this.xOffset = DEFAULT_X_OFFSET;
    119            this.yOffset = DEFAULT_Y_OFFSET;
    120            this.wallPaint = DEFAULT_WALL_PAINT;
    121        }
    122
    123        /**
    124         * Returns the x-offset for the 3D effect.
    125         *
    126         * @return The x-offset.
    127         *
    128         * @see #setXOffset(double)
    129         * @see #getYOffset()
    130         */
    131        public double getXOffset() {
    132            return this.xOffset;
    133        }
    134
    135        /**
    136         * Returns the y-offset for the 3D effect.
    137         *
    138         * @return The y-offset.
    139         *
    140         * @see #setYOffset(double)
    141         * @see #getXOffset()
    142         */
    143        public double getYOffset() {
    144            return this.yOffset;
    145        }
    146
    147        /**
    148         * Sets the x-offset and sends a {@link RendererChangeEvent} to all
    149         * registered listeners.
    150         *
    151         * @param xOffset  the x-offset.
    152         *
    153         * @see #getXOffset()
    154         */
    155        public void setXOffset(double xOffset) {
    156            this.xOffset = xOffset;
    157            fireChangeEvent();
    158        }
    159
    160        /**
    161         * Sets the y-offset and sends a {@link RendererChangeEvent} to all
    162         * registered listeners.
    163         *
    164         * @param yOffset  the y-offset.
    165         *
    166         * @see #getYOffset()
    167         */
    168        public void setYOffset(double yOffset) {
    169            this.yOffset = yOffset;
    170            fireChangeEvent();
    171        }
    172
    173        /**
    174         * Returns the paint used to highlight the left and bottom wall in the plot
    175         * background.
    176         *
    177         * @return The paint.
    178         *
    179         * @see #setWallPaint(Paint)
    180         */
    181        public Paint getWallPaint() {
    182            return this.wallPaint;
    183        }
    184
    185        /**
    186         * Sets the paint used to hightlight the left and bottom walls in the plot
    187         * background, and sends a {@link RendererChangeEvent} to all
    188         * registered listeners.
    189         *
    190         * @param paint  the paint (<code>null</code> not permitted).
    191         *
    192         * @see #getWallPaint()
    193         */
    194        public void setWallPaint(Paint paint) {
    195            if (paint == null) {
    196                throw new IllegalArgumentException("Null 'paint' argument.");
    197            }
    198            this.wallPaint = paint;
    199            fireChangeEvent();
    200        }
    201
    202        /**
    203         * Draws the background for the plot.
    204         *
    205         * @param g2  the graphics device.
    206         * @param plot  the plot.
    207         * @param dataArea  the area inside the axes.
    208         */
    209        public void drawBackground(Graphics2D g2, CategoryPlot plot,
    210                                   Rectangle2D dataArea) {
    211
    212            float x0 = (float) dataArea.getX();
    213            float x1 = x0 + (float) Math.abs(this.xOffset);
    214            float x3 = (float) dataArea.getMaxX();
    215            float x2 = x3 - (float) Math.abs(this.xOffset);
    216
    217            float y0 = (float) dataArea.getMaxY();
    218            float y1 = y0 - (float) Math.abs(this.yOffset);
    219            float y3 = (float) dataArea.getMinY();
    220            float y2 = y3 + (float) Math.abs(this.yOffset);
    221
    222            GeneralPath clip = new GeneralPath();
    223            clip.moveTo(x0, y0);
    224            clip.lineTo(x0, y2);
    225            clip.lineTo(x1, y3);
    226            clip.lineTo(x3, y3);
    227            clip.lineTo(x3, y1);
    228            clip.lineTo(x2, y0);
    229            clip.closePath();
    230
    231            Composite originalComposite = g2.getComposite();
    232            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    233                    plot.getBackgroundAlpha()));
    234
    235            // fill background...
    236            Paint backgroundPaint = plot.getBackgroundPaint();
    237            if (backgroundPaint != null) {
    238                g2.setPaint(backgroundPaint);
    239                g2.fill(clip);
    240            }
    241
    242            GeneralPath leftWall = new GeneralPath();
    243            leftWall.moveTo(x0, y0);
    244            leftWall.lineTo(x0, y2);
    245            leftWall.lineTo(x1, y3);
    246            leftWall.lineTo(x1, y1);
    247            leftWall.closePath();
    248            g2.setPaint(getWallPaint());
    249            g2.fill(leftWall);
    250
    251            GeneralPath bottomWall = new GeneralPath();
    252            bottomWall.moveTo(x0, y0);
    253            bottomWall.lineTo(x1, y1);
    254            bottomWall.lineTo(x3, y1);
    255            bottomWall.lineTo(x2, y0);
    256            bottomWall.closePath();
    257            g2.setPaint(getWallPaint());
    258            g2.fill(bottomWall);
    259
    260            // higlight the background corners...
    261            g2.setPaint(Color.lightGray);
    262            Line2D corner = new Line2D.Double(x0, y0, x1, y1);
    263            g2.draw(corner);
    264            corner.setLine(x1, y1, x1, y3);
    265            g2.draw(corner);
    266            corner.setLine(x1, y1, x3, y1);
    267            g2.draw(corner);
    268
    269            // draw background image, if there is one...
    270            Image backgroundImage = plot.getBackgroundImage();
    271            if (backgroundImage != null) {
    272                Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX()
    273                        + getXOffset(), dataArea.getY(),
    274                        dataArea.getWidth() - getXOffset(),
    275                        dataArea.getHeight() - getYOffset());
    276                plot.drawBackgroundImage(g2, adjusted);
    277            }
    278
    279            g2.setComposite(originalComposite);
    280
    281        }
    282
    283        /**
    284         * Draws the outline for the plot.
    285         *
    286         * @param g2  the graphics device.
    287         * @param plot  the plot.
    288         * @param dataArea  the area inside the axes.
    289         */
    290        public void drawOutline(Graphics2D g2, CategoryPlot plot,
    291                                Rectangle2D dataArea) {
    292
    293            float x0 = (float) dataArea.getX();
    294            float x1 = x0 + (float) Math.abs(this.xOffset);
    295            float x3 = (float) dataArea.getMaxX();
    296            float x2 = x3 - (float) Math.abs(this.xOffset);
    297
    298            float y0 = (float) dataArea.getMaxY();
    299            float y1 = y0 - (float) Math.abs(this.yOffset);
    300            float y3 = (float) dataArea.getMinY();
    301            float y2 = y3 + (float) Math.abs(this.yOffset);
    302
    303            GeneralPath clip = new GeneralPath();
    304            clip.moveTo(x0, y0);
    305            clip.lineTo(x0, y2);
    306            clip.lineTo(x1, y3);
    307            clip.lineTo(x3, y3);
    308            clip.lineTo(x3, y1);
    309            clip.lineTo(x2, y0);
    310            clip.closePath();
    311
    312            // put an outline around the data area...
    313            Stroke outlineStroke = plot.getOutlineStroke();
    314            Paint outlinePaint = plot.getOutlinePaint();
    315            if ((outlineStroke != null) && (outlinePaint != null)) {
    316                g2.setStroke(outlineStroke);
    317                g2.setPaint(outlinePaint);
    318                g2.draw(clip);
    319            }
    320
    321        }
    322
    323        /**
    324         * Draws a grid line against the domain axis.
    325         *
    326         * @param g2  the graphics device.
    327         * @param plot  the plot.
    328         * @param dataArea  the area for plotting data (not yet adjusted for any
    329         *                  3D effect).
    330         * @param value  the Java2D value at which the grid line should be drawn.
    331         *
    332         */
    333        public void drawDomainGridline(Graphics2D g2,
    334                                       CategoryPlot plot,
    335                                       Rectangle2D dataArea,
    336                                       double value) {
    337
    338            Line2D line1 = null;
    339            Line2D line2 = null;
    340            PlotOrientation orientation = plot.getOrientation();
    341            if (orientation == PlotOrientation.HORIZONTAL) {
    342                double y0 = value;
    343                double y1 = value - getYOffset();
    344                double x0 = dataArea.getMinX();
    345                double x1 = x0 + getXOffset();
    346                double x2 = dataArea.getMaxX();
    347                line1 = new Line2D.Double(x0, y0, x1, y1);
    348                line2 = new Line2D.Double(x1, y1, x2, y1);
    349            }
    350            else if (orientation == PlotOrientation.VERTICAL) {
    351                double x0 = value;
    352                double x1 = value + getXOffset();
    353                double y0 = dataArea.getMaxY();
    354                double y1 = y0 - getYOffset();
    355                double y2 = dataArea.getMinY();
    356                line1 = new Line2D.Double(x0, y0, x1, y1);
    357                line2 = new Line2D.Double(x1, y1, x1, y2);
    358            }
    359            g2.setPaint(plot.getDomainGridlinePaint());
    360            g2.setStroke(plot.getDomainGridlineStroke());
    361            g2.draw(line1);
    362            g2.draw(line2);
    363
    364        }
    365
    366        /**
    367         * Draws a grid line against the range axis.
    368         *
    369         * @param g2  the graphics device.
    370         * @param plot  the plot.
    371         * @param axis  the value axis.
    372         * @param dataArea  the area for plotting data (not yet adjusted for any
    373         *                  3D effect).
    374         * @param value  the value at which the grid line should be drawn.
    375         *
    376         */
    377        public void drawRangeGridline(Graphics2D g2,
    378                                      CategoryPlot plot,
    379                                      ValueAxis axis,
    380                                      Rectangle2D dataArea,
    381                                      double value) {
    382
    383            Range range = axis.getRange();
    384
    385            if (!range.contains(value)) {
    386                return;
    387            }
    388
    389            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    390                    dataArea.getY() + getYOffset(),
    391                    dataArea.getWidth() - getXOffset(),
    392                    dataArea.getHeight() - getYOffset());
    393
    394            Line2D line1 = null;
    395            Line2D line2 = null;
    396            PlotOrientation orientation = plot.getOrientation();
    397            if (orientation == PlotOrientation.HORIZONTAL) {
    398                double x0 = axis.valueToJava2D(value, adjusted,
    399                        plot.getRangeAxisEdge());
    400                double x1 = x0 + getXOffset();
    401                double y0 = dataArea.getMaxY();
    402                double y1 = y0 - getYOffset();
    403                double y2 = dataArea.getMinY();
    404                line1 = new Line2D.Double(x0, y0, x1, y1);
    405                line2 = new Line2D.Double(x1, y1, x1, y2);
    406            }
    407            else if (orientation == PlotOrientation.VERTICAL) {
    408                double y0 = axis.valueToJava2D(value, adjusted,
    409                        plot.getRangeAxisEdge());
    410                double y1 = y0 - getYOffset();
    411                double x0 = dataArea.getMinX();
    412                double x1 = x0 + getXOffset();
    413                double x2 = dataArea.getMaxX();
    414                line1 = new Line2D.Double(x0, y0, x1, y1);
    415                line2 = new Line2D.Double(x1, y1, x2, y1);
    416            }
    417            g2.setPaint(plot.getRangeGridlinePaint());
    418            g2.setStroke(plot.getRangeGridlineStroke());
    419            g2.draw(line1);
    420            g2.draw(line2);
    421
    422        }
    423
    424        /**
    425         * Draws a range marker.
    426         *
    427         * @param g2  the graphics device.
    428         * @param plot  the plot.
    429         * @param axis  the value axis.
    430         * @param marker  the marker.
    431         * @param dataArea  the area for plotting data (not including 3D effect).
    432         */
    433        public void drawRangeMarker(Graphics2D g2,
    434                                    CategoryPlot plot,
    435                                    ValueAxis axis,
    436                                    Marker marker,
    437                                    Rectangle2D dataArea) {
    438
    439            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    440                    dataArea.getY() + getYOffset(),
    441                    dataArea.getWidth() - getXOffset(),
    442                    dataArea.getHeight() - getYOffset());
    443
    444            if (marker instanceof ValueMarker) {
    445                ValueMarker vm = (ValueMarker) marker;
    446                double value = vm.getValue();
    447                Range range = axis.getRange();
    448                if (!range.contains(value)) {
    449                    return;
    450                }
    451
    452                GeneralPath path = null;
    453                PlotOrientation orientation = plot.getOrientation();
    454                if (orientation == PlotOrientation.HORIZONTAL) {
    455                    float x = (float) axis.valueToJava2D(value, adjusted,
    456                            plot.getRangeAxisEdge());
    457                    float y = (float) adjusted.getMaxY();
    458                    path = new GeneralPath();
    459                    path.moveTo(x, y);
    460                    path.lineTo((float) (x + getXOffset()),
    461                            y - (float) getYOffset());
    462                    path.lineTo((float) (x + getXOffset()),
    463                            (float) (adjusted.getMinY() - getYOffset()));
    464                    path.lineTo(x, (float) adjusted.getMinY());
    465                    path.closePath();
    466                }
    467                else if (orientation == PlotOrientation.VERTICAL) {
    468                    float y = (float) axis.valueToJava2D(value, adjusted,
    469                            plot.getRangeAxisEdge());
    470                    float x = (float) dataArea.getX();
    471                    path = new GeneralPath();
    472                    path.moveTo(x, y);
    473                    path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
    474                    path.lineTo((float) (adjusted.getMaxX() + this.xOffset),
    475                            y - (float) this.yOffset);
    476                    path.lineTo((float) (adjusted.getMaxX()), y);
    477                    path.closePath();
    478                }
    479                g2.setPaint(marker.getPaint());
    480                g2.fill(path);
    481                g2.setPaint(marker.getOutlinePaint());
    482                g2.draw(path);
    483            }
    484            else {
    485                super.drawRangeMarker(g2, plot, axis, marker, adjusted);
    486                // TODO: draw the interval marker with a 3D effect
    487            }
    488        }
    489
    490       /**
    491         * Draw a single data item.
    492         *
    493         * @param g2  the graphics device.
    494         * @param state  the renderer state.
    495         * @param dataArea  the area in which the data is drawn.
    496         * @param plot  the plot.
    497         * @param domainAxis  the domain axis.
    498         * @param rangeAxis  the range axis.
    499         * @param dataset  the dataset.
    500         * @param row  the row index (zero-based).
    501         * @param column  the column index (zero-based).
    502         * @param pass  the pass index.
    503         */
    504        public void drawItem(Graphics2D g2,
    505                             CategoryItemRendererState state,
    506                             Rectangle2D dataArea,
    507                             CategoryPlot plot,
    508                             CategoryAxis domainAxis,
    509                             ValueAxis rangeAxis,
    510                             CategoryDataset dataset,
    511                             int row,
    512                             int column,
    513                             int pass) {
    514
    515            if (!getItemVisible(row, column)) {
    516                return;
    517            }
    518
    519            // nothing is drawn for null...
    520            Number v = dataset.getValue(row, column);
    521            if (v == null) {
    522                return;
    523            }
    524
    525            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    526                    dataArea.getY() + getYOffset(),
    527                    dataArea.getWidth() - getXOffset(),
    528                    dataArea.getHeight() - getYOffset());
    529
    530            PlotOrientation orientation = plot.getOrientation();
    531
    532            // current data point...
    533            double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    534                    adjusted, plot.getDomainAxisEdge());
    535            double value = v.doubleValue();
    536            double y1 = rangeAxis.valueToJava2D(value, adjusted,
    537                    plot.getRangeAxisEdge());
    538
    539            Shape shape = getItemShape(row, column);
    540            if (orientation == PlotOrientation.HORIZONTAL) {
    541                shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
    542            }
    543            else if (orientation == PlotOrientation.VERTICAL) {
    544                shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
    545            }
    546
    547            if (getItemLineVisible(row, column)) {
    548                if (column != 0) {
    549
    550                    Number previousValue = dataset.getValue(row, column - 1);
    551                    if (previousValue != null) {
    552
    553                        // previous data point...
    554                        double previous = previousValue.doubleValue();
    555                        double x0 = domainAxis.getCategoryMiddle(column - 1,
    556                                getColumnCount(), adjusted,
    557                                plot.getDomainAxisEdge());
    558                        double y0 = rangeAxis.valueToJava2D(previous, adjusted,
    559                                plot.getRangeAxisEdge());
    560
    561                        double x2 = x0 + getXOffset();
    562                        double y2 = y0 - getYOffset();
    563                        double x3 = x1 + getXOffset();
    564                        double y3 = y1 - getYOffset();
    565
    566                        GeneralPath clip = new GeneralPath();
    567
    568                        if (orientation == PlotOrientation.HORIZONTAL) {
    569                            clip.moveTo((float) y0, (float) x0);
    570                            clip.lineTo((float) y1, (float) x1);
    571                            clip.lineTo((float) y3, (float) x3);
    572                            clip.lineTo((float) y2, (float) x2);
    573                            clip.lineTo((float) y0, (float) x0);
    574                            clip.closePath();
    575                        }
    576                        else if (orientation == PlotOrientation.VERTICAL) {
    577                            clip.moveTo((float) x0, (float) y0);
    578                            clip.lineTo((float) x1, (float) y1);
    579                            clip.lineTo((float) x3, (float) y3);
    580                            clip.lineTo((float) x2, (float) y2);
    581                            clip.lineTo((float) x0, (float) y0);
    582                            clip.closePath();
    583                        }
    584
    585                        g2.setPaint(getItemPaint(row, column));
    586                        g2.fill(clip);
    587                        g2.setStroke(getItemOutlineStroke(row, column));
    588                        g2.setPaint(getItemOutlinePaint(row, column));
    589                        g2.draw(clip);
    590                    }
    591                }
    592            }
    593
    594            // draw the item label if there is one...
    595            if (isItemLabelVisible(row, column)) {
    596                drawItemLabel(g2, orientation, dataset, row, column, x1, y1,
    597                        (value < 0.0));
    598            }
    599
    600            // add an item entity, if this information is being collected
    601            EntityCollection entities = state.getEntityCollection();
    602            if (entities != null) {
    603                addItemEntity(entities, dataset, row, column, shape);
    604            }
    605
    606        }
    607
    608        /**
    609         * Checks this renderer for equality with an arbitrary object.
    610         *
    611         * @param obj  the object (<code>null</code> permitted).
    612         *
    613         * @return A boolean.
    614         */
    615        public boolean equals(Object obj) {
    616            if (obj == this) {
    617                return true;
    618            }
    619            if (!(obj instanceof LineRenderer3D)) {
    620                return false;
    621            }
    622            LineRenderer3D that = (LineRenderer3D) obj;
    623            if (this.xOffset != that.xOffset) {
    624                return false;
    625            }
    626            if (this.yOffset != that.yOffset) {
    627                return false;
    628            }
    629            if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
    630                return false;
    631            }
    632            return super.equals(obj);
    633        }
    634
    635        /**
    636         * Provides serialization support.
    637         *
    638         * @param stream  the output stream.
    639         *
    640         * @throws IOException  if there is an I/O error.
    641         */
    642        private void writeObject(ObjectOutputStream stream) throws IOException {
    643            stream.defaultWriteObject();
    644            SerialUtilities.writePaint(this.wallPaint, stream);
    645        }
    646
    647        /**
    648         * Provides serialization support.
    649         *
    650         * @param stream  the input stream.
    651         *
    652         * @throws IOException  if there is an I/O error.
    653         * @throws ClassNotFoundException  if there is a classpath problem.
    654         */
    655        private void readObject(ObjectInputStream stream)
    656                throws IOException, ClassNotFoundException {
    657            stream.defaultReadObject();
    658            this.wallPaint = SerialUtilities.readPaint(stream);
    659        }
    660
    661    }