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.14 2008/06/02 06:58:28 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(args[0], args[1], args[2],
336                                        args[3], args[4], args[5]);
337                                break;
338                            case PathIterator.SEG_QUADTO :
339                                gp.quadTo(args[0], args[1], args[2], args[3]);
340                                break;
341                            case PathIterator.SEG_CLOSE :
342                                gp.closePath();
343                                break;
344                            default :
345                                throw new RuntimeException(
346                                        "JFreeChart - No path exists");
347                        }
348                        gp.setWindingRule(stream.readInt());
349                        hasNext = stream.readBoolean();
350                    }
351                    result = gp;
352                }
353                else {
354                    result = (Shape) stream.readObject();
355                }
356            }
357            return result;
358    
359        }
360    
361        /**
362         * Serialises a <code>Shape</code> object.
363         *
364         * @param shape  the shape object (<code>null</code> permitted).
365         * @param stream  the output stream (<code>null</code> not permitted).
366         *
367         * @throws IOException if there is an I/O error.
368         */
369        public static void writeShape(final Shape shape,
370                                      final ObjectOutputStream stream)
371            throws IOException {
372    
373            if (stream == null) {
374                throw new IllegalArgumentException("Null 'stream' argument.");
375            }
376            if (shape != null) {
377                stream.writeBoolean(false);
378                if (shape instanceof Line2D) {
379                    final Line2D line = (Line2D) shape;
380                    stream.writeObject(Line2D.class);
381                    stream.writeDouble(line.getX1());
382                    stream.writeDouble(line.getY1());
383                    stream.writeDouble(line.getX2());
384                    stream.writeDouble(line.getY2());
385                }
386                else if (shape instanceof Rectangle2D) {
387                    final Rectangle2D rectangle = (Rectangle2D) shape;
388                    stream.writeObject(Rectangle2D.class);
389                    stream.writeDouble(rectangle.getX());
390                    stream.writeDouble(rectangle.getY());
391                    stream.writeDouble(rectangle.getWidth());
392                    stream.writeDouble(rectangle.getHeight());
393                }
394                else if (shape instanceof Ellipse2D) {
395                    final Ellipse2D ellipse = (Ellipse2D) shape;
396                    stream.writeObject(Ellipse2D.class);
397                    stream.writeDouble(ellipse.getX());
398                    stream.writeDouble(ellipse.getY());
399                    stream.writeDouble(ellipse.getWidth());
400                    stream.writeDouble(ellipse.getHeight());
401                }
402                else if (shape instanceof Arc2D) {
403                    final Arc2D arc = (Arc2D) shape;
404                    stream.writeObject(Arc2D.class);
405                    stream.writeDouble(arc.getX());
406                    stream.writeDouble(arc.getY());
407                    stream.writeDouble(arc.getWidth());
408                    stream.writeDouble(arc.getHeight());
409                    stream.writeDouble(arc.getAngleStart());
410                    stream.writeDouble(arc.getAngleExtent());
411                    stream.writeInt(arc.getArcType());
412                }
413                else if (shape instanceof GeneralPath) {
414                    stream.writeObject(GeneralPath.class);
415                    final PathIterator pi = shape.getPathIterator(null);
416                    final float[] args = new float[6];
417                    stream.writeBoolean(pi.isDone());
418                    while (!pi.isDone()) {
419                        final int type = pi.currentSegment(args);
420                        stream.writeInt(type);
421                        // TODO: could write this to only stream the values
422                        // required for the segment type
423                        for (int i = 0; i < 6; i++) {
424                            stream.writeFloat(args[i]);
425                        }
426                        stream.writeInt(pi.getWindingRule());
427                        pi.next();
428                        stream.writeBoolean(pi.isDone());
429                    }
430                }
431                else {
432                    stream.writeObject(shape.getClass());
433                    stream.writeObject(shape);
434                }
435            }
436            else {
437                stream.writeBoolean(true);
438            }
439        }
440    
441        /**
442         * Reads a <code>Point2D</code> object that has been serialised by the
443         * {@link #writePoint2D(Point2D, ObjectOutputStream)} method.
444         *
445         * @param stream  the input stream (<code>null</code> not permitted).
446         *
447         * @return The point object (possibly <code>null</code>).
448         *
449         * @throws IOException  if there is an I/O problem.
450         */
451        public static Point2D readPoint2D(final ObjectInputStream stream)
452            throws IOException {
453    
454            if (stream == null) {
455                throw new IllegalArgumentException("Null 'stream' argument.");
456            }
457            Point2D result = null;
458            final boolean isNull = stream.readBoolean();
459            if (!isNull) {
460                final double x = stream.readDouble();
461                final double y = stream.readDouble();
462                result = new Point2D.Double(x, y);
463            }
464            return result;
465    
466        }
467    
468        /**
469         * Serialises a <code>Point2D</code> object.
470         *
471         * @param p  the point object (<code>null</code> permitted).
472         * @param stream  the output stream (<code>null</code> not permitted).
473         *
474         * @throws IOException if there is an I/O error.
475         */
476        public static void writePoint2D(final Point2D p,
477                                        final ObjectOutputStream stream)
478            throws IOException {
479    
480            if (stream == null) {
481                throw new IllegalArgumentException("Null 'stream' argument.");
482            }
483            if (p != null) {
484                stream.writeBoolean(false);
485                stream.writeDouble(p.getX());
486                stream.writeDouble(p.getY());
487            }
488            else {
489                stream.writeBoolean(true);
490            }
491        }
492    
493        /**
494         * Reads a <code>AttributedString</code> object that has been serialised by
495         * the {@link SerialUtilities#writeAttributedString(AttributedString,
496         * ObjectOutputStream)} method.
497         *
498         * @param stream  the input stream (<code>null</code> not permitted).
499         *
500         * @return The attributed string object (possibly <code>null</code>).
501         *
502         * @throws IOException  if there is an I/O problem.
503         * @throws ClassNotFoundException  if there is a problem loading a class.
504         */
505        public static AttributedString readAttributedString(
506                ObjectInputStream stream)
507                throws IOException, ClassNotFoundException {
508    
509            if (stream == null) {
510                throw new IllegalArgumentException("Null 'stream' argument.");
511            }
512            AttributedString result = null;
513            final boolean isNull = stream.readBoolean();
514            if (!isNull) {
515                // read string and attributes then create result
516                String plainStr = (String) stream.readObject();
517                result = new AttributedString(plainStr);
518                char c = stream.readChar();
519                int start = 0;
520                while (c != CharacterIterator.DONE) {
521                    int limit = stream.readInt();
522                    Map atts = (Map) stream.readObject();
523                    result.addAttributes(atts, start, limit);
524                    start = limit;
525                    c = stream.readChar();
526                }
527            }
528            return result;
529        }
530    
531        /**
532         * Serialises an <code>AttributedString</code> object.
533         *
534         * @param as  the attributed string object (<code>null</code> permitted).
535         * @param stream  the output stream (<code>null</code> not permitted).
536         *
537         * @throws IOException if there is an I/O error.
538         */
539        public static void writeAttributedString(AttributedString as,
540                ObjectOutputStream stream) throws IOException {
541    
542            if (stream == null) {
543                throw new IllegalArgumentException("Null 'stream' argument.");
544            }
545            if (as != null) {
546                stream.writeBoolean(false);
547                AttributedCharacterIterator aci = as.getIterator();
548                // build a plain string from aci
549                // then write the string
550                StringBuffer plainStr = new StringBuffer();
551                char current = aci.first();
552                while (current != CharacterIterator.DONE) {
553                    plainStr = plainStr.append(current);
554                    current = aci.next();
555                }
556                stream.writeObject(plainStr.toString());
557    
558                // then write the attributes and limits for each run
559                current = aci.first();
560                int begin = aci.getBeginIndex();
561                while (current != CharacterIterator.DONE) {
562                    // write the current character - when the reader sees that this
563                    // is not CharacterIterator.DONE, it will know to read the
564                    // run limits and attributes
565                    stream.writeChar(current);
566    
567                    // now write the limit, adjusted as if beginIndex is zero
568                    int limit = aci.getRunLimit();
569                    stream.writeInt(limit - begin);
570    
571                    // now write the attribute set
572                    Map atts = new HashMap(aci.getAttributes());
573                    stream.writeObject(atts);
574                    current = aci.setIndex(limit);
575                }
576                // write a character that signals to the reader that all runs
577                // are done...
578                stream.writeChar(CharacterIterator.DONE);
579            }
580            else {
581                // write a flag that indicates a null
582                stream.writeBoolean(true);
583            }
584    
585        }
586    
587    }
588