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