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
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(
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