001    /* ========================================================================
002     * JCommon : a free general purpose class library for the Java(tm) platform
003     * ========================================================================
004     *
005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006     * 
007     * Project Info:  http://www.jfree.org/jcommon/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     * SerialUtilities.java
029     * --------------------
030     * (C) Copyright 2000-2005, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Arik Levin;
034     *
035     * $Id: SerialUtilities.java,v 1.13 2005/11/03 09:55:27 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 25-Mar-2003 : Version 1 (DG);
040     * 18-Sep-2003 : Added capability to serialize GradientPaint (DG);
041     * 26-Apr-2004 : Added read/writePoint2D() methods (DG);
042     * 22-Feb-2005 : Added support for Arc2D - see patch 1147035 by Arik Levin (DG);
043     * 29-Jul-2005 : Added support for AttributedString (DG);
044     * 
045     */
046    
047    package org.jfree.io;
048    
049    import java.awt.BasicStroke;
050    import java.awt.Color;
051    import java.awt.GradientPaint;
052    import java.awt.Paint;
053    import java.awt.Shape;
054    import java.awt.Stroke;
055    import java.awt.geom.Arc2D;
056    import java.awt.geom.Ellipse2D;
057    import java.awt.geom.GeneralPath;
058    import java.awt.geom.Line2D;
059    import java.awt.geom.PathIterator;
060    import java.awt.geom.Point2D;
061    import java.awt.geom.Rectangle2D;
062    import java.io.IOException;
063    import java.io.ObjectInputStream;
064    import java.io.ObjectOutputStream;
065    import java.io.Serializable;
066    import java.text.AttributedCharacterIterator;
067    import java.text.AttributedString;
068    import java.text.CharacterIterator;
069    import java.util.HashMap;
070    import java.util.Map;
071    
072    /**
073     * A class containing useful utility methods relating to serialization.
074     *
075     * @author David Gilbert
076     */
077    public class SerialUtilities {
078    
079        /**
080         * Private constructor prevents object creation.
081         */
082        private SerialUtilities() {
083        }
084    
085        /**
086         * Returns <code>true</code> if a class implements <code>Serializable</code>
087         * and <code>false</code> otherwise.
088         * 
089         * @param c  the class.
090         * 
091         * @return A boolean.
092         */
093        public static boolean isSerializable(final Class c) {
094            /**
095            final Class[] interfaces = c.getInterfaces();
096            for (int i = 0; i < interfaces.length; i++) {
097                if (interfaces[i].equals(Serializable.class)) {
098                    return true;                
099                }
100            }
101            Class cc = c.getSuperclass();
102            if (cc != null) {
103                return isSerializable(cc);   
104            }
105             */
106            return (Serializable.class.isAssignableFrom(c));
107        }
108        
109        /**
110         * Reads a <code>Paint</code> object that has been serialised by the
111         * {@link SerialUtilities#writePaint(Paint, ObjectOutputStream)} method.
112         *
113         * @param stream  the input stream (<code>null</code> not permitted).
114         *
115         * @return The paint object (possibly <code>null</code>).
116         *
117         * @throws IOException  if there is an I/O problem.
118         * @throws ClassNotFoundException  if there is a problem loading a class.
119         */
120        public static Paint readPaint(final ObjectInputStream stream)
121            throws IOException, ClassNotFoundException {
122    
123            if (stream == null) {
124                throw new IllegalArgumentException("Null 'stream' argument.");   
125            }
126            Paint result = null;
127            final boolean isNull = stream.readBoolean();
128            if (!isNull) {
129                final Class c = (Class) stream.readObject();
130                if (isSerializable(c)) {
131                    result = (Paint) stream.readObject();
132                }
133                else if (c.equals(GradientPaint.class)) {
134                    final float x1 = stream.readFloat();
135                    final float y1 = stream.readFloat();
136                    final Color c1 = (Color) stream.readObject();
137                    final float x2 = stream.readFloat();
138                    final float y2 = stream.readFloat();
139                    final Color c2 = (Color) stream.readObject();
140                    final boolean isCyclic = stream.readBoolean();
141                    result = new GradientPaint(x1, y1, c1, x2, y2, c2, isCyclic);
142                }
143            }
144            return result;
145    
146        }
147    
148        /**
149         * Serialises a <code>Paint</code> object.
150         *
151         * @param paint  the paint object (<code>null</code> permitted).
152         * @param stream  the output stream (<code>null</code> not permitted).
153         *
154         * @throws IOException if there is an I/O error.
155         */
156        public static void writePaint(final Paint paint,
157                                      final ObjectOutputStream stream) 
158            throws IOException {
159    
160            if (stream == null) {
161                throw new IllegalArgumentException("Null 'stream' argument.");   
162            }
163            if (paint != null) {
164                stream.writeBoolean(false);
165                stream.writeObject(paint.getClass());
166                if (paint instanceof Serializable) {
167                    stream.writeObject(paint);
168                }
169                else if (paint instanceof GradientPaint) {
170                    final GradientPaint gp = (GradientPaint) paint;
171                    stream.writeFloat((float) gp.getPoint1().getX());
172                    stream.writeFloat((float) gp.getPoint1().getY());
173                    stream.writeObject(gp.getColor1());
174                    stream.writeFloat((float) gp.getPoint2().getX());
175                    stream.writeFloat((float) gp.getPoint2().getY());
176                    stream.writeObject(gp.getColor2());
177                    stream.writeBoolean(gp.isCyclic());
178                }
179            }
180            else {
181                stream.writeBoolean(true);
182            }
183    
184        }
185    
186        /**
187         * Reads a <code>Stroke</code> object that has been serialised by the
188         * {@link SerialUtilities#writeStroke(Stroke, ObjectOutputStream)} method.
189         *
190         * @param stream  the input stream (<code>null</code> not permitted).
191         *
192         * @return The stroke object (possibly <code>null</code>).
193         *
194         * @throws IOException  if there is an I/O problem.
195         * @throws ClassNotFoundException  if there is a problem loading a class.
196         */
197        public static Stroke readStroke(final ObjectInputStream stream)
198            throws IOException, ClassNotFoundException {
199    
200            if (stream == null) {
201                throw new IllegalArgumentException("Null 'stream' argument.");   
202            }
203            Stroke result = null;
204            final boolean isNull = stream.readBoolean();
205            if (!isNull) {
206                final Class c = (Class) stream.readObject();
207                if (c.equals(BasicStroke.class)) {
208                    final float width = stream.readFloat();
209                    final int cap = stream.readInt();
210                    final int join = stream.readInt();
211                    final float miterLimit = stream.readFloat();
212                    final float[] dash = (float[]) stream.readObject();
213                    final float dashPhase = stream.readFloat();
214                    result = new BasicStroke(
215                        width, cap, join, miterLimit, dash, dashPhase
216                    );
217                }
218                else {
219                    result = (Stroke) stream.readObject();
220                }
221            }
222            return result;
223    
224        }
225    
226        /**
227         * Serialises a <code>Stroke</code> object.  This code handles the
228         * <code>BasicStroke</code> class which is the only <code>Stroke</code> 
229         * implementation provided by the JDK (and isn't directly 
230         * <code>Serializable</code>).
231         *
232         * @param stroke  the stroke object (<code>null</code> permitted).
233         * @param stream  the output stream (<code>null</code> not permitted).
234         *
235         * @throws IOException if there is an I/O error.
236         */
237        public static void writeStroke(final Stroke stroke,
238                                       final ObjectOutputStream stream) 
239            throws IOException {
240    
241            if (stream == null) {
242                throw new IllegalArgumentException("Null 'stream' argument.");   
243            }
244            if (stroke != null) {
245                stream.writeBoolean(false);
246                if (stroke instanceof BasicStroke) {
247                    final BasicStroke s = (BasicStroke) stroke;
248                    stream.writeObject(BasicStroke.class);
249                    stream.writeFloat(s.getLineWidth());
250                    stream.writeInt(s.getEndCap());
251                    stream.writeInt(s.getLineJoin());
252                    stream.writeFloat(s.getMiterLimit());
253                    stream.writeObject(s.getDashArray());
254                    stream.writeFloat(s.getDashPhase());
255                }
256                else {
257                    stream.writeObject(stroke.getClass());
258                    stream.writeObject(stroke);
259                }
260            }
261            else {
262                stream.writeBoolean(true);
263            }
264        }
265    
266        /**
267         * Reads a <code>Shape</code> object that has been serialised by the 
268         * {@link #writeShape(Shape, ObjectOutputStream)} method.
269         *
270         * @param stream  the input stream (<code>null</code> not permitted).
271         *
272         * @return The shape object (possibly <code>null</code>).
273         *
274         * @throws IOException  if there is an I/O problem.
275         * @throws ClassNotFoundException  if there is a problem loading a class.
276         */
277        public static Shape readShape(final ObjectInputStream stream)
278            throws IOException, ClassNotFoundException {
279    
280            if (stream == null) {
281                throw new IllegalArgumentException("Null 'stream' argument.");   
282            }
283            Shape result = null;
284            final boolean isNull = stream.readBoolean();
285            if (!isNull) {
286                final Class c = (Class) stream.readObject();
287                if (c.equals(Line2D.class)) {
288                    final double x1 = stream.readDouble();
289                    final double y1 = stream.readDouble();
290                    final double x2 = stream.readDouble();
291                    final double y2 = stream.readDouble();
292                    result = new Line2D.Double(x1, y1, x2, y2);
293                }
294                else if (c.equals(Rectangle2D.class)) {
295                    final double x = stream.readDouble();
296                    final double y = stream.readDouble();
297                    final double w = stream.readDouble();
298                    final double h = stream.readDouble();
299                    result = new Rectangle2D.Double(x, y, w, h);
300                }
301                else if (c.equals(Ellipse2D.class)) {
302                    final double x = stream.readDouble();
303                    final double y = stream.readDouble();
304                    final double w = stream.readDouble();
305                    final double h = stream.readDouble();
306                    result = new Ellipse2D.Double(x, y, w, h);
307                }
308                else if (c.equals(Arc2D.class)) {
309                    final double x = stream.readDouble();
310                    final double y = stream.readDouble();
311                    final double w = stream.readDouble();
312                    final double h = stream.readDouble();
313                    final double as = stream.readDouble(); // Angle Start
314                    final double ae = stream.readDouble(); // Angle Extent
315                    final int at = stream.readInt();       // Arc type
316                    result = new Arc2D.Double(x, y, w, h, as, ae, at);
317                }            
318                else if (c.equals(GeneralPath.class)) {
319                    final GeneralPath gp = new GeneralPath();
320                    final float[] args = new float[6];
321                    boolean hasNext = stream.readBoolean();
322                    while (!hasNext) {
323                        final int type = stream.readInt();
324                        for (int i = 0; i < 6; i++) {
325                            args[i] = stream.readFloat();
326                        }
327                        switch (type) { 
328                            case PathIterator.SEG_MOVETO :  
329                                gp.moveTo(args[0], args[1]);
330                                break;
331                            case PathIterator.SEG_LINETO :                           
332                                gp.lineTo(args[0], args[1]);
333                                break; 
334                            case PathIterator.SEG_CUBICTO :
335                                gp.curveTo(
336                                    args[0], args[1], args[2], 
337                                    args[3], args[4], args[5]
338                                );
339                                break;
340                            case PathIterator.SEG_QUADTO :
341                                gp.quadTo(args[0], args[1], args[2], args[3]);
342                                break;                  
343                            case PathIterator.SEG_CLOSE :
344                                //result = gp;
345                                break;
346                            default : 
347                                throw new RuntimeException(
348                                    "JFreeChart - No path exists"
349                                ); 
350                        } 
351                        gp.setWindingRule(stream.readInt());    
352                        hasNext = stream.readBoolean();
353                    }
354                    result = gp;
355                }
356                else {
357                    result = (Shape) stream.readObject();
358                }
359            }
360            return result;
361    
362        }
363    
364        /**
365         * Serialises a <code>Shape</code> object.
366         *
367         * @param shape  the shape object (<code>null</code> permitted).
368         * @param stream  the output stream (<code>null</code> not permitted).
369         *
370         * @throws IOException if there is an I/O error.
371         */
372        public static void writeShape(final Shape shape,
373                                      final ObjectOutputStream stream) 
374            throws IOException {
375    
376            if (stream == null) {
377                throw new IllegalArgumentException("Null 'stream' argument.");   
378            }
379            if (shape != null) {
380                stream.writeBoolean(false);
381                if (shape instanceof Line2D) {
382                    final Line2D line = (Line2D) shape;
383                    stream.writeObject(Line2D.class);
384                    stream.writeDouble(line.getX1());
385                    stream.writeDouble(line.getY1());
386                    stream.writeDouble(line.getX2());
387                    stream.writeDouble(line.getY2());
388                }
389                else if (shape instanceof Rectangle2D) {
390                    final Rectangle2D rectangle = (Rectangle2D) shape;
391                    stream.writeObject(Rectangle2D.class);
392                    stream.writeDouble(rectangle.getX());
393                    stream.writeDouble(rectangle.getY());
394                    stream.writeDouble(rectangle.getWidth());
395                    stream.writeDouble(rectangle.getHeight());
396                }
397                else if (shape instanceof Ellipse2D) {
398                    final Ellipse2D ellipse = (Ellipse2D) shape;
399                    stream.writeObject(Ellipse2D.class);
400                    stream.writeDouble(ellipse.getX());
401                    stream.writeDouble(ellipse.getY());
402                    stream.writeDouble(ellipse.getWidth());
403                    stream.writeDouble(ellipse.getHeight());
404                }
405                else if (shape instanceof Arc2D) {
406                    final Arc2D arc = (Arc2D) shape;
407                    stream.writeObject(Arc2D.class);
408                    stream.writeDouble(arc.getX());
409                    stream.writeDouble(arc.getY());
410                    stream.writeDouble(arc.getWidth());
411                    stream.writeDouble(arc.getHeight());
412                    stream.writeDouble(arc.getAngleStart());
413                    stream.writeDouble(arc.getAngleExtent());
414                    stream.writeInt(arc.getArcType());
415                }
416                else if (shape instanceof GeneralPath) {
417                    stream.writeObject(GeneralPath.class);
418                    final PathIterator pi = shape.getPathIterator(null);
419                    final float[] args = new float[6];
420                    stream.writeBoolean(pi.isDone());
421                    while (!pi.isDone()) {
422                        final int type = pi.currentSegment(args);
423                        stream.writeInt(type);
424                        // TODO: could write this to only stream the values
425                        // required for the segment type
426                        for (int i = 0; i < 6; i++) {
427                            stream.writeFloat(args[i]);
428                        }
429                        stream.writeInt(pi.getWindingRule());
430                        pi.next();
431                        stream.writeBoolean(pi.isDone());
432                    }
433                }
434                else {
435                    stream.writeObject(shape.getClass());
436                    stream.writeObject(shape);
437                }
438            }
439            else {
440                stream.writeBoolean(true);
441            }
442        }
443    
444        /**
445         * Reads a <code>Point2D</code> object that has been serialised by the 
446         * {@link #writePoint2D(Point2D, ObjectOutputStream)} method.
447         *
448         * @param stream  the input stream (<code>null</code> not permitted).
449         *
450         * @return The point object (possibly <code>null</code>).
451         *
452         * @throws IOException  if there is an I/O problem.
453         */
454        public static Point2D readPoint2D(final ObjectInputStream stream)
455            throws IOException {
456    
457            if (stream == null) {
458                throw new IllegalArgumentException("Null 'stream' argument.");   
459            }
460            Point2D result = null;
461            final boolean isNull = stream.readBoolean();
462            if (!isNull) {
463                final double x = stream.readDouble();
464                final double y = stream.readDouble();
465                result = new Point2D.Double(x, y);
466            }
467            return result;
468    
469        }
470    
471        /**
472         * Serialises a <code>Point2D</code> object.
473         *
474         * @param p  the point object (<code>null</code> permitted).
475         * @param stream  the output stream (<code>null</code> not permitted).
476         *
477         * @throws IOException if there is an I/O error.
478         */
479        public static void writePoint2D(final Point2D p,
480                                        final ObjectOutputStream stream) 
481            throws IOException {
482    
483            if (stream == null) {
484                throw new IllegalArgumentException("Null 'stream' argument.");   
485            }
486            if (p != null) {
487                stream.writeBoolean(false);
488                stream.writeDouble(p.getX());
489                stream.writeDouble(p.getY());
490            }
491            else {
492                stream.writeBoolean(true);
493            }
494        }
495        
496        /**
497         * Reads a <code>AttributedString</code> object that has been serialised by 
498         * the {@link SerialUtilities#writeAttributedString(AttributedString, 
499         * ObjectOutputStream)} method.
500         *
501         * @param stream  the input stream (<code>null</code> not permitted).
502         *
503         * @return The attributed string object (possibly <code>null</code>).
504         *
505         * @throws IOException  if there is an I/O problem.
506         * @throws ClassNotFoundException  if there is a problem loading a class.
507         */
508        public static AttributedString readAttributedString(
509                ObjectInputStream stream) 
510                throws IOException, ClassNotFoundException {
511            
512            if (stream == null) {
513                throw new IllegalArgumentException("Null 'stream' argument.");   
514            }
515            AttributedString result = null;
516            final boolean isNull = stream.readBoolean();
517            if (!isNull) {
518                // read string and attributes then create result
519                String plainStr = (String) stream.readObject();
520                result = new AttributedString(plainStr);
521                char c = stream.readChar();
522                int start = 0;
523                while (c != CharacterIterator.DONE) {
524                    int limit = stream.readInt();
525                    Map atts = (Map) stream.readObject();
526                    result.addAttributes(atts, start, limit);
527                    start = limit;
528                    c = stream.readChar();
529                }
530            }
531            return result;
532        }
533        
534        /**
535         * Serialises an <code>AttributedString</code> object.
536         *
537         * @param as  the attributed string object (<code>null</code> permitted).
538         * @param stream  the output stream (<code>null</code> not permitted).
539         *
540         * @throws IOException if there is an I/O error.
541         */
542        public static void writeAttributedString(AttributedString as, 
543                ObjectOutputStream stream) throws IOException {
544            
545            if (stream == null) {
546                throw new IllegalArgumentException("Null 'stream' argument.");   
547            }
548            if (as != null) {
549                stream.writeBoolean(false);
550                AttributedCharacterIterator aci = as.getIterator();
551                // build a plain string from aci
552                // then write the string
553                StringBuffer plainStr = new StringBuffer();
554                char current = aci.first();
555                while (current != CharacterIterator.DONE) {
556                    plainStr = plainStr.append(current);
557                    current = aci.next();
558                }
559                stream.writeObject(plainStr.toString());
560                
561                // then write the attributes and limits for each run
562                current = aci.first();
563                int begin = aci.getBeginIndex();
564                while (current != CharacterIterator.DONE) {
565                    // write the current character - when the reader sees that this
566                    // is not CharacterIterator.DONE, it will know to read the
567                    // run limits and attributes
568                    stream.writeChar(current);
569                    
570                    // now write the limit, adjusted as if beginIndex is zero
571                    int limit = aci.getRunLimit();
572                    stream.writeInt(limit - begin);
573                    
574                    // now write the attribute set
575                    Map atts = new HashMap(aci.getAttributes());
576                    stream.writeObject(atts);
577                    current = aci.setIndex(limit);
578                }
579                // write a character that signals to the reader that all runs
580                // are done...
581                stream.writeChar(CharacterIterator.DONE);  
582            }
583            else {
584                // write a flag that indicates a null
585                stream.writeBoolean(true);
586            }
587    
588        }
589    
590    }
591