    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