001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, by Object Refinery Limited 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-2020, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 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 * @since 1.0.17 251 */ 252 public static Composite readComposite(ObjectInputStream stream) 253 throws IOException, ClassNotFoundException { 254 255 if (stream == null) { 256 throw new IllegalArgumentException("Null 'stream' argument."); 257 } 258 Composite result = null; 259 boolean isNull = stream.readBoolean(); 260 if (!isNull) { 261 Class c = (Class) stream.readObject(); 262 if (isSerializable(c)) { 263 result = (Composite) stream.readObject(); 264 } 265 else if (c.equals(AlphaComposite.class)) { 266 int rule = stream.readInt(); 267 float alpha = stream.readFloat(); 268 result = AlphaComposite.getInstance(rule, alpha); 269 } 270 } 271 return result; 272 273 } 274 275 /** 276 * Serialises a {@code Composite} object. 277 * 278 * @param composite the composite object ({@code null} permitted). 279 * @param stream the output stream ({@code null} not permitted). 280 * 281 * @throws IOException if there is an I/O error. 282 * 283 * @since 1.0.17 284 */ 285 public static void writeComposite(Composite composite, 286 ObjectOutputStream stream) throws IOException { 287 288 if (stream == null) { 289 throw new IllegalArgumentException("Null 'stream' argument."); 290 } 291 if (composite != null) { 292 stream.writeBoolean(false); 293 stream.writeObject(composite.getClass()); 294 if (composite instanceof Serializable) { 295 stream.writeObject(composite); 296 } 297 else if (composite instanceof AlphaComposite) { 298 AlphaComposite ac = (AlphaComposite) composite; 299 stream.writeInt(ac.getRule()); 300 stream.writeFloat(ac.getAlpha()); 301 } 302 } else { 303 stream.writeBoolean(true); 304 } 305 } 306 307 /** 308 * Reads a {@code Shape} object that has been serialised by the 309 * {@link #writeShape(Shape, ObjectOutputStream)} method. 310 * 311 * @param stream the input stream ({@code null} not permitted). 312 * 313 * @return The shape object (possibly {@code null}). 314 * 315 * @throws IOException if there is an I/O problem. 316 * @throws ClassNotFoundException if there is a problem loading a class. 317 */ 318 public static Shape readShape(ObjectInputStream stream) 319 throws IOException, ClassNotFoundException { 320 321 if (stream == null) { 322 throw new IllegalArgumentException("Null 'stream' argument."); 323 } 324 Shape result = null; 325 boolean isNull = stream.readBoolean(); 326 if (!isNull) { 327 Class c = (Class) stream.readObject(); 328 if (c.equals(Line2D.class)) { 329 double x1 = stream.readDouble(); 330 double y1 = stream.readDouble(); 331 double x2 = stream.readDouble(); 332 double y2 = stream.readDouble(); 333 result = new Line2D.Double(x1, y1, x2, y2); 334 } 335 else if (c.equals(Rectangle2D.class)) { 336 double x = stream.readDouble(); 337 double y = stream.readDouble(); 338 double w = stream.readDouble(); 339 double h = stream.readDouble(); 340 result = new Rectangle2D.Double(x, y, w, h); 341 } 342 else if (c.equals(Ellipse2D.class)) { 343 double x = stream.readDouble(); 344 double y = stream.readDouble(); 345 double w = stream.readDouble(); 346 double h = stream.readDouble(); 347 result = new Ellipse2D.Double(x, y, w, h); 348 } 349 else if (c.equals(Arc2D.class)) { 350 double x = stream.readDouble(); 351 double y = stream.readDouble(); 352 double w = stream.readDouble(); 353 double h = stream.readDouble(); 354 double as = stream.readDouble(); // Angle Start 355 double ae = stream.readDouble(); // Angle Extent 356 int at = stream.readInt(); // Arc type 357 result = new Arc2D.Double(x, y, w, h, as, ae, at); 358 } 359 else if (c.equals(GeneralPath.class)) { 360 GeneralPath gp = new GeneralPath(); 361 float[] args = new float[6]; 362 boolean hasNext = stream.readBoolean(); 363 while (!hasNext) { 364 int type = stream.readInt(); 365 for (int i = 0; i < 6; i++) { 366 args[i] = stream.readFloat(); 367 } 368 switch (type) { 369 case PathIterator.SEG_MOVETO : 370 gp.moveTo(args[0], args[1]); 371 break; 372 case PathIterator.SEG_LINETO : 373 gp.lineTo(args[0], args[1]); 374 break; 375 case PathIterator.SEG_CUBICTO : 376 gp.curveTo(args[0], args[1], args[2], 377 args[3], args[4], args[5]); 378 break; 379 case PathIterator.SEG_QUADTO : 380 gp.quadTo(args[0], args[1], args[2], args[3]); 381 break; 382 case PathIterator.SEG_CLOSE : 383 gp.closePath(); 384 break; 385 default : 386 throw new RuntimeException( 387 "JFreeChart - No path exists"); 388 } 389 gp.setWindingRule(stream.readInt()); 390 hasNext = stream.readBoolean(); 391 } 392 result = gp; 393 } 394 else { 395 result = (Shape) stream.readObject(); 396 } 397 } 398 return result; 399 400 } 401 402 /** 403 * Serialises a {@code Shape} object. 404 * 405 * @param shape the shape object ({@code null} permitted). 406 * @param stream the output stream ({@code null} not permitted). 407 * 408 * @throws IOException if there is an I/O error. 409 */ 410 public static void writeShape(Shape shape, ObjectOutputStream stream) 411 throws IOException { 412 413 if (stream == null) { 414 throw new IllegalArgumentException("Null 'stream' argument."); 415 } 416 if (shape != null) { 417 stream.writeBoolean(false); 418 if (shape instanceof Line2D) { 419 final Line2D line = (Line2D) shape; 420 stream.writeObject(Line2D.class); 421 stream.writeDouble(line.getX1()); 422 stream.writeDouble(line.getY1()); 423 stream.writeDouble(line.getX2()); 424 stream.writeDouble(line.getY2()); 425 } 426 else if (shape instanceof Rectangle2D) { 427 final Rectangle2D rectangle = (Rectangle2D) shape; 428 stream.writeObject(Rectangle2D.class); 429 stream.writeDouble(rectangle.getX()); 430 stream.writeDouble(rectangle.getY()); 431 stream.writeDouble(rectangle.getWidth()); 432 stream.writeDouble(rectangle.getHeight()); 433 } 434 else if (shape instanceof Ellipse2D) { 435 final Ellipse2D ellipse = (Ellipse2D) shape; 436 stream.writeObject(Ellipse2D.class); 437 stream.writeDouble(ellipse.getX()); 438 stream.writeDouble(ellipse.getY()); 439 stream.writeDouble(ellipse.getWidth()); 440 stream.writeDouble(ellipse.getHeight()); 441 } 442 else if (shape instanceof Arc2D) { 443 final Arc2D arc = (Arc2D) shape; 444 stream.writeObject(Arc2D.class); 445 stream.writeDouble(arc.getX()); 446 stream.writeDouble(arc.getY()); 447 stream.writeDouble(arc.getWidth()); 448 stream.writeDouble(arc.getHeight()); 449 stream.writeDouble(arc.getAngleStart()); 450 stream.writeDouble(arc.getAngleExtent()); 451 stream.writeInt(arc.getArcType()); 452 } 453 else if (shape instanceof GeneralPath) { 454 stream.writeObject(GeneralPath.class); 455 final PathIterator pi = shape.getPathIterator(null); 456 final float[] args = new float[6]; 457 stream.writeBoolean(pi.isDone()); 458 while (!pi.isDone()) { 459 final int type = pi.currentSegment(args); 460 stream.writeInt(type); 461 // TODO: could write this to only stream the values 462 // required for the segment type 463 for (int i = 0; i < 6; i++) { 464 stream.writeFloat(args[i]); 465 } 466 stream.writeInt(pi.getWindingRule()); 467 pi.next(); 468 stream.writeBoolean(pi.isDone()); 469 } 470 } 471 else { 472 stream.writeObject(shape.getClass()); 473 stream.writeObject(shape); 474 } 475 } 476 else { 477 stream.writeBoolean(true); 478 } 479 } 480 481 /** 482 * Reads a {@code Point2D} object that has been serialised by the 483 * {@link #writePoint2D(Point2D, ObjectOutputStream)} method. 484 * 485 * @param stream the input stream ({@code null} not permitted). 486 * 487 * @return The point object (possibly {@code null}). 488 * 489 * @throws IOException if there is an I/O problem. 490 */ 491 public static Point2D readPoint2D(ObjectInputStream stream) 492 throws IOException { 493 494 if (stream == null) { 495 throw new IllegalArgumentException("Null 'stream' argument."); 496 } 497 Point2D result = null; 498 boolean isNull = stream.readBoolean(); 499 if (!isNull) { 500 double x = stream.readDouble(); 501 double y = stream.readDouble(); 502 result = new Point2D.Double(x, y); 503 } 504 return result; 505 506 } 507 508 /** 509 * Serialises a {@code Point2D} object. 510 * 511 * @param p the point object ({@code null} permitted). 512 * @param stream the output stream ({@code null} not permitted). 513 * 514 * @throws IOException if there is an I/O error. 515 */ 516 public static void writePoint2D(Point2D p, ObjectOutputStream stream) 517 throws IOException { 518 519 if (stream == null) { 520 throw new IllegalArgumentException("Null 'stream' argument."); 521 } 522 if (p != null) { 523 stream.writeBoolean(false); 524 stream.writeDouble(p.getX()); 525 stream.writeDouble(p.getY()); 526 } 527 else { 528 stream.writeBoolean(true); 529 } 530 } 531 532 /** 533 * Reads a {@code AttributedString} object that has been serialised by 534 * the {@link #writeAttributedString(AttributedString, 535 * ObjectOutputStream)} method. 536 * 537 * @param stream the input stream ({@code null} not permitted). 538 * 539 * @return The attributed string object (possibly {@code null}). 540 * 541 * @throws IOException if there is an I/O problem. 542 * @throws ClassNotFoundException if there is a problem loading a class. 543 */ 544 public static AttributedString readAttributedString( 545 ObjectInputStream stream) 546 throws IOException, ClassNotFoundException { 547 548 if (stream == null) { 549 throw new IllegalArgumentException("Null 'stream' argument."); 550 } 551 AttributedString result = null; 552 final boolean isNull = stream.readBoolean(); 553 if (!isNull) { 554 // read string and attributes then create result 555 String plainStr = (String) stream.readObject(); 556 result = new AttributedString(plainStr); 557 char c = stream.readChar(); 558 int start = 0; 559 while (c != CharacterIterator.DONE) { 560 int limit = stream.readInt(); 561 Map atts = (Map) stream.readObject(); 562 result.addAttributes(atts, start, limit); 563 start = limit; 564 c = stream.readChar(); 565 } 566 } 567 return result; 568 } 569 570 /** 571 * Serialises an {@code AttributedString} object. 572 * 573 * @param as the attributed string object ({@code null} permitted). 574 * @param stream the output stream ({@code null} not permitted). 575 * 576 * @throws IOException if there is an I/O error. 577 */ 578 public static void writeAttributedString(AttributedString as, 579 ObjectOutputStream stream) throws IOException { 580 581 if (stream == null) { 582 throw new IllegalArgumentException("Null 'stream' argument."); 583 } 584 if (as != null) { 585 stream.writeBoolean(false); 586 AttributedCharacterIterator aci = as.getIterator(); 587 // build a plain string from aci 588 // then write the string 589 StringBuffer plainStr = new StringBuffer(); 590 char current = aci.first(); 591 while (current != CharacterIterator.DONE) { 592 plainStr = plainStr.append(current); 593 current = aci.next(); 594 } 595 stream.writeObject(plainStr.toString()); 596 597 // then write the attributes and limits for each run 598 current = aci.first(); 599 int begin = aci.getBeginIndex(); 600 while (current != CharacterIterator.DONE) { 601 // write the current character - when the reader sees that this 602 // is not CharacterIterator.DONE, it will know to read the 603 // run limits and attributes 604 stream.writeChar(current); 605 606 // now write the limit, adjusted as if beginIndex is zero 607 int limit = aci.getRunLimit(); 608 stream.writeInt(limit - begin); 609 610 // now write the attribute set 611 Map atts = new HashMap(aci.getAttributes()); 612 stream.writeObject(atts); 613 current = aci.setIndex(limit); 614 } 615 // write a character that signals to the reader that all runs 616 // are done... 617 stream.writeChar(CharacterIterator.DONE); 618 } 619 else { 620 // write a flag that indicates a null 621 stream.writeBoolean(true); 622 } 623 624 } 625 626}