    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     * LineRenderer3D.java
    029     * -------------------
    030     * (C) Copyright 2004-2007, 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     * $Id: LineRenderer3D.java,v 1.10.2.7 2007/01/17 14:16:11 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 15-Oct-2004 : Version 1 (TS);
    040     * 05-Nov-2004 : Modified drawItem() signature (DG);
    041     * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
    042     * 26-Jan-2005 : Update for changes in super class (DG);
    043     * 13-Apr-2005 : Check item visibility in drawItem() method (DG);
    044     * 09-Jun-2005 : Use addItemEntity() in drawItem() method (DG);
    045     * 10-Jun-2005 : Fixed capitalisation of setXOffset() and setYOffset() (DG);
    046     * ------------- JFREECHART 1.0.x ---------------------------------------------
    047     * 01-Dec-2006 : Fixed equals() and serialization (DG);
    048     * 17-Jan-2007 : Fixed bug in drawDomainGridline() method and added
    049     *               argument check to setWallPaint() (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            notifyListeners(new RendererChangeEvent(this));
    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            notifyListeners(new RendererChangeEvent(this));
    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            notifyListeners(new RendererChangeEvent(this));
    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            // fill background...
    232            Paint backgroundPaint = plot.getBackgroundPaint();
    233            if (backgroundPaint != null) {
    234                g2.setPaint(backgroundPaint);
    235                g2.fill(clip);
    236            }
    237
    238            GeneralPath leftWall = new GeneralPath();
    239            leftWall.moveTo(x0, y0);
    240            leftWall.lineTo(x0, y2);
    241            leftWall.lineTo(x1, y3);
    242            leftWall.lineTo(x1, y1);
    243            leftWall.closePath();
    244            g2.setPaint(getWallPaint());
    245            g2.fill(leftWall);
    246
    247            GeneralPath bottomWall = new GeneralPath();
    248            bottomWall.moveTo(x0, y0);
    249            bottomWall.lineTo(x1, y1);
    250            bottomWall.lineTo(x3, y1);
    251            bottomWall.lineTo(x2, y0);
    252            bottomWall.closePath();
    253            g2.setPaint(getWallPaint());
    254            g2.fill(bottomWall);
    255
    256            // higlight the background corners...
    257            g2.setPaint(Color.lightGray);
    258            Line2D corner = new Line2D.Double(x0, y0, x1, y1);
    259            g2.draw(corner);
    260            corner.setLine(x1, y1, x1, y3);
    261            g2.draw(corner);
    262            corner.setLine(x1, y1, x3, y1);
    263            g2.draw(corner);
    264
    265            // draw background image, if there is one...
    266            Image backgroundImage = plot.getBackgroundImage();
    267            if (backgroundImage != null) {
    268                Composite originalComposite = g2.getComposite();
    269                g2.setComposite(AlphaComposite.getInstance(
    270                        AlphaComposite.SRC, plot.getBackgroundAlpha()));
    271                g2.drawImage(backgroundImage, (int) x1, (int) y3,
    272                        (int) (x3 - x1 + 1), (int) (y1 - y3 + 1), null);
    273                g2.setComposite(originalComposite);
    274            }
    275
    276        }
    277
    278        /**
    279         * Draws the outline for the plot.
    280         *
    281         * @param g2  the graphics device.
    282         * @param plot  the plot.
    283         * @param dataArea  the area inside the axes.
    284         */
    285        public void drawOutline(Graphics2D g2, CategoryPlot plot,
    286                                Rectangle2D dataArea) {
    287
    288            float x0 = (float) dataArea.getX();
    289            float x1 = x0 + (float) Math.abs(this.xOffset);
    290            float x3 = (float) dataArea.getMaxX();
    291            float x2 = x3 - (float) Math.abs(this.xOffset);
    292
    293            float y0 = (float) dataArea.getMaxY();
    294            float y1 = y0 - (float) Math.abs(this.yOffset);
    295            float y3 = (float) dataArea.getMinY();
    296            float y2 = y3 + (float) Math.abs(this.yOffset);
    297
    298            GeneralPath clip = new GeneralPath();
    299            clip.moveTo(x0, y0);
    300            clip.lineTo(x0, y2);
    301            clip.lineTo(x1, y3);
    302            clip.lineTo(x3, y3);
    303            clip.lineTo(x3, y1);
    304            clip.lineTo(x2, y0);
    305            clip.closePath();
    306
    307            // put an outline around the data area...
    308            Stroke outlineStroke = plot.getOutlineStroke();
    309            Paint outlinePaint = plot.getOutlinePaint();
    310            if ((outlineStroke != null) && (outlinePaint != null)) {
    311                g2.setStroke(outlineStroke);
    312                g2.setPaint(outlinePaint);
    313                g2.draw(clip);
    314            }
    315
    316        }
    317
    318        /**
    319         * Draws a grid line against the domain axis.
    320         *
    321         * @param g2  the graphics device.
    322         * @param plot  the plot.
    323         * @param dataArea  the area for plotting data (not yet adjusted for any
    324         *                  3D effect).
    325         * @param value  the Java2D value at which the grid line should be drawn.
    326         *
    327         */
    328        public void drawDomainGridline(Graphics2D g2,
    329                                       CategoryPlot plot,
    330                                       Rectangle2D dataArea,
    331                                       double value) {
    332
    333            Line2D line1 = null;
    334            Line2D line2 = null;
    335            PlotOrientation orientation = plot.getOrientation();
    336            if (orientation == PlotOrientation.HORIZONTAL) {
    337                double y0 = value;
    338                double y1 = value - getYOffset();
    339                double x0 = dataArea.getMinX();
    340                double x1 = x0 + getXOffset();
    341                double x2 = dataArea.getMaxX();
    342                line1 = new Line2D.Double(x0, y0, x1, y1);
    343                line2 = new Line2D.Double(x1, y1, x2, y1);
    344            }
    345            else if (orientation == PlotOrientation.VERTICAL) {
    346                double x0 = value;
    347                double x1 = value + getXOffset();
    348                double y0 = dataArea.getMaxY();
    349                double y1 = y0 - getYOffset();
    350                double y2 = dataArea.getMinY();
    351                line1 = new Line2D.Double(x0, y0, x1, y1);
    352                line2 = new Line2D.Double(x1, y1, x1, y2);
    353            }
    354            g2.setPaint(plot.getDomainGridlinePaint());
    355            g2.setStroke(plot.getDomainGridlineStroke());
    356            g2.draw(line1);
    357            g2.draw(line2);
    358
    359        }
    360
    361        /**
    362         * Draws a grid line against the range axis.
    363         *
    364         * @param g2  the graphics device.
    365         * @param plot  the plot.
    366         * @param axis  the value axis.
    367         * @param dataArea  the area for plotting data (not yet adjusted for any
    368         *                  3D effect).
    369         * @param value  the value at which the grid line should be drawn.
    370         *
    371         */
    372        public void drawRangeGridline(Graphics2D g2,
    373                                      CategoryPlot plot,
    374                                      ValueAxis axis,
    375                                      Rectangle2D dataArea,
    376                                      double value) {
    377
    378            Range range = axis.getRange();
    379
    380            if (!range.contains(value)) {
    381                return;
    382            }
    383
    384            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    385                    dataArea.getY() + getYOffset(),
    386                    dataArea.getWidth() - getXOffset(),
    387                    dataArea.getHeight() - getYOffset());
    388
    389            Line2D line1 = null;
    390            Line2D line2 = null;
    391            PlotOrientation orientation = plot.getOrientation();
    392            if (orientation == PlotOrientation.HORIZONTAL) {
    393                double x0 = axis.valueToJava2D(value, adjusted,
    394                        plot.getRangeAxisEdge());
    395                double x1 = x0 + getXOffset();
    396                double y0 = dataArea.getMaxY();
    397                double y1 = y0 - getYOffset();
    398                double y2 = dataArea.getMinY();
    399                line1 = new Line2D.Double(x0, y0, x1, y1);
    400                line2 = new Line2D.Double(x1, y1, x1, y2);
    401            }
    402            else if (orientation == PlotOrientation.VERTICAL) {
    403                double y0 = axis.valueToJava2D(value, adjusted,
    404                        plot.getRangeAxisEdge());
    405                double y1 = y0 - getYOffset();
    406                double x0 = dataArea.getMinX();
    407                double x1 = x0 + getXOffset();
    408                double x2 = dataArea.getMaxX();
    409                line1 = new Line2D.Double(x0, y0, x1, y1);
    410                line2 = new Line2D.Double(x1, y1, x2, y1);
    411            }
    412            g2.setPaint(plot.getRangeGridlinePaint());
    413            g2.setStroke(plot.getRangeGridlineStroke());
    414            g2.draw(line1);
    415            g2.draw(line2);
    416
    417        }
    418
    419        /**
    420         * Draws a range marker.
    421         *
    422         * @param g2  the graphics device.
    423         * @param plot  the plot.
    424         * @param axis  the value axis.
    425         * @param marker  the marker.
    426         * @param dataArea  the area for plotting data (not including 3D effect).
    427         */
    428        public void drawRangeMarker(Graphics2D g2,
    429                                    CategoryPlot plot,
    430                                    ValueAxis axis,
    431                                    Marker marker,
    432                                    Rectangle2D dataArea) {
    433
    434            if (marker instanceof ValueMarker) {
    435                ValueMarker vm = (ValueMarker) marker;
    436                double value = vm.getValue();
    437                Range range = axis.getRange();
    438                if (!range.contains(value)) {
    439                    return;
    440                }
    441
    442                Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    443                        dataArea.getY() + getYOffset(),
    444                        dataArea.getWidth() - getXOffset(),
    445                        dataArea.getHeight() - getYOffset());
    446
    447                GeneralPath path = null;
    448                PlotOrientation orientation = plot.getOrientation();
    449                if (orientation == PlotOrientation.HORIZONTAL) {
    450                    float x = (float) axis.valueToJava2D(value, adjusted,
    451                            plot.getRangeAxisEdge());
    452                    float y = (float) adjusted.getMaxY();
    453                    path = new GeneralPath();
    454                    path.moveTo(x, y);
    455                    path.lineTo((float) (x + getXOffset()),
    456                            y - (float) getYOffset());
    457                    path.lineTo((float) (x + getXOffset()),
    458                            (float) (adjusted.getMinY() - getYOffset()));
    459                    path.lineTo(x, (float) adjusted.getMinY());
    460                    path.closePath();
    461                }
    462                else if (orientation == PlotOrientation.VERTICAL) {
    463                    float y = (float) axis.valueToJava2D(value, adjusted,
    464                            plot.getRangeAxisEdge());
    465                    float x = (float) dataArea.getX();
    466                    path = new GeneralPath();
    467                    path.moveTo(x, y);
    468                    path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
    469                    path.lineTo((float) (adjusted.getMaxX() + this.xOffset),
    470                            y - (float) this.yOffset);
    471                    path.lineTo((float) (adjusted.getMaxX()), y);
    472                    path.closePath();
    473                }
    474                g2.setPaint(marker.getPaint());
    475                g2.fill(path);
    476                g2.setPaint(marker.getOutlinePaint());
    477                g2.draw(path);
    478            }
    479        }
    480
    481       /**
    482         * Draw a single data item.
    483         *
    484         * @param g2  the graphics device.
    485         * @param state  the renderer state.
    486         * @param dataArea  the area in which the data is drawn.
    487         * @param plot  the plot.
    488         * @param domainAxis  the domain axis.
    489         * @param rangeAxis  the range axis.
    490         * @param dataset  the dataset.
    491         * @param row  the row index (zero-based).
    492         * @param column  the column index (zero-based).
    493         * @param pass  the pass index.
    494         */
    495        public void drawItem(Graphics2D g2,
    496                             CategoryItemRendererState state,
    497                             Rectangle2D dataArea,
    498                             CategoryPlot plot,
    499                             CategoryAxis domainAxis,
    500                             ValueAxis rangeAxis,
    501                             CategoryDataset dataset,
    502                             int row,
    503                             int column,
    504                             int pass) {
    505
    506            if (!getItemVisible(row, column)) {
    507                return;
    508            }
    509
    510            // nothing is drawn for null...
    511            Number v = dataset.getValue(row, column);
    512            if (v == null) {
    513                return;
    514            }
    515
    516            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    517                    dataArea.getY() + getYOffset(),
    518                    dataArea.getWidth() - getXOffset(),
    519                    dataArea.getHeight() - getYOffset());
    520
    521            PlotOrientation orientation = plot.getOrientation();
    522
    523            // current data point...
    524            double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    525                    adjusted, plot.getDomainAxisEdge());
    526            double value = v.doubleValue();
    527            double y1 = rangeAxis.valueToJava2D(value, adjusted,
    528                    plot.getRangeAxisEdge());
    529
    530            Shape shape = getItemShape(row, column);
    531            if (orientation == PlotOrientation.HORIZONTAL) {
    532                shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
    533            }
    534            else if (orientation == PlotOrientation.VERTICAL) {
    535                shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
    536            }
    537
    538            if (getItemLineVisible(row, column)) {
    539                if (column != 0) {
    540
    541                    Number previousValue = dataset.getValue(row, column - 1);
    542                    if (previousValue != null) {
    543
    544                        // previous data point...
    545                        double previous = previousValue.doubleValue();
    546                        double x0 = domainAxis.getCategoryMiddle(column - 1,
    547                                getColumnCount(), adjusted,
    548                                plot.getDomainAxisEdge());
    549                        double y0 = rangeAxis.valueToJava2D(previous, adjusted,
    550                                plot.getRangeAxisEdge());
    551
    552                        double x2 = x0 + getXOffset();
    553                        double y2 = y0 - getYOffset();
    554                        double x3 = x1 + getXOffset();
    555                        double y3 = y1 - getYOffset();
    556
    557                        GeneralPath clip = new GeneralPath();
    558
    559                        if (orientation == PlotOrientation.HORIZONTAL) {
    560                            clip.moveTo((float) y0, (float) x0);
    561                            clip.lineTo((float) y1, (float) x1);
    562                            clip.lineTo((float) y3, (float) x3);
    563                            clip.lineTo((float) y2, (float) x2);
    564                            clip.lineTo((float) y0, (float) x0);
    565                            clip.closePath();
    566                        }
    567                        else if (orientation == PlotOrientation.VERTICAL) {
    568                            clip.moveTo((float) x0, (float) y0);
    569                            clip.lineTo((float) x1, (float) y1);
    570                            clip.lineTo((float) x3, (float) y3);
    571                            clip.lineTo((float) x2, (float) y2);
    572                            clip.lineTo((float) x0, (float) y0);
    573                            clip.closePath();
    574                        }
    575
    576                        g2.setPaint(getItemPaint(row, column));
    577                        g2.fill(clip);
    578                        g2.setStroke(getItemOutlineStroke(row, column));
    579                        g2.setPaint(getItemOutlinePaint(row, column));
    580                        g2.draw(clip);
    581                    }
    582                }
    583            }
    584
    585            // draw the item label if there is one...
    586            if (isItemLabelVisible(row, column)) {
    587                drawItemLabel(g2, orientation, dataset, row, column, x1, y1,
    588                        (value < 0.0));
    589            }
    590
    591            // add an item entity, if this information is being collected
    592            EntityCollection entities = state.getEntityCollection();
    593            if (entities != null) {
    594                addItemEntity(entities, dataset, row, column, shape);
    595            }
    596
    597        }
    598
    599        /**
    600         * Checks this renderer for equality with an arbitrary object.
    601         *
    602         * @param obj  the object (<code>null</code> permitted).
    603         *
    604         * @return A boolean.
    605         */
    606        public boolean equals(Object obj) {
    607            if (obj == this) {
    608                return true;
    609            }
    610            if (!(obj instanceof LineRenderer3D)) {
    611                return false;
    612            }
    613            LineRenderer3D that = (LineRenderer3D) obj;
    614            if (this.xOffset != that.xOffset) {
    615                return false;
    616            }
    617            if (this.yOffset != that.yOffset) {
    618                return false;
    619            }
    620            if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
    621                return false;
    622            }
    623            return super.equals(obj);
    624        }
    625
    626        /**
    627         * Provides serialization support.
    628         *
    629         * @param stream  the output stream.
    630         *
    631         * @throws IOException  if there is an I/O error.
    632         */
    633        private void writeObject(ObjectOutputStream stream) throws IOException {
    634            stream.defaultWriteObject();
    635            SerialUtilities.writePaint(this.wallPaint, stream);
    636        }
    637
    638        /**
    639         * Provides serialization support.
    640         *
    641         * @param stream  the input stream.
    642         *
    643         * @throws IOException  if there is an I/O error.
    644         * @throws ClassNotFoundException  if there is a classpath problem.
    645         */
    646        private void readObject(ObjectInputStream stream)
    647                throws IOException, ClassNotFoundException {
    648            stream.defaultReadObject();
    649            this.wallPaint = SerialUtilities.readPaint(stream);
    650        }
    651
    652    }