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.14 2008/06/02 06:58:28 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(args[0], args[1], args[2], 336 args[3], args[4], args[5]); 337 break; 338 case PathIterator.SEG_QUADTO : 339 gp.quadTo(args[0], args[1], args[2], args[3]); 340 break; 341 case PathIterator.SEG_CLOSE : 342 gp.closePath(); 343 break; 344 default : 345 throw new RuntimeException( 346 "JFreeChart - No path exists"); 347 } 348 gp.setWindingRule(stream.readInt()); 349 hasNext = stream.readBoolean(); 350 } 351 result = gp; 352 } 353 else { 354 result = (Shape) stream.readObject(); 355 } 356 } 357 return result; 358 359 } 360 361 /** 362 * Serialises a <code>Shape</code> object. 363 * 364 * @param shape the shape object (<code>null</code> permitted). 365 * @param stream the output stream (<code>null</code> not permitted). 366 * 367 * @throws IOException if there is an I/O error. 368 */ 369 public static void writeShape(final Shape shape, 370 final ObjectOutputStream stream) 371 throws IOException { 372 373 if (stream == null) { 374 throw new IllegalArgumentException("Null 'stream' argument."); 375 } 376 if (shape != null) { 377 stream.writeBoolean(false); 378 if (shape instanceof Line2D) { 379 final Line2D line = (Line2D) shape; 380 stream.writeObject(Line2D.class); 381 stream.writeDouble(line.getX1()); 382 stream.writeDouble(line.getY1()); 383 stream.writeDouble(line.getX2()); 384 stream.writeDouble(line.getY2()); 385 } 386 else if (shape instanceof Rectangle2D) { 387 final Rectangle2D rectangle = (Rectangle2D) shape; 388 stream.writeObject(Rectangle2D.class); 389 stream.writeDouble(rectangle.getX()); 390 stream.writeDouble(rectangle.getY()); 391 stream.writeDouble(rectangle.getWidth()); 392 stream.writeDouble(rectangle.getHeight()); 393 } 394 else if (shape instanceof Ellipse2D) { 395 final Ellipse2D ellipse = (Ellipse2D) shape; 396 stream.writeObject(Ellipse2D.class); 397 stream.writeDouble(ellipse.getX()); 398 stream.writeDouble(ellipse.getY()); 399 stream.writeDouble(ellipse.getWidth()); 400 stream.writeDouble(ellipse.getHeight()); 401 } 402 else if (shape instanceof Arc2D) { 403 final Arc2D arc = (Arc2D) shape; 404 stream.writeObject(Arc2D.class); 405 stream.writeDouble(arc.getX()); 406 stream.writeDouble(arc.getY()); 407 stream.writeDouble(arc.getWidth()); 408 stream.writeDouble(arc.getHeight()); 409 stream.writeDouble(arc.getAngleStart()); 410 stream.writeDouble(arc.getAngleExtent()); 411 stream.writeInt(arc.getArcType()); 412 } 413 else if (shape instanceof GeneralPath) { 414 stream.writeObject(GeneralPath.class); 415 final PathIterator pi = shape.getPathIterator(null); 416 final float[] args = new float[6]; 417 stream.writeBoolean(pi.isDone()); 418 while (!pi.isDone()) { 419 final int type = pi.currentSegment(args); 420 stream.writeInt(type); 421 // TODO: could write this to only stream the values 422 // required for the segment type 423 for (int i = 0; i < 6; i++) { 424 stream.writeFloat(args[i]); 425 } 426 stream.writeInt(pi.getWindingRule()); 427 pi.next(); 428 stream.writeBoolean(pi.isDone()); 429 } 430 } 431 else { 432 stream.writeObject(shape.getClass()); 433 stream.writeObject(shape); 434 } 435 } 436 else { 437 stream.writeBoolean(true); 438 } 439 } 440 441 /** 442 * Reads a <code>Point2D</code> object that has been serialised by the 443 * {@link #writePoint2D(Point2D, ObjectOutputStream)} method. 444 * 445 * @param stream the input stream (<code>null</code> not permitted). 446 * 447 * @return The point object (possibly <code>null</code>). 448 * 449 * @throws IOException if there is an I/O problem. 450 */ 451 public static Point2D readPoint2D(final ObjectInputStream stream) 452 throws IOException { 453 454 if (stream == null) { 455 throw new IllegalArgumentException("Null 'stream' argument."); 456 } 457 Point2D result = null; 458 final boolean isNull = stream.readBoolean(); 459 if (!isNull) { 460 final double x = stream.readDouble(); 461 final double y = stream.readDouble(); 462 result = new Point2D.Double(x, y); 463 } 464 return result; 465 466 } 467 468 /** 469 * Serialises a <code>Point2D</code> object. 470 * 471 * @param p the point object (<code>null</code> permitted). 472 * @param stream the output stream (<code>null</code> not permitted). 473 * 474 * @throws IOException if there is an I/O error. 475 */ 476 public static void writePoint2D(final Point2D p, 477 final ObjectOutputStream stream) 478 throws IOException { 479 480 if (stream == null) { 481 throw new IllegalArgumentException("Null 'stream' argument."); 482 } 483 if (p != null) { 484 stream.writeBoolean(false); 485 stream.writeDouble(p.getX()); 486 stream.writeDouble(p.getY()); 487 } 488 else { 489 stream.writeBoolean(true); 490 } 491 } 492 493 /** 494 * Reads a <code>AttributedString</code> object that has been serialised by 495 * the {@link SerialUtilities#writeAttributedString(AttributedString, 496 * ObjectOutputStream)} method. 497 * 498 * @param stream the input stream (<code>null</code> not permitted). 499 * 500 * @return The attributed string object (possibly <code>null</code>). 501 * 502 * @throws IOException if there is an I/O problem. 503 * @throws ClassNotFoundException if there is a problem loading a class. 504 */ 505 public static AttributedString readAttributedString( 506 ObjectInputStream stream) 507 throws IOException, ClassNotFoundException { 508 509 if (stream == null) { 510 throw new IllegalArgumentException("Null 'stream' argument."); 511 } 512 AttributedString result = null; 513 final boolean isNull = stream.readBoolean(); 514 if (!isNull) { 515 // read string and attributes then create result 516 String plainStr = (String) stream.readObject(); 517 result = new AttributedString(plainStr); 518 char c = stream.readChar(); 519 int start = 0; 520 while (c != CharacterIterator.DONE) { 521 int limit = stream.readInt(); 522 Map atts = (Map) stream.readObject(); 523 result.addAttributes(atts, start, limit); 524 start = limit; 525 c = stream.readChar(); 526 } 527 } 528 return result; 529 } 530 531 /** 532 * Serialises an <code>AttributedString</code> object. 533 * 534 * @param as the attributed string object (<code>null</code> permitted). 535 * @param stream the output stream (<code>null</code> not permitted). 536 * 537 * @throws IOException if there is an I/O error. 538 */ 539 public static void writeAttributedString(AttributedString as, 540 ObjectOutputStream stream) throws IOException { 541 542 if (stream == null) { 543 throw new IllegalArgumentException("Null 'stream' argument."); 544 } 545 if (as != null) { 546 stream.writeBoolean(false); 547 AttributedCharacterIterator aci = as.getIterator(); 548 // build a plain string from aci 549 // then write the string 550 StringBuffer plainStr = new StringBuffer(); 551 char current = aci.first(); 552 while (current != CharacterIterator.DONE) { 553 plainStr = plainStr.append(current); 554 current = aci.next(); 555 } 556 stream.writeObject(plainStr.toString()); 557 558 // then write the attributes and limits for each run 559 current = aci.first(); 560 int begin = aci.getBeginIndex(); 561 while (current != CharacterIterator.DONE) { 562 // write the current character - when the reader sees that this 563 // is not CharacterIterator.DONE, it will know to read the 564 // run limits and attributes 565 stream.writeChar(current); 566 567 // now write the limit, adjusted as if beginIndex is zero 568 int limit = aci.getRunLimit(); 569 stream.writeInt(limit - begin); 570 571 // now write the attribute set 572 Map atts = new HashMap(aci.getAttributes()); 573 stream.writeObject(atts); 574 current = aci.setIndex(limit); 575 } 576 // write a character that signals to the reader that all runs 577 // are done... 578 stream.writeChar(CharacterIterator.DONE); 579 } 580 else { 581 // write a flag that indicates a null 582 stream.writeBoolean(true); 583 } 584 585 } 586 587 } 588