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