Source for org.jfree.io.SerialUtilities

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it
  10:  * under the terms of the GNU Lesser General Public License as published by
  11:  * the Free Software Foundation; either version 2.1 of the License, or
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
  22:  * USA.
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  25:  * in the United States and other countries.]
  26:  *
  27:  * --------------------
  28:  * SerialUtilities.java
  29:  * --------------------
  30:  * (C) Copyright 2000-2005, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Arik Levin;
  34:  *
  35:  * $Id: SerialUtilities.java,v 1.14 2008/06/02 06:58:28 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 25-Mar-2003 : Version 1 (DG);
  40:  * 18-Sep-2003 : Added capability to serialize GradientPaint (DG);
  41:  * 26-Apr-2004 : Added read/writePoint2D() methods (DG);
  42:  * 22-Feb-2005 : Added support for Arc2D - see patch 1147035 by Arik Levin (DG);
  43:  * 29-Jul-2005 : Added support for AttributedString (DG);
  44:  *
  45:  */
  46: 
  47: package org.jfree.io;
  48: 
  49: import java.awt.BasicStroke;
  50: import java.awt.Color;
  51: import java.awt.GradientPaint;
  52: import java.awt.Paint;
  53: import java.awt.Shape;
  54: import java.awt.Stroke;
  55: import java.awt.geom.Arc2D;
  56: import java.awt.geom.Ellipse2D;
  57: import java.awt.geom.GeneralPath;
  58: import java.awt.geom.Line2D;
  59: import java.awt.geom.PathIterator;
  60: import java.awt.geom.Point2D;
  61: import java.awt.geom.Rectangle2D;
  62: import java.io.IOException;
  63: import java.io.ObjectInputStream;
  64: import java.io.ObjectOutputStream;
  65: import java.io.Serializable;
  66: import java.text.AttributedCharacterIterator;
  67: import java.text.AttributedString;
  68: import java.text.CharacterIterator;
  69: import java.util.HashMap;
  70: import java.util.Map;
  71: 
  72: /**
  73:  * A class containing useful utility methods relating to serialization.
  74:  *
  75:  * @author David Gilbert
  76:  */
  77: public class SerialUtilities {
  78: 
  79:     /**
  80:      * Private constructor prevents object creation.
  81:      */
  82:     private SerialUtilities() {
  83:     }
  84: 
  85:     /**
  86:      * Returns <code>true</code> if a class implements <code>Serializable</code>
  87:      * and <code>false</code> otherwise.
  88:      *
  89:      * @param c  the class.
  90:      *
  91:      * @return A boolean.
  92:      */
  93:     public static boolean isSerializable(final Class c) {
  94:         /**
  95:         final Class[] interfaces = c.getInterfaces();
  96:         for (int i = 0; i < interfaces.length; i++) {
  97:             if (interfaces[i].equals(Serializable.class)) {
  98:                 return true;
  99:             }
 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: }