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