SerialUtilities.java 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. /* ========================================================================
  2. * JCommon : a free general purpose class library for the Java(tm) platform
  3. * ========================================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jcommon/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2.1 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17. * License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  22. * USA.
  23. *
  24. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  25. * in the United States and other countries.]
  26. *
  27. * --------------------
  28. * SerialUtilities.java
  29. * --------------------
  30. * (C) Copyright 2000-2005, by Object Refinery Limited.
  31. *
  32. * Original Author: David Gilbert (for Object Refinery Limited);
  33. * Contributor(s): Arik Levin;
  34. *
  35. * $Id: SerialUtilities.java,v 1.15 2011/10/11 12:45:02 matinh Exp $
  36. *
  37. * Changes
  38. * -------
  39. * 25-Mar-2003 : Version 1 (DG);
  40. * 18-Sep-2003 : Added capability to serialize GradientPaint (DG);
  41. * 26-Apr-2004 : Added read/writePoint2D() methods (DG);
  42. * 22-Feb-2005 : Added support for Arc2D - see patch 1147035 by Arik Levin (DG);
  43. * 29-Jul-2005 : Added support for AttributedString (DG);
  44. * 10-Oct-2011 : Added support for AlphaComposite instances (MH);
  45. *
  46. */
  47. package org.jfree.io;
  48. import java.awt.AlphaComposite;
  49. import java.awt.BasicStroke;
  50. import java.awt.Color;
  51. import java.awt.Composite;
  52. import java.awt.GradientPaint;
  53. import java.awt.Paint;
  54. import java.awt.Shape;
  55. import java.awt.Stroke;
  56. import java.awt.geom.Arc2D;
  57. import java.awt.geom.Ellipse2D;
  58. import java.awt.geom.GeneralPath;
  59. import java.awt.geom.Line2D;
  60. import java.awt.geom.PathIterator;
  61. import java.awt.geom.Point2D;
  62. import java.awt.geom.Rectangle2D;
  63. import java.io.IOException;
  64. import java.io.ObjectInputStream;
  65. import java.io.ObjectOutputStream;
  66. import java.io.Serializable;
  67. import java.text.AttributedCharacterIterator;
  68. import java.text.AttributedString;
  69. import java.text.CharacterIterator;
  70. import java.util.HashMap;
  71. import java.util.Map;
  72. /**
  73. * A class containing useful utility methods relating to serialization.
  74. *
  75. * @author David Gilbert
  76. */
  77. public class SerialUtilities {
  78. /**
  79. * Private constructor prevents object creation.
  80. */
  81. private SerialUtilities() {
  82. }
  83. /**
  84. * Returns <code>true</code> if a class implements <code>Serializable</code>
  85. * and <code>false</code> otherwise.
  86. *
  87. * @param c the class.
  88. *
  89. * @return A boolean.
  90. */
  91. public static boolean isSerializable(final Class c) {
  92. /**
  93. final Class[] interfaces = c.getInterfaces();
  94. for (int i = 0; i < interfaces.length; i++) {
  95. if (interfaces[i].equals(Serializable.class)) {
  96. return true;
  97. }
  98. }
  99. Class cc = c.getSuperclass();
  100. if (cc != null) {
  101. return isSerializable(cc);
  102. }
  103. */
  104. return (Serializable.class.isAssignableFrom(c));
  105. }
  106. /**
  107. * Reads a <code>Paint</code> object that has been serialised by the
  108. * {@link SerialUtilities#writePaint(Paint, ObjectOutputStream)} method.
  109. *
  110. * @param stream the input stream (<code>null</code> not permitted).
  111. *
  112. * @return The paint object (possibly <code>null</code>).
  113. *
  114. * @throws IOException if there is an I/O problem.
  115. * @throws ClassNotFoundException if there is a problem loading a class.
  116. */
  117. public static Paint readPaint(final ObjectInputStream stream)
  118. throws IOException, ClassNotFoundException {
  119. if (stream == null) {
  120. throw new IllegalArgumentException("Null 'stream' argument.");
  121. }
  122. Paint result = null;
  123. final boolean isNull = stream.readBoolean();
  124. if (!isNull) {
  125. final Class c = (Class) stream.readObject();
  126. if (isSerializable(c)) {
  127. result = (Paint) stream.readObject();
  128. }
  129. else if (c.equals(GradientPaint.class)) {
  130. final float x1 = stream.readFloat();
  131. final float y1 = stream.readFloat();
  132. final Color c1 = (Color) stream.readObject();
  133. final float x2 = stream.readFloat();
  134. final float y2 = stream.readFloat();
  135. final Color c2 = (Color) stream.readObject();
  136. final boolean isCyclic = stream.readBoolean();
  137. result = new GradientPaint(x1, y1, c1, x2, y2, c2, isCyclic);
  138. }
  139. }
  140. return result;
  141. }
  142. /**
  143. * Serialises a <code>Paint</code> object.
  144. *
  145. * @param paint the paint object (<code>null</code> permitted).
  146. * @param stream the output stream (<code>null</code> not permitted).
  147. *
  148. * @throws IOException if there is an I/O error.
  149. */
  150. public static void writePaint(final Paint paint,
  151. final ObjectOutputStream stream)
  152. throws IOException {
  153. if (stream == null) {
  154. throw new IllegalArgumentException("Null 'stream' argument.");
  155. }
  156. if (paint != null) {
  157. stream.writeBoolean(false);
  158. stream.writeObject(paint.getClass());
  159. if (paint instanceof Serializable) {
  160. stream.writeObject(paint);
  161. }
  162. else if (paint instanceof GradientPaint) {
  163. final GradientPaint gp = (GradientPaint) paint;
  164. stream.writeFloat((float) gp.getPoint1().getX());
  165. stream.writeFloat((float) gp.getPoint1().getY());
  166. stream.writeObject(gp.getColor1());
  167. stream.writeFloat((float) gp.getPoint2().getX());
  168. stream.writeFloat((float) gp.getPoint2().getY());
  169. stream.writeObject(gp.getColor2());
  170. stream.writeBoolean(gp.isCyclic());
  171. }
  172. }
  173. else {
  174. stream.writeBoolean(true);
  175. }
  176. }
  177. /**
  178. * Reads a <code>Stroke</code> object that has been serialised by the
  179. * {@link SerialUtilities#writeStroke(Stroke, ObjectOutputStream)} method.
  180. *
  181. * @param stream the input stream (<code>null</code> not permitted).
  182. *
  183. * @return The stroke object (possibly <code>null</code>).
  184. *
  185. * @throws IOException if there is an I/O problem.
  186. * @throws ClassNotFoundException if there is a problem loading a class.
  187. */
  188. public static Stroke readStroke(final ObjectInputStream stream)
  189. throws IOException, ClassNotFoundException {
  190. if (stream == null) {
  191. throw new IllegalArgumentException("Null 'stream' argument.");
  192. }
  193. Stroke result = null;
  194. final boolean isNull = stream.readBoolean();
  195. if (!isNull) {
  196. final Class c = (Class) stream.readObject();
  197. if (c.equals(BasicStroke.class)) {
  198. final float width = stream.readFloat();
  199. final int cap = stream.readInt();
  200. final int join = stream.readInt();
  201. final float miterLimit = stream.readFloat();
  202. final float[] dash = (float[]) stream.readObject();
  203. final float dashPhase = stream.readFloat();
  204. result = new BasicStroke(
  205. width, cap, join, miterLimit, dash, dashPhase
  206. );
  207. }
  208. else {
  209. result = (Stroke) stream.readObject();
  210. }
  211. }
  212. return result;
  213. }
  214. /**
  215. * Serialises a <code>Stroke</code> object. This code handles the
  216. * <code>BasicStroke</code> class which is the only <code>Stroke</code>
  217. * implementation provided by the JDK (and isn't directly
  218. * <code>Serializable</code>).
  219. *
  220. * @param stroke the stroke object (<code>null</code> permitted).
  221. * @param stream the output stream (<code>null</code> not permitted).
  222. *
  223. * @throws IOException if there is an I/O error.
  224. */
  225. public static void writeStroke(final Stroke stroke,
  226. final ObjectOutputStream stream)
  227. throws IOException {
  228. if (stream == null) {
  229. throw new IllegalArgumentException("Null 'stream' argument.");
  230. }
  231. if (stroke != null) {
  232. stream.writeBoolean(false);
  233. if (stroke instanceof BasicStroke) {
  234. final BasicStroke s = (BasicStroke) stroke;
  235. stream.writeObject(BasicStroke.class);
  236. stream.writeFloat(s.getLineWidth());
  237. stream.writeInt(s.getEndCap());
  238. stream.writeInt(s.getLineJoin());
  239. stream.writeFloat(s.getMiterLimit());
  240. stream.writeObject(s.getDashArray());
  241. stream.writeFloat(s.getDashPhase());
  242. }
  243. else {
  244. stream.writeObject(stroke.getClass());
  245. stream.writeObject(stroke);
  246. }
  247. }
  248. else {
  249. stream.writeBoolean(true);
  250. }
  251. }
  252. /**
  253. * Reads a <code>Composite</code> object that has been serialised by the
  254. * {@link SerialUtilities#writeComposite(Composite, ObjectOutputStream)}
  255. * method.
  256. *
  257. * @param stream the input stream (<code>null</code> not permitted).
  258. *
  259. * @return The composite object (possibly <code>null</code>).
  260. *
  261. * @throws IOException if there is an I/O problem.
  262. * @throws ClassNotFoundException if there is a problem loading a class.
  263. *
  264. * @since 1.0.17
  265. */
  266. public static Composite readComposite(final ObjectInputStream stream)
  267. throws IOException, ClassNotFoundException {
  268. if (stream == null) {
  269. throw new IllegalArgumentException("Null 'stream' argument.");
  270. }
  271. Composite result = null;
  272. final boolean isNull = stream.readBoolean();
  273. if (!isNull) {
  274. final Class c = (Class) stream.readObject();
  275. if (isSerializable(c)) {
  276. result = (Composite) stream.readObject();
  277. }
  278. else if (c.equals(AlphaComposite.class)) {
  279. final int rule = stream.readInt();
  280. final float alpha = stream.readFloat();
  281. result = AlphaComposite.getInstance(rule, alpha);
  282. }
  283. }
  284. return result;
  285. }
  286. /**
  287. * Serialises a <code>Composite</code> object.
  288. *
  289. * @param composite the composite object (<code>null</code> permitted).
  290. * @param stream the output stream (<code>null</code> not permitted).
  291. *
  292. * @throws IOException if there is an I/O error.
  293. *
  294. * @since 1.0.17
  295. */
  296. public static void writeComposite(final Composite composite,
  297. final ObjectOutputStream stream)
  298. throws IOException {
  299. if (stream == null) {
  300. throw new IllegalArgumentException("Null 'stream' argument.");
  301. }
  302. if (composite != null) {
  303. stream.writeBoolean(false);
  304. stream.writeObject(composite.getClass());
  305. if (composite instanceof Serializable) {
  306. stream.writeObject(composite);
  307. }
  308. else if (composite instanceof AlphaComposite) {
  309. final AlphaComposite ac = (AlphaComposite) composite;
  310. stream.writeInt(ac.getRule());
  311. stream.writeFloat(ac.getAlpha());
  312. }
  313. }
  314. else {
  315. stream.writeBoolean(true);
  316. }
  317. }
  318. /**
  319. * Reads a <code>Shape</code> object that has been serialised by the
  320. * {@link #writeShape(Shape, ObjectOutputStream)} method.
  321. *
  322. * @param stream the input stream (<code>null</code> not permitted).
  323. *
  324. * @return The shape object (possibly <code>null</code>).
  325. *
  326. * @throws IOException if there is an I/O problem.
  327. * @throws ClassNotFoundException if there is a problem loading a class.
  328. */
  329. public static Shape readShape(final ObjectInputStream stream)
  330. throws IOException, ClassNotFoundException {
  331. if (stream == null) {
  332. throw new IllegalArgumentException("Null 'stream' argument.");
  333. }
  334. Shape result = null;
  335. final boolean isNull = stream.readBoolean();
  336. if (!isNull) {
  337. final Class c = (Class) stream.readObject();
  338. if (c.equals(Line2D.class)) {
  339. final double x1 = stream.readDouble();
  340. final double y1 = stream.readDouble();
  341. final double x2 = stream.readDouble();
  342. final double y2 = stream.readDouble();
  343. result = new Line2D.Double(x1, y1, x2, y2);
  344. }
  345. else if (c.equals(Rectangle2D.class)) {
  346. final double x = stream.readDouble();
  347. final double y = stream.readDouble();
  348. final double w = stream.readDouble();
  349. final double h = stream.readDouble();
  350. result = new Rectangle2D.Double(x, y, w, h);
  351. }
  352. else if (c.equals(Ellipse2D.class)) {
  353. final double x = stream.readDouble();
  354. final double y = stream.readDouble();
  355. final double w = stream.readDouble();
  356. final double h = stream.readDouble();
  357. result = new Ellipse2D.Double(x, y, w, h);
  358. }
  359. else if (c.equals(Arc2D.class)) {
  360. final double x = stream.readDouble();
  361. final double y = stream.readDouble();
  362. final double w = stream.readDouble();
  363. final double h = stream.readDouble();
  364. final double as = stream.readDouble(); // Angle Start
  365. final double ae = stream.readDouble(); // Angle Extent
  366. final int at = stream.readInt(); // Arc type
  367. result = new Arc2D.Double(x, y, w, h, as, ae, at);
  368. }
  369. else if (c.equals(GeneralPath.class)) {
  370. final GeneralPath gp = new GeneralPath();
  371. final float[] args = new float[6];
  372. boolean hasNext = stream.readBoolean();
  373. while (!hasNext) {
  374. final int type = stream.readInt();
  375. for (int i = 0; i < 6; i++) {
  376. args[i] = stream.readFloat();
  377. }
  378. switch (type) {
  379. case PathIterator.SEG_MOVETO :
  380. gp.moveTo(args[0], args[1]);
  381. break;
  382. case PathIterator.SEG_LINETO :
  383. gp.lineTo(args[0], args[1]);
  384. break;
  385. case PathIterator.SEG_CUBICTO :
  386. gp.curveTo(args[0], args[1], args[2],
  387. args[3], args[4], args[5]);
  388. break;
  389. case PathIterator.SEG_QUADTO :
  390. gp.quadTo(args[0], args[1], args[2], args[3]);
  391. break;
  392. case PathIterator.SEG_CLOSE :
  393. gp.closePath();
  394. break;
  395. default :
  396. throw new RuntimeException(
  397. "JFreeChart - No path exists");
  398. }
  399. gp.setWindingRule(stream.readInt());
  400. hasNext = stream.readBoolean();
  401. }
  402. result = gp;
  403. }
  404. else {
  405. result = (Shape) stream.readObject();
  406. }
  407. }
  408. return result;
  409. }
  410. /**
  411. * Serialises a <code>Shape</code> object.
  412. *
  413. * @param shape the shape object (<code>null</code> permitted).
  414. * @param stream the output stream (<code>null</code> not permitted).
  415. *
  416. * @throws IOException if there is an I/O error.
  417. */
  418. public static void writeShape(final Shape shape,
  419. final ObjectOutputStream stream)
  420. throws IOException {
  421. if (stream == null) {
  422. throw new IllegalArgumentException("Null 'stream' argument.");
  423. }
  424. if (shape != null) {
  425. stream.writeBoolean(false);
  426. if (shape instanceof Line2D) {
  427. final Line2D line = (Line2D) shape;
  428. stream.writeObject(Line2D.class);
  429. stream.writeDouble(line.getX1());
  430. stream.writeDouble(line.getY1());
  431. stream.writeDouble(line.getX2());
  432. stream.writeDouble(line.getY2());
  433. }
  434. else if (shape instanceof Rectangle2D) {
  435. final Rectangle2D rectangle = (Rectangle2D) shape;
  436. stream.writeObject(Rectangle2D.class);
  437. stream.writeDouble(rectangle.getX());
  438. stream.writeDouble(rectangle.getY());
  439. stream.writeDouble(rectangle.getWidth());
  440. stream.writeDouble(rectangle.getHeight());
  441. }
  442. else if (shape instanceof Ellipse2D) {
  443. final Ellipse2D ellipse = (Ellipse2D) shape;
  444. stream.writeObject(Ellipse2D.class);
  445. stream.writeDouble(ellipse.getX());
  446. stream.writeDouble(ellipse.getY());
  447. stream.writeDouble(ellipse.getWidth());
  448. stream.writeDouble(ellipse.getHeight());
  449. }
  450. else if (shape instanceof Arc2D) {
  451. final Arc2D arc = (Arc2D) shape;
  452. stream.writeObject(Arc2D.class);
  453. stream.writeDouble(arc.getX());
  454. stream.writeDouble(arc.getY());
  455. stream.writeDouble(arc.getWidth());
  456. stream.writeDouble(arc.getHeight());
  457. stream.writeDouble(arc.getAngleStart());
  458. stream.writeDouble(arc.getAngleExtent());
  459. stream.writeInt(arc.getArcType());
  460. }
  461. else if (shape instanceof GeneralPath) {
  462. stream.writeObject(GeneralPath.class);
  463. final PathIterator pi = shape.getPathIterator(null);
  464. final float[] args = new float[6];
  465. stream.writeBoolean(pi.isDone());
  466. while (!pi.isDone()) {
  467. final int type = pi.currentSegment(args);
  468. stream.writeInt(type);
  469. // TODO: could write this to only stream the values
  470. // required for the segment type
  471. for (int i = 0; i < 6; i++) {
  472. stream.writeFloat(args[i]);
  473. }
  474. stream.writeInt(pi.getWindingRule());
  475. pi.next();
  476. stream.writeBoolean(pi.isDone());
  477. }
  478. }
  479. else {
  480. stream.writeObject(shape.getClass());
  481. stream.writeObject(shape);
  482. }
  483. }
  484. else {
  485. stream.writeBoolean(true);
  486. }
  487. }
  488. /**
  489. * Reads a <code>Point2D</code> object that has been serialised by the
  490. * {@link #writePoint2D(Point2D, ObjectOutputStream)} method.
  491. *
  492. * @param stream the input stream (<code>null</code> not permitted).
  493. *
  494. * @return The point object (possibly <code>null</code>).
  495. *
  496. * @throws IOException if there is an I/O problem.
  497. */
  498. public static Point2D readPoint2D(final ObjectInputStream stream)
  499. throws IOException {
  500. if (stream == null) {
  501. throw new IllegalArgumentException("Null 'stream' argument.");
  502. }
  503. Point2D result = null;
  504. final boolean isNull = stream.readBoolean();
  505. if (!isNull) {
  506. final double x = stream.readDouble();
  507. final double y = stream.readDouble();
  508. result = new Point2D.Double(x, y);
  509. }
  510. return result;
  511. }
  512. /**
  513. * Serialises a <code>Point2D</code> object.
  514. *
  515. * @param p the point object (<code>null</code> permitted).
  516. * @param stream the output stream (<code>null</code> not permitted).
  517. *
  518. * @throws IOException if there is an I/O error.
  519. */
  520. public static void writePoint2D(final Point2D p,
  521. final ObjectOutputStream stream)
  522. throws IOException {
  523. if (stream == null) {
  524. throw new IllegalArgumentException("Null 'stream' argument.");
  525. }
  526. if (p != null) {
  527. stream.writeBoolean(false);
  528. stream.writeDouble(p.getX());
  529. stream.writeDouble(p.getY());
  530. }
  531. else {
  532. stream.writeBoolean(true);
  533. }
  534. }
  535. /**
  536. * Reads a <code>AttributedString</code> object that has been serialised by
  537. * the {@link SerialUtilities#writeAttributedString(AttributedString,
  538. * ObjectOutputStream)} method.
  539. *
  540. * @param stream the input stream (<code>null</code> not permitted).
  541. *
  542. * @return The attributed string object (possibly <code>null</code>).
  543. *
  544. * @throws IOException if there is an I/O problem.
  545. * @throws ClassNotFoundException if there is a problem loading a class.
  546. */
  547. public static AttributedString readAttributedString(
  548. ObjectInputStream stream)
  549. throws IOException, ClassNotFoundException {
  550. if (stream == null) {
  551. throw new IllegalArgumentException("Null 'stream' argument.");
  552. }
  553. AttributedString result = null;
  554. final boolean isNull = stream.readBoolean();
  555. if (!isNull) {
  556. // read string and attributes then create result
  557. String plainStr = (String) stream.readObject();
  558. result = new AttributedString(plainStr);
  559. char c = stream.readChar();
  560. int start = 0;
  561. while (c != CharacterIterator.DONE) {
  562. int limit = stream.readInt();
  563. Map atts = (Map) stream.readObject();
  564. result.addAttributes(atts, start, limit);
  565. start = limit;
  566. c = stream.readChar();
  567. }
  568. }
  569. return result;
  570. }
  571. /**
  572. * Serialises an <code>AttributedString</code> object.
  573. *
  574. * @param as the attributed string object (<code>null</code> permitted).
  575. * @param stream the output stream (<code>null</code> not permitted).
  576. *
  577. * @throws IOException if there is an I/O error.
  578. */
  579. public static void writeAttributedString(AttributedString as,
  580. ObjectOutputStream stream) throws IOException {
  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. // then write the attributes and limits for each run
  597. current = aci.first();
  598. int begin = aci.getBeginIndex();
  599. while (current != CharacterIterator.DONE) {
  600. // write the current character - when the reader sees that this
  601. // is not CharacterIterator.DONE, it will know to read the
  602. // run limits and attributes
  603. stream.writeChar(current);
  604. // now write the limit, adjusted as if beginIndex is zero
  605. int limit = aci.getRunLimit();
  606. stream.writeInt(limit - begin);
  607. // now write the attribute set
  608. Map atts = new HashMap(aci.getAttributes());
  609. stream.writeObject(atts);
  610. current = aci.setIndex(limit);
  611. }
  612. // write a character that signals to the reader that all runs
  613. // are done...
  614. stream.writeChar(CharacterIterator.DONE);
  615. }
  616. else {
  617. // write a flag that indicates a null
  618. stream.writeBoolean(true);
  619. }
  620. }
  621. }