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