    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