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