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    }