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
047package org.jfree.io;
048
049import java.awt.BasicStroke;
050import java.awt.Color;
051import java.awt.GradientPaint;
052import java.awt.Paint;
053import java.awt.Shape;
054import java.awt.Stroke;
055import java.awt.geom.Arc2D;
056import java.awt.geom.Ellipse2D;
057import java.awt.geom.GeneralPath;
058import java.awt.geom.Line2D;
059import java.awt.geom.PathIterator;
060import java.awt.geom.Point2D;
061import java.awt.geom.Rectangle2D;
062import java.io.IOException;
063import java.io.ObjectInputStream;
064import java.io.ObjectOutputStream;
065import java.io.Serializable;
066import java.text.AttributedCharacterIterator;
067import java.text.AttributedString;
068import java.text.CharacterIterator;
069import java.util.HashMap;
070import java.util.Map;
071
072/**
073 * A class containing useful utility methods relating to serialization.
074 *
075 * @author David Gilbert
076 */
077public 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