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 * DialPlot.java
029 * -------------
030 * (C) Copyright 2006, 2007, 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 * 
043 */
044
045package org.jfree.chart.plot.dial;
046
047import java.awt.Graphics2D;
048import java.awt.Shape;
049import java.awt.geom.Point2D;
050import java.awt.geom.Rectangle2D;
051import java.io.IOException;
052import java.io.ObjectInputStream;
053import java.io.ObjectOutputStream;
054import java.util.Iterator;
055import java.util.List;
056
057import org.jfree.chart.JFreeChart;
058import org.jfree.chart.event.PlotChangeEvent;
059import org.jfree.chart.plot.Plot;
060import org.jfree.chart.plot.PlotRenderingInfo;
061import org.jfree.chart.plot.PlotState;
062import org.jfree.data.general.DatasetChangeEvent;
063import org.jfree.data.general.ValueDataset;
064import org.jfree.util.ObjectList;
065import org.jfree.util.ObjectUtilities;
066
067/**
068 * A dial plot.
069 */
070public class DialPlot extends Plot implements DialLayerChangeListener {
071
072    /**
073     * The background layer (optional).
074     */
075    private DialLayer background;
076    
077    /**
078     * The needle cap (optional).
079     */
080    private DialLayer cap;
081    
082    /**
083     * The dial frame.
084     */
085    private DialFrame dialFrame;
086    
087    /**
088     * The dataset(s) for the dial plot.
089     */
090    private ObjectList datasets;
091    
092    /**
093     * The scale(s) for the dial plot. 
094     */
095    private ObjectList scales;
096    
097    /** Storage for keys that map datasets to scales. */
098    private ObjectList datasetToScaleMap;
099
100    /**
101     * The drawing layers for the dial plot.
102     */
103    private List layers;
104    
105    /** 
106     * The pointer(s) for the dial.
107     */
108    private List pointers;
109    
110    /**
111     * The x-coordinate for the view window.
112     */
113    private double viewX;
114    
115    /**
116     * The y-coordinate for the view window.
117     */
118    private double viewY;
119    
120    /**
121     * The width of the view window, expressed as a percentage.
122     */
123    private double viewW;
124    
125    /**
126     * The height of the view window, expressed as a percentage.
127     */
128    private double viewH;
129    
130    /** 
131     * Creates a new instance of <code>DialPlot</code>.
132     */
133    public DialPlot() {
134        this(null);    
135    }
136    
137    /** 
138     * Creates a new instance of <code>DialPlot</code>.
139     * 
140     * @param dataset  the dataset (<code>null</code> permitted).
141     */
142    public DialPlot(ValueDataset dataset) {
143        this.background = null;
144        this.cap = null;
145        this.dialFrame = new ArcDialFrame();
146        this.datasets = new ObjectList();
147        if (dataset != null) {
148            this.setDataset(dataset);  
149        }
150        this.scales = new ObjectList();
151        this.datasetToScaleMap = new ObjectList();
152        this.layers = new java.util.ArrayList();
153        this.pointers = new java.util.ArrayList();
154        this.viewX = 0.0;
155        this.viewY = 0.0;
156        this.viewW = 1.0;
157        this.viewH = 1.0;
158    }
159
160    /**
161     * Returns the background.
162     *
163     * @return The background (possibly <code>null</code>).
164     *
165     * @see #setBackground(DialLayer)
166     */
167    public DialLayer getBackground() {
168        return this.background;
169    }
170    
171    /**
172     * Sets the background layer and sends a {@link PlotChangeEvent} to all
173     * registered listeners.
174     *
175     * @param background  the background layer (<code>null</code> permitted).
176     *
177     * @see #getBackground()
178     */
179    public void setBackground(DialLayer background) {
180        if (this.background != null) {
181            this.background.removeChangeListener(this);
182        }
183        this.background = background;
184        if (background != null) {
185            background.addChangeListener(this);
186        }
187        notifyListeners(new PlotChangeEvent(this));
188    }
189    
190    /**
191     * Returns the cap.
192     *
193     * @return The cap (possibly <code>null</code>).
194     *
195     * @see #setCap(DialLayer)
196     */
197    public DialLayer getCap() {
198        return this.cap;
199    }
200    
201    /**
202     * Sets the cap and sends a {@link PlotChangeEvent} to all registered 
203     * listeners.
204     *
205     * @param cap  the cap (<code>null</code> permitted).
206     *
207     * @see #getCap()
208     */
209    public void setCap(DialLayer cap) {
210        if (this.cap != null) {
211            this.cap.removeChangeListener(this);
212        }
213        this.cap = cap;
214        if (cap != null) {
215            cap.addChangeListener(this);
216        }
217        notifyListeners(new PlotChangeEvent(this));
218    }
219
220    /**
221     * Returns the dial's frame.
222     *
223     * @return The dial's frame (never <code>null</code>).
224     *
225     * @see #setDialFrame(DialFrame)
226     */
227    public DialFrame getDialFrame() {
228        return this.dialFrame;
229    }
230    
231    /**
232     * Sets the dial's frame and sends a {@link PlotChangeEvent} to all 
233     * registered listeners.
234     *
235     * @param frame  the frame (<code>null</code> not permitted).
236     *
237     * @see #getDialFrame()
238     */
239    public void setDialFrame(DialFrame frame) {
240        if (frame == null) {
241            throw new IllegalArgumentException("Null 'frame' argument.");
242        }
243        this.dialFrame.removeChangeListener(this);
244        this.dialFrame = frame;
245        frame.addChangeListener(this);
246        notifyListeners(new PlotChangeEvent(this));
247    }
248
249    /**
250     * Returns the x-coordinate of the viewing rectangle.  This is specified
251     * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
252     * 
253     * @return The x-coordinate of the viewing rectangle.
254     * 
255     * @see #setView(double, double, double, double)
256     */
257    public double getViewX() {
258        return this.viewX;
259    }
260    
261    /**
262     * Returns the y-coordinate of the viewing rectangle.  This is specified
263     * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
264     * 
265     * @return The y-coordinate of the viewing rectangle.
266     * 
267     * @see #setView(double, double, double, double)
268     */
269    public double getViewY() {
270        return this.viewY;
271    }
272    
273    /**
274     * Returns the width of the viewing rectangle.  This is specified
275     * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
276     * 
277     * @return The width of the viewing rectangle.
278     * 
279     * @see #setView(double, double, double, double)
280     */
281    public double getViewWidth() {
282        return this.viewW;
283    }
284    
285    /**
286     * Returns the height of the viewing rectangle.  This is specified
287     * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
288     * 
289     * @return The height of the viewing rectangle.
290     * 
291     * @see #setView(double, double, double, double)
292     */
293    public double getViewHeight() {
294        return this.viewH;
295    }
296    
297    /**
298     * Sets the viewing rectangle, relative to the dial's framing rectangle,
299     * and sends a {@link PlotChangeEvent} to all registered listeners.
300     * 
301     * @param x  the x-coordinate (in the range 0.0 to 1.0).
302     * @param y  the y-coordinate (in the range 0.0 to 1.0).
303     * @param w  the width (in the range 0.0 to 1.0).
304     * @param h  the height (in the range 0.0 to 1.0).
305     * 
306     * @see #getViewX()
307     * @see #getViewY()
308     * @see #getViewWidth()
309     * @see #getViewHeight()
310     */
311    public void setView(double x, double y, double w, double h) {
312        this.viewX = x;
313        this.viewY = y;
314        this.viewW = w;
315        this.viewH = h;
316        notifyListeners(new PlotChangeEvent(this));
317    }
318
319    /**
320     * Adds a layer to the plot and sends a {@link PlotChangeEvent} to all 
321     * registered listeners.
322     * 
323     * @param layer  the layer (<code>null</code> not permitted).
324     */
325    public void addLayer(DialLayer layer) {
326        if (layer == null) {
327            throw new IllegalArgumentException("Null 'layer' argument.");
328        }
329        this.layers.add(layer);
330        layer.addChangeListener(this);
331        notifyListeners(new PlotChangeEvent(this));
332    }
333    
334    /**
335     * Returns the index for the specified layer.
336     * 
337     * @param layer  the layer (<code>null</code> not permitted).
338     * 
339     * @return The layer index.
340     */
341    public int getLayerIndex(DialLayer layer) {
342        if (layer == null) {
343            throw new IllegalArgumentException("Null 'layer' argument.");
344        }
345        return this.layers.indexOf(layer);
346    }
347    
348    /**
349     * Removes the layer at the specified index and sends a 
350     * {@link PlotChangeEvent} to all registered listeners.
351     * 
352     * @param index  the index.
353     */
354    public void removeLayer(int index) {
355        DialLayer layer = (DialLayer) this.layers.get(index);
356        if (layer != null) {
357            layer.removeChangeListener(this);
358        }
359        this.layers.remove(index);
360        notifyListeners(new PlotChangeEvent(this));
361    }
362    
363    /**
364     * Removes the specified layer and sends a {@link PlotChangeEvent} to all
365     * registered listeners.
366     * 
367     * @param layer  the layer (<code>null</code> not permitted).
368     */
369    public void removeLayer(DialLayer layer) {
370        // defer argument checking
371        removeLayer(getLayerIndex(layer));
372    }
373    
374    /**
375     * Adds a pointer to the plot and sends a {@link PlotChangeEvent} to all 
376     * registered listeners.
377     * 
378     * @param pointer  the pointer (<code>null</code> not permitted).
379     */
380    public void addPointer(DialPointer pointer) {
381        if (pointer == null) {
382            throw new IllegalArgumentException("Null 'pointer' argument.");
383        }
384        this.pointers.add(pointer);
385        pointer.addChangeListener(this);
386        notifyListeners(new PlotChangeEvent(this));
387    }
388    
389    /**
390     * Returns the index for the specified pointer.
391     * 
392     * @param pointer  the pointer (<code>null</code> not permitted).
393     * 
394     * @return The pointer index.
395     */
396    public int getPointerIndex(DialPointer pointer) {
397        if (pointer == null) {
398            throw new IllegalArgumentException("Null 'pointer' argument.");
399        }
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        notifyListeners(new PlotChangeEvent(this));
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    public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 
529            PlotState parentState, PlotRenderingInfo info) {
530        
531        // first, expand the viewing area into a drawing frame
532        Rectangle2D frame = viewToFrame(area);
533        
534        // draw the background if there is one...
535        if (this.background != null && this.background.isVisible()) {
536            if (this.background.isClippedToWindow()) {
537                Shape savedClip = g2.getClip();
538                g2.setClip(this.dialFrame.getWindow(frame));
539                this.background.draw(g2, this, frame, area);
540                g2.setClip(savedClip);
541            }
542            else {
543                this.background.draw(g2, this, frame, area);
544            }
545        }
546        
547        Iterator iterator = this.layers.iterator();
548        while (iterator.hasNext()) {
549            DialLayer current = (DialLayer) iterator.next();
550            if (current.isVisible()) {
551                if (current.isClippedToWindow()) {
552                    Shape savedClip = g2.getClip();
553                    g2.setClip(this.dialFrame.getWindow(frame));
554                    current.draw(g2, this, frame, area);
555                    g2.setClip(savedClip);
556                }
557                else {
558                    current.draw(g2, this, frame, area);
559                }
560            }
561        }
562        
563        // draw the pointers
564        iterator = this.pointers.iterator();
565        while (iterator.hasNext()) {
566            DialPointer current = (DialPointer) iterator.next();
567            if (current.isVisible()) {
568                if (current.isClippedToWindow()) {
569                    Shape savedClip = g2.getClip();
570                    g2.setClip(this.dialFrame.getWindow(frame));
571                    current.draw(g2, this, frame, area);
572                    g2.setClip(savedClip);
573                }
574                else {
575                    current.draw(g2, this, frame, area);
576                }
577            }
578        }
579
580        // draw the cap if there is one...
581        if (this.cap != null && this.cap.isVisible()) {
582            if (this.cap.isClippedToWindow()) {
583                Shape savedClip = g2.getClip();
584                g2.setClip(this.dialFrame.getWindow(frame));
585                this.cap.draw(g2, this, frame, area);
586                g2.setClip(savedClip);
587            }
588            else {
589                this.cap.draw(g2, this, frame, area);
590            }
591        }
592        
593        if (this.dialFrame.isVisible()) {
594            this.dialFrame.draw(g2, this, frame, area);
595        }
596        
597    }
598    
599    /**
600     * Returns the frame surrounding the specified view rectangle.
601     * 
602     * @param view  the view rectangle (<code>null</code> not permitted).
603     * 
604     * @return The frame rectangle.
605     */
606    private Rectangle2D viewToFrame(Rectangle2D view) {
607        double width = view.getWidth() / this.viewW;
608        double height = view.getHeight() / this.viewH;
609        double x = view.getX() - (width * this.viewX);
610        double y = view.getY() - (height * this.viewY);
611        return new Rectangle2D.Double(x, y, width, height);
612    }
613    
614    /**
615     * Returns the value from the specified dataset.
616     * 
617     * @param datasetIndex  the dataset index.
618     * 
619     * @return The data value.
620     */
621    public double getValue(int datasetIndex) {
622        double result = Double.NaN;
623        ValueDataset dataset = getDataset(datasetIndex);
624        if (dataset != null) {
625            Number n = dataset.getValue();
626            if (n != null) {
627                result = n.doubleValue();
628            }
629        }
630        return result;
631    }
632    
633    /**
634     * Adds a dial scale to the plot and sends a {@link PlotChangeEvent} to 
635     * all registered listeners.
636     * 
637     * @param index  the scale index.
638     * @param scale  the scale (<code>null</code> not permitted).
639     */
640    public void addScale(int index, DialScale scale) {
641        if (scale == null) {
642            throw new IllegalArgumentException("Null 'scale' argument.");
643        }
644        DialScale existing = (DialScale) this.scales.get(index);
645        if (existing != null) {
646            removeLayer(existing);
647        }
648        this.layers.add(scale);
649        this.scales.set(index, scale);
650        scale.addChangeListener(this);
651        notifyListeners(new PlotChangeEvent(this));         
652    }
653    
654    /**
655     * Returns the scale at the given index.
656     *
657     * @param index  the scale index.
658     *
659     * @return The scale (possibly <code>null</code>).
660     */
661    public DialScale getScale(int index) {
662        DialScale result = null;
663        if (this.scales.size() > index) {
664            result = (DialScale) this.scales.get(index);
665        }
666        return result;
667    }
668
669    /**
670     * Maps a dataset to a particular scale.
671     * 
672     * @param index  the dataset index (zero-based).
673     * @param scaleIndex  the scale index (zero-based).
674     */
675    public void mapDatasetToScale(int index, int scaleIndex) {
676        this.datasetToScaleMap.set(index, new Integer(scaleIndex));  
677        notifyListeners(new PlotChangeEvent(this)); 
678    }
679    
680    /**
681     * Returns the dial scale for a specific dataset.
682     * 
683     * @param datasetIndex  the dataset index.
684     * 
685     * @return The dial scale.
686     */
687    public DialScale getScaleForDataset(int datasetIndex) {
688        DialScale result = (DialScale) this.scales.get(0);    
689        Integer scaleIndex = (Integer) this.datasetToScaleMap.get(datasetIndex);
690        if (scaleIndex != null) {
691            result = getScale(scaleIndex.intValue());
692        }
693        return result;    
694    }
695    
696    /**
697     * A utility method that computes a rectangle using relative radius values.
698     * 
699     * @param rect  the reference rectangle (<code>null</code> not permitted).
700     * @param radiusW  the width radius (must be > 0.0)
701     * @param radiusH  the height radius.
702     * 
703     * @return A new rectangle.
704     */
705    public static Rectangle2D rectangleByRadius(Rectangle2D rect, 
706            double radiusW, double radiusH) {
707        if (rect == null) {
708            throw new IllegalArgumentException("Null 'rect' argument.");
709        }
710        double x = rect.getCenterX();
711        double y = rect.getCenterY();
712        double w = rect.getWidth() * radiusW;
713        double h = rect.getHeight() * radiusH;
714        return new Rectangle2D.Double(x - w / 2.0, y - h / 2.0, w, h);
715    }
716    
717    /**
718     * Receives notification when a layer has changed, and responds by 
719     * forwarding a {@link PlotChangeEvent} to all registered listeners.
720     * 
721     * @param event  the event.
722     */
723    public void dialLayerChanged(DialLayerChangeEvent event) {
724        this.notifyListeners(new PlotChangeEvent(this));
725    }
726
727    /**
728     * Tests this <code>DialPlot</code> instance for equality with an 
729     * arbitrary object.  The plot's dataset(s) is (are) not included in 
730     * the test.
731     *
732     * @param obj  the object (<code>null</code> permitted).
733     *
734     * @return A boolean.
735     */
736    public boolean equals(Object obj) {
737        if (obj == this) {
738            return true;
739        }
740        if (!(obj instanceof DialPlot)) {
741            return false;
742        }
743        DialPlot that = (DialPlot) obj;
744        if (!ObjectUtilities.equal(this.background, that.background)) {
745            return false;
746        }
747        if (!ObjectUtilities.equal(this.cap, that.cap)) {
748            return false;
749        }
750        if (!this.dialFrame.equals(that.dialFrame)) {
751            return false;
752        }
753        if (this.viewX != that.viewX) {
754            return false;
755        }
756        if (this.viewY != that.viewY) {
757            return false;
758        }
759        if (this.viewW != that.viewW) {
760            return false;
761        }
762        if (this.viewH != that.viewH) {
763            return false;
764        }
765        if (!this.layers.equals(that.layers)) {
766            return false;
767        }
768        if (!this.pointers.equals(that.pointers)) {
769            return false;
770        }
771        return super.equals(obj);
772    }
773
774    /**
775     * Returns a hash code for this instance.
776     * 
777     * @return The hash code.
778     */
779    public int hashCode() {
780        int result = 193;
781        result = 37 * result + ObjectUtilities.hashCode(this.background);
782        result = 37 * result + ObjectUtilities.hashCode(this.cap);
783        result = 37 * result + this.dialFrame.hashCode();
784        long temp = Double.doubleToLongBits(this.viewX);
785        result = 37 * result + (int) (temp ^ (temp >>> 32));
786        temp = Double.doubleToLongBits(this.viewY);
787        result = 37 * result + (int) (temp ^ (temp >>> 32));
788        temp = Double.doubleToLongBits(this.viewW);
789        result = 37 * result + (int) (temp ^ (temp >>> 32));
790        temp = Double.doubleToLongBits(this.viewH);
791        result = 37 * result + (int) (temp ^ (temp >>> 32));
792        return result;
793    }
794    
795    /**
796     * Returns the plot type.
797     * 
798     * @return <code>"DialPlot"</code>
799     */
800    public String getPlotType() {
801        return "DialPlot";
802    }
803    
804    /**
805     * Provides serialization support.
806     *
807     * @param stream  the output stream.
808     *
809     * @throws IOException  if there is an I/O error.
810     */
811    private void writeObject(ObjectOutputStream stream) throws IOException {
812        stream.defaultWriteObject();
813    }
814
815    /**
816     * Provides serialization support.
817     *
818     * @param stream  the input stream.
819     *
820     * @throws IOException  if there is an I/O error.
821     * @throws ClassNotFoundException  if there is a classpath problem.
822     */
823    private void readObject(ObjectInputStream stream) 
824            throws IOException, ClassNotFoundException {
825        stream.defaultReadObject();
826    }
827
828    
829}