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