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