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