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