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.13 2005/11/03 09:55:27 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(
 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: }