001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2014, 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 * DialPlot.java 029 * ------------- 030 * (C) Copyright 2006-2014, 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 * 08-Mar-2007 : Fix in hashCode() (DG); 039 * 17-Oct-2007 : Fixed listener registration/deregistration bugs (DG); 040 * 24-Oct-2007 : Maintain pointers in their own list, so they can be 041 * drawn after other layers (DG); 042 * 15-Feb-2008 : Fixed clipping bug (1873160) (DG); 043 * 03-Jul-2013 : Use ParamChecks (DG); 044 * 045 */ 046 047package org.jfree.chart.plot.dial; 048 049import java.awt.Graphics2D; 050import java.awt.Shape; 051import java.awt.geom.Point2D; 052import java.awt.geom.Rectangle2D; 053import java.io.IOException; 054import java.io.ObjectInputStream; 055import java.io.ObjectOutputStream; 056import java.util.Iterator; 057import java.util.List; 058 059import org.jfree.chart.JFreeChart; 060import org.jfree.chart.event.PlotChangeEvent; 061import org.jfree.chart.plot.Plot; 062import org.jfree.chart.plot.PlotRenderingInfo; 063import org.jfree.chart.plot.PlotState; 064import org.jfree.chart.util.ParamChecks; 065import org.jfree.data.general.DatasetChangeEvent; 066import org.jfree.data.general.ValueDataset; 067import org.jfree.util.ObjectList; 068import org.jfree.util.ObjectUtilities; 069 070/** 071 * A dial plot composed of user-definable layers. 072 * The example shown here is generated by the <code>DialDemo2.java</code> 073 * program included in the JFreeChart Demo Collection: 074 * <br><br> 075 * <img src="../../../../../images/DialPlotSample.png" 076 * alt="DialPlotSample.png"> 077 * 078 * @since 1.0.7 079 */ 080public class DialPlot extends Plot implements DialLayerChangeListener { 081 082 /** 083 * The background layer (optional). 084 */ 085 private DialLayer background; 086 087 /** 088 * The needle cap (optional). 089 */ 090 private DialLayer cap; 091 092 /** 093 * The dial frame. 094 */ 095 private DialFrame dialFrame; 096 097 /** 098 * The dataset(s) for the dial plot. 099 */ 100 private ObjectList datasets; 101 102 /** 103 * The scale(s) for the dial plot. 104 */ 105 private ObjectList scales; 106 107 /** Storage for keys that map datasets to scales. */ 108 private ObjectList datasetToScaleMap; 109 110 /** 111 * The drawing layers for the dial plot. 112 */ 113 private List layers; 114 115 /** 116 * The pointer(s) for the dial. 117 */ 118 private List pointers; 119 120 /** 121 * The x-coordinate for the view window. 122 */ 123 private double viewX; 124 125 /** 126 * The y-coordinate for the view window. 127 */ 128 private double viewY; 129 130 /** 131 * The width of the view window, expressed as a percentage. 132 */ 133 private double viewW; 134 135 /** 136 * The height of the view window, expressed as a percentage. 137 */ 138 private double viewH; 139 140 /** 141 * Creates a new instance of <code>DialPlot</code>. 142 */ 143 public DialPlot() { 144 this(null); 145 } 146 147 /** 148 * Creates a new instance of <code>DialPlot</code>. 149 * 150 * @param dataset the dataset (<code>null</code> permitted). 151 */ 152 public DialPlot(ValueDataset dataset) { 153 this.background = null; 154 this.cap = null; 155 this.dialFrame = new ArcDialFrame(); 156 this.datasets = new ObjectList(); 157 if (dataset != null) { 158 setDataset(dataset); 159 } 160 this.scales = new ObjectList(); 161 this.datasetToScaleMap = new ObjectList(); 162 this.layers = new java.util.ArrayList(); 163 this.pointers = new java.util.ArrayList(); 164 this.viewX = 0.0; 165 this.viewY = 0.0; 166 this.viewW = 1.0; 167 this.viewH = 1.0; 168 } 169 170 /** 171 * Returns the background. 172 * 173 * @return The background (possibly <code>null</code>). 174 * 175 * @see #setBackground(DialLayer) 176 */ 177 public DialLayer getBackground() { 178 return this.background; 179 } 180 181 /** 182 * Sets the background layer and sends a {@link PlotChangeEvent} to all 183 * registered listeners. 184 * 185 * @param background the background layer (<code>null</code> permitted). 186 * 187 * @see #getBackground() 188 */ 189 public void setBackground(DialLayer background) { 190 if (this.background != null) { 191 this.background.removeChangeListener(this); 192 } 193 this.background = background; 194 if (background != null) { 195 background.addChangeListener(this); 196 } 197 fireChangeEvent(); 198 } 199 200 /** 201 * Returns the cap. 202 * 203 * @return The cap (possibly <code>null</code>). 204 * 205 * @see #setCap(DialLayer) 206 */ 207 public DialLayer getCap() { 208 return this.cap; 209 } 210 211 /** 212 * Sets the cap and sends a {@link PlotChangeEvent} to all registered 213 * listeners. 214 * 215 * @param cap the cap (<code>null</code> permitted). 216 * 217 * @see #getCap() 218 */ 219 public void setCap(DialLayer cap) { 220 if (this.cap != null) { 221 this.cap.removeChangeListener(this); 222 } 223 this.cap = cap; 224 if (cap != null) { 225 cap.addChangeListener(this); 226 } 227 fireChangeEvent(); 228 } 229 230 /** 231 * Returns the dial's frame. 232 * 233 * @return The dial's frame (never <code>null</code>). 234 * 235 * @see #setDialFrame(DialFrame) 236 */ 237 public DialFrame getDialFrame() { 238 return this.dialFrame; 239 } 240 241 /** 242 * Sets the dial's frame and sends a {@link PlotChangeEvent} to all 243 * registered listeners. 244 * 245 * @param frame the frame (<code>null</code> not permitted). 246 * 247 * @see #getDialFrame() 248 */ 249 public void setDialFrame(DialFrame frame) { 250 ParamChecks.nullNotPermitted(frame, "frame"); 251 this.dialFrame.removeChangeListener(this); 252 this.dialFrame = frame; 253 frame.addChangeListener(this); 254 fireChangeEvent(); 255 } 256 257 /** 258 * Returns the x-coordinate of the viewing rectangle. This is specified 259 * in the range 0.0 to 1.0, relative to the dial's framing rectangle. 260 * 261 * @return The x-coordinate of the viewing rectangle. 262 * 263 * @see #setView(double, double, double, double) 264 */ 265 public double getViewX() { 266 return this.viewX; 267 } 268 269 /** 270 * Returns the y-coordinate of the viewing rectangle. This is specified 271 * in the range 0.0 to 1.0, relative to the dial's framing rectangle. 272 * 273 * @return The y-coordinate of the viewing rectangle. 274 * 275 * @see #setView(double, double, double, double) 276 */ 277 public double getViewY() { 278 return this.viewY; 279 } 280 281 /** 282 * Returns the width of the viewing rectangle. This is specified 283 * in the range 0.0 to 1.0, relative to the dial's framing rectangle. 284 * 285 * @return The width of the viewing rectangle. 286 * 287 * @see #setView(double, double, double, double) 288 */ 289 public double getViewWidth() { 290 return this.viewW; 291 } 292 293 /** 294 * Returns the height of the viewing rectangle. This is specified 295 * in the range 0.0 to 1.0, relative to the dial's framing rectangle. 296 * 297 * @return The height of the viewing rectangle. 298 * 299 * @see #setView(double, double, double, double) 300 */ 301 public double getViewHeight() { 302 return this.viewH; 303 } 304 305 /** 306 * Sets the viewing rectangle, relative to the dial's framing rectangle, 307 * and sends a {@link PlotChangeEvent} to all registered listeners. 308 * 309 * @param x the x-coordinate (in the range 0.0 to 1.0). 310 * @param y the y-coordinate (in the range 0.0 to 1.0). 311 * @param w the width (in the range 0.0 to 1.0). 312 * @param h the height (in the range 0.0 to 1.0). 313 * 314 * @see #getViewX() 315 * @see #getViewY() 316 * @see #getViewWidth() 317 * @see #getViewHeight() 318 */ 319 public void setView(double x, double y, double w, double h) { 320 this.viewX = x; 321 this.viewY = y; 322 this.viewW = w; 323 this.viewH = h; 324 fireChangeEvent(); 325 } 326 327 /** 328 * Adds a layer to the plot and sends a {@link PlotChangeEvent} to all 329 * registered listeners. 330 * 331 * @param layer the layer (<code>null</code> not permitted). 332 */ 333 public void addLayer(DialLayer layer) { 334 ParamChecks.nullNotPermitted(layer, "layer"); 335 this.layers.add(layer); 336 layer.addChangeListener(this); 337 fireChangeEvent(); 338 } 339 340 /** 341 * Returns the index for the specified layer. 342 * 343 * @param layer the layer (<code>null</code> not permitted). 344 * 345 * @return The layer index. 346 */ 347 public int getLayerIndex(DialLayer layer) { 348 ParamChecks.nullNotPermitted(layer, "layer"); 349 return this.layers.indexOf(layer); 350 } 351 352 /** 353 * Removes the layer at the specified index and sends a 354 * {@link PlotChangeEvent} to all registered listeners. 355 * 356 * @param index the index. 357 */ 358 public void removeLayer(int index) { 359 DialLayer layer = (DialLayer) this.layers.get(index); 360 if (layer != null) { 361 layer.removeChangeListener(this); 362 } 363 this.layers.remove(index); 364 fireChangeEvent(); 365 } 366 367 /** 368 * Removes the specified layer and sends a {@link PlotChangeEvent} to all 369 * registered listeners. 370 * 371 * @param layer the layer (<code>null</code> not permitted). 372 */ 373 public void removeLayer(DialLayer layer) { 374 // defer argument checking 375 removeLayer(getLayerIndex(layer)); 376 } 377 378 /** 379 * Adds a pointer to the plot and sends a {@link PlotChangeEvent} to all 380 * registered listeners. 381 * 382 * @param pointer the pointer (<code>null</code> not permitted). 383 */ 384 public void addPointer(DialPointer pointer) { 385 ParamChecks.nullNotPermitted(pointer, "pointer"); 386 this.pointers.add(pointer); 387 pointer.addChangeListener(this); 388 fireChangeEvent(); 389 } 390 391 /** 392 * Returns the index for the specified pointer. 393 * 394 * @param pointer the pointer (<code>null</code> not permitted). 395 * 396 * @return The pointer index. 397 */ 398 public int getPointerIndex(DialPointer pointer) { 399 ParamChecks.nullNotPermitted(pointer, "pointer"); 400 return this.pointers.indexOf(pointer); 401 } 402 403 /** 404 * Removes the pointer at the specified index and sends a 405 * {@link PlotChangeEvent} to all registered listeners. 406 * 407 * @param index the index. 408 */ 409 public void removePointer(int index) { 410 DialPointer pointer = (DialPointer) this.pointers.get(index); 411 if (pointer != null) { 412 pointer.removeChangeListener(this); 413 } 414 this.pointers.remove(index); 415 fireChangeEvent(); 416 } 417 418 /** 419 * Removes the specified pointer and sends a {@link PlotChangeEvent} to all 420 * registered listeners. 421 * 422 * @param pointer the pointer (<code>null</code> not permitted). 423 */ 424 public void removePointer(DialPointer pointer) { 425 // defer argument checking 426 removeLayer(getPointerIndex(pointer)); 427 } 428 429 /** 430 * Returns the dial pointer that is associated with the specified 431 * dataset, or <code>null</code>. 432 * 433 * @param datasetIndex the dataset index. 434 * 435 * @return The pointer. 436 */ 437 public DialPointer getPointerForDataset(int datasetIndex) { 438 DialPointer result = null; 439 Iterator iterator = this.pointers.iterator(); 440 while (iterator.hasNext()) { 441 DialPointer p = (DialPointer) iterator.next(); 442 if (p.getDatasetIndex() == datasetIndex) { 443 return p; 444 } 445 } 446 return result; 447 } 448 449 /** 450 * Returns the primary dataset for the plot. 451 * 452 * @return The primary dataset (possibly <code>null</code>). 453 */ 454 public ValueDataset getDataset() { 455 return getDataset(0); 456 } 457 458 /** 459 * Returns the dataset at the given index. 460 * 461 * @param index the dataset index. 462 * 463 * @return The dataset (possibly <code>null</code>). 464 */ 465 public ValueDataset getDataset(int index) { 466 ValueDataset result = null; 467 if (this.datasets.size() > index) { 468 result = (ValueDataset) this.datasets.get(index); 469 } 470 return result; 471 } 472 473 /** 474 * Sets the dataset for the plot, replacing the existing dataset, if there 475 * is one, and sends a {@link PlotChangeEvent} to all registered 476 * listeners. 477 * 478 * @param dataset the dataset (<code>null</code> permitted). 479 */ 480 public void setDataset(ValueDataset dataset) { 481 setDataset(0, dataset); 482 } 483 484 /** 485 * Sets a dataset for the plot. 486 * 487 * @param index the dataset index. 488 * @param dataset the dataset (<code>null</code> permitted). 489 */ 490 public void setDataset(int index, ValueDataset dataset) { 491 492 ValueDataset existing = (ValueDataset) this.datasets.get(index); 493 if (existing != null) { 494 existing.removeChangeListener(this); 495 } 496 this.datasets.set(index, dataset); 497 if (dataset != null) { 498 dataset.addChangeListener(this); 499 } 500 501 // send a dataset change event to self... 502 DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); 503 datasetChanged(event); 504 505 } 506 507 /** 508 * Returns the number of datasets. 509 * 510 * @return The number of datasets. 511 */ 512 public int getDatasetCount() { 513 return this.datasets.size(); 514 } 515 516 /** 517 * Draws the plot. This method is usually called by the {@link JFreeChart} 518 * instance that manages the plot. 519 * 520 * @param g2 the graphics target. 521 * @param area the area in which the plot should be drawn. 522 * @param anchor the anchor point (typically the last point that the 523 * mouse clicked on, <code>null</code> is permitted). 524 * @param parentState the state for the parent plot (if any). 525 * @param info used to collect plot rendering info (<code>null</code> 526 * permitted). 527 */ 528 @Override 529 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 530 PlotState parentState, PlotRenderingInfo info) { 531 532 Shape origClip = g2.getClip(); 533 g2.setClip(area); 534 535 // first, expand the viewing area into a drawing frame 536 Rectangle2D frame = viewToFrame(area); 537 538 // draw the background if there is one... 539 if (this.background != null && this.background.isVisible()) { 540 if (this.background.isClippedToWindow()) { 541 Shape savedClip = g2.getClip(); 542 g2.clip(this.dialFrame.getWindow(frame)); 543 this.background.draw(g2, this, frame, area); 544 g2.setClip(savedClip); 545 } 546 else { 547 this.background.draw(g2, this, frame, area); 548 } 549 } 550 551 Iterator iterator = this.layers.iterator(); 552 while (iterator.hasNext()) { 553 DialLayer current = (DialLayer) iterator.next(); 554 if (current.isVisible()) { 555 if (current.isClippedToWindow()) { 556 Shape savedClip = g2.getClip(); 557 g2.clip(this.dialFrame.getWindow(frame)); 558 current.draw(g2, this, frame, area); 559 g2.setClip(savedClip); 560 } 561 else { 562 current.draw(g2, this, frame, area); 563 } 564 } 565 } 566 567 // draw the pointers 568 iterator = this.pointers.iterator(); 569 while (iterator.hasNext()) { 570 DialPointer current = (DialPointer) iterator.next(); 571 if (current.isVisible()) { 572 if (current.isClippedToWindow()) { 573 Shape savedClip = g2.getClip(); 574 g2.clip(this.dialFrame.getWindow(frame)); 575 current.draw(g2, this, frame, area); 576 g2.setClip(savedClip); 577 } 578 else { 579 current.draw(g2, this, frame, area); 580 } 581 } 582 } 583 584 // draw the cap if there is one... 585 if (this.cap != null && this.cap.isVisible()) { 586 if (this.cap.isClippedToWindow()) { 587 Shape savedClip = g2.getClip(); 588 g2.clip(this.dialFrame.getWindow(frame)); 589 this.cap.draw(g2, this, frame, area); 590 g2.setClip(savedClip); 591 } 592 else { 593 this.cap.draw(g2, this, frame, area); 594 } 595 } 596 597 if (this.dialFrame.isVisible()) { 598 this.dialFrame.draw(g2, this, frame, area); 599 } 600 601 g2.setClip(origClip); 602 603 } 604 605 /** 606 * Returns the frame surrounding the specified view rectangle. 607 * 608 * @param view the view rectangle (<code>null</code> not permitted). 609 * 610 * @return The frame rectangle. 611 */ 612 private Rectangle2D viewToFrame(Rectangle2D view) { 613 double width = view.getWidth() / this.viewW; 614 double height = view.getHeight() / this.viewH; 615 double x = view.getX() - (width * this.viewX); 616 double y = view.getY() - (height * this.viewY); 617 return new Rectangle2D.Double(x, y, width, height); 618 } 619 620 /** 621 * Returns the value from the specified dataset. 622 * 623 * @param datasetIndex the dataset index. 624 * 625 * @return The data value. 626 */ 627 public double getValue(int datasetIndex) { 628 double result = Double.NaN; 629 ValueDataset dataset = getDataset(datasetIndex); 630 if (dataset != null) { 631 Number n = dataset.getValue(); 632 if (n != null) { 633 result = n.doubleValue(); 634 } 635 } 636 return result; 637 } 638 639 /** 640 * Adds a dial scale to the plot and sends a {@link PlotChangeEvent} to 641 * all registered listeners. 642 * 643 * @param index the scale index. 644 * @param scale the scale (<code>null</code> not permitted). 645 */ 646 public void addScale(int index, DialScale scale) { 647 ParamChecks.nullNotPermitted(scale, "scale"); 648 DialScale existing = (DialScale) this.scales.get(index); 649 if (existing != null) { 650 removeLayer(existing); 651 } 652 this.layers.add(scale); 653 this.scales.set(index, scale); 654 scale.addChangeListener(this); 655 fireChangeEvent(); 656 } 657 658 /** 659 * Returns the scale at the given index. 660 * 661 * @param index the scale index. 662 * 663 * @return The scale (possibly <code>null</code>). 664 */ 665 public DialScale getScale(int index) { 666 DialScale result = null; 667 if (this.scales.size() > index) { 668 result = (DialScale) this.scales.get(index); 669 } 670 return result; 671 } 672 673 /** 674 * Maps a dataset to a particular scale. 675 * 676 * @param index the dataset index (zero-based). 677 * @param scaleIndex the scale index (zero-based). 678 */ 679 public void mapDatasetToScale(int index, int scaleIndex) { 680 this.datasetToScaleMap.set(index, new Integer(scaleIndex)); 681 fireChangeEvent(); 682 } 683 684 /** 685 * Returns the dial scale for a specific dataset. 686 * 687 * @param datasetIndex the dataset index. 688 * 689 * @return The dial scale. 690 */ 691 public DialScale getScaleForDataset(int datasetIndex) { 692 DialScale result = (DialScale) this.scales.get(0); 693 Integer scaleIndex = (Integer) this.datasetToScaleMap.get(datasetIndex); 694 if (scaleIndex != null) { 695 result = getScale(scaleIndex.intValue()); 696 } 697 return result; 698 } 699 700 /** 701 * A utility method that computes a rectangle using relative radius values. 702 * 703 * @param rect the reference rectangle ({@code null} not permitted). 704 * @param radiusW the width radius (must be > 0.0) 705 * @param radiusH the height radius. 706 * 707 * @return A new rectangle. 708 */ 709 public static Rectangle2D rectangleByRadius(Rectangle2D rect, 710 double radiusW, double radiusH) { 711 ParamChecks.nullNotPermitted(rect, "rect"); 712 double x = rect.getCenterX(); 713 double y = rect.getCenterY(); 714 double w = rect.getWidth() * radiusW; 715 double h = rect.getHeight() * radiusH; 716 return new Rectangle2D.Double(x - w / 2.0, y - h / 2.0, w, h); 717 } 718 719 /** 720 * Receives notification when a layer has changed, and responds by 721 * forwarding a {@link PlotChangeEvent} to all registered listeners. 722 * 723 * @param event the event. 724 */ 725 @Override 726 public void dialLayerChanged(DialLayerChangeEvent event) { 727 fireChangeEvent(); 728 } 729 730 /** 731 * Tests this <code>DialPlot</code> instance for equality with an 732 * arbitrary object. The plot's dataset(s) is (are) not included in 733 * the test. 734 * 735 * @param obj the object (<code>null</code> permitted). 736 * 737 * @return A boolean. 738 */ 739 @Override 740 public boolean equals(Object obj) { 741 if (obj == this) { 742 return true; 743 } 744 if (!(obj instanceof DialPlot)) { 745 return false; 746 } 747 DialPlot that = (DialPlot) obj; 748 if (!ObjectUtilities.equal(this.background, that.background)) { 749 return false; 750 } 751 if (!ObjectUtilities.equal(this.cap, that.cap)) { 752 return false; 753 } 754 if (!this.dialFrame.equals(that.dialFrame)) { 755 return false; 756 } 757 if (this.viewX != that.viewX) { 758 return false; 759 } 760 if (this.viewY != that.viewY) { 761 return false; 762 } 763 if (this.viewW != that.viewW) { 764 return false; 765 } 766 if (this.viewH != that.viewH) { 767 return false; 768 } 769 if (!this.layers.equals(that.layers)) { 770 return false; 771 } 772 if (!this.pointers.equals(that.pointers)) { 773 return false; 774 } 775 return super.equals(obj); 776 } 777 778 /** 779 * Returns a hash code for this instance. 780 * 781 * @return The hash code. 782 */ 783 @Override 784 public int hashCode() { 785 int result = 193; 786 result = 37 * result + ObjectUtilities.hashCode(this.background); 787 result = 37 * result + ObjectUtilities.hashCode(this.cap); 788 result = 37 * result + this.dialFrame.hashCode(); 789 long temp = Double.doubleToLongBits(this.viewX); 790 result = 37 * result + (int) (temp ^ (temp >>> 32)); 791 temp = Double.doubleToLongBits(this.viewY); 792 result = 37 * result + (int) (temp ^ (temp >>> 32)); 793 temp = Double.doubleToLongBits(this.viewW); 794 result = 37 * result + (int) (temp ^ (temp >>> 32)); 795 temp = Double.doubleToLongBits(this.viewH); 796 result = 37 * result + (int) (temp ^ (temp >>> 32)); 797 return result; 798 } 799 800 /** 801 * Returns the plot type. 802 * 803 * @return <code>"DialPlot"</code> 804 */ 805 @Override 806 public String getPlotType() { 807 return "DialPlot"; 808 } 809 810 /** 811 * Provides serialization support. 812 * 813 * @param stream the output stream. 814 * 815 * @throws IOException if there is an I/O error. 816 */ 817 private void writeObject(ObjectOutputStream stream) throws IOException { 818 stream.defaultWriteObject(); 819 } 820 821 /** 822 * Provides serialization support. 823 * 824 * @param stream the input stream. 825 * 826 * @throws IOException if there is an I/O error. 827 * @throws ClassNotFoundException if there is a classpath problem. 828 */ 829 private void readObject(ObjectInputStream stream) 830 throws IOException, ClassNotFoundException { 831 stream.defaultReadObject(); 832 } 833 834 835}