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.13 2005/11/03 09:55:27 mungady 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 * 045 */ 046 047 package org.jfree.io; 048 049 import java.awt.BasicStroke; 050 import java.awt.Color; 051 import java.awt.GradientPaint; 052 import java.awt.Paint; 053 import java.awt.Shape; 054 import java.awt.Stroke; 055 import java.awt.geom.Arc2D; 056 import java.awt.geom.Ellipse2D; 057 import java.awt.geom.GeneralPath; 058 import java.awt.geom.Line2D; 059 import java.awt.geom.PathIterator; 060 import java.awt.geom.Point2D; 061 import java.awt.geom.Rectangle2D; 062 import java.io.IOException; 063 import java.io.ObjectInputStream; 064 import java.io.ObjectOutputStream; 065 import java.io.Serializable; 066 import java.text.AttributedCharacterIterator; 067 import java.text.AttributedString; 068 import java.text.CharacterIterator; 069 import java.util.HashMap; 070 import java.util.Map; 071 072 /** 073 * A class containing useful utility methods relating to serialization. 074 * 075 * @author David Gilbert 076 */ 077 public class SerialUtilities { 078 079 /** 080 * Private constructor prevents object creation. 081 */ 082 private SerialUtilities() { 083 } 084 085 /** 086 * Returns <code>true</code> if a class implements <code>Serializable</code> 087 * and <code>false</code> otherwise. 088 * 089 * @param c the class. 090 * 091 * @return A boolean. 092 */ 093 public static boolean isSerializable(final Class c) { 094 /** 095 final Class[] interfaces = c.getInterfaces(); 096 for (int i = 0; i < interfaces.length; i++) { 097 if (interfaces[i].equals(Serializable.class)) { 098 return true; 099 } 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 } 591