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