1. /*
  2. * @(#)AbstractWriter.java 1.19 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text;
  8. import java.io.Writer;
  9. import java.io.IOException;
  10. import java.util.Enumeration;
  11. /**
  12. * AbstractWriter is an abstract class that actually
  13. * does the work of writing out the element tree
  14. * including the attributes. In terms of how much is
  15. * written out per line, the writer defaults to 100.
  16. * But this value can be set by subclasses.
  17. *
  18. * @author Sunita Mani
  19. * @version 1.19, 01/23/03
  20. */
  21. public abstract class AbstractWriter {
  22. private ElementIterator it;
  23. private Writer out;
  24. private int indentLevel = 0;
  25. private int indentSpace = 2;
  26. private Document doc = null;
  27. private int maxLineLength = 100;
  28. private int currLength = 0;
  29. private int startOffset = 0;
  30. private int endOffset = 0;
  31. // If (indentLevel * indentSpace) becomes >= maxLineLength, this will
  32. // get incremened instead of indentLevel to avoid indenting going greater
  33. // than line length.
  34. private int offsetIndent = 0;
  35. /**
  36. * String used for end of line. If the Document has the property
  37. * EndOfLineStringProperty, it will be used for newlines. Otherwise
  38. * the System property line.separator will be used. The line separator
  39. * can also be set.
  40. */
  41. private String lineSeparator;
  42. /**
  43. * True indicates that when writing, the line can be split, false
  44. * indicates that even if the line is > than max line length it should
  45. * not be split.
  46. */
  47. private boolean canWrapLines;
  48. /**
  49. * True while the current line is empty. This will remain true after
  50. * indenting.
  51. */
  52. private boolean isLineEmpty;
  53. /**
  54. * Used when indenting. Will contain the spaces.
  55. */
  56. private char[] indentChars;
  57. /**
  58. * Used when writing out a string.
  59. */
  60. private char[] tempChars;
  61. /**
  62. * This is used in <code>writeLineSeparator</code> instead of
  63. * tempChars. If tempChars were used it would mean write couldn't invoke
  64. * <code>writeLineSeparator</code> as it might have been passed
  65. * tempChars.
  66. */
  67. private char[] newlineChars;
  68. /**
  69. * Used for writing text.
  70. */
  71. private Segment segment;
  72. /**
  73. * How the text packages models newlines.
  74. * @see #getLineSeparator
  75. */
  76. protected static final char NEWLINE = '\n';
  77. /**
  78. * Creates a new AbstractWriter.
  79. * Initializes the ElementIterator with the default
  80. * root of the document.
  81. *
  82. * @param w a Writer.
  83. * @param doc a Document
  84. */
  85. protected AbstractWriter(Writer w, Document doc) {
  86. this(w, doc, 0, doc.getLength());
  87. }
  88. /**
  89. * Creates a new AbstractWriter.
  90. * Initializes the ElementIterator with the
  91. * element passed in.
  92. *
  93. * @param w a Writer
  94. * @param doc an Element
  95. * @param pos The location in the document to fetch the
  96. * content.
  97. * @param len The amount to write out.
  98. */
  99. protected AbstractWriter(Writer w, Document doc, int pos, int len) {
  100. this.doc = doc;
  101. it = new ElementIterator(doc.getDefaultRootElement());
  102. out = w;
  103. startOffset = pos;
  104. endOffset = pos + len;
  105. Object docNewline = doc.getProperty(DefaultEditorKit.
  106. EndOfLineStringProperty);
  107. if (docNewline instanceof String) {
  108. setLineSeparator((String)docNewline);
  109. }
  110. else {
  111. String newline = null;
  112. try {
  113. newline = System.getProperty("line.separator");
  114. } catch (SecurityException se) {}
  115. if (newline == null) {
  116. // Should not get here, but if we do it means we could not
  117. // find a newline string, use \n in this case.
  118. newline = "\n";
  119. }
  120. setLineSeparator(newline);
  121. }
  122. canWrapLines = true;
  123. }
  124. /**
  125. * Creates a new AbstractWriter.
  126. * Initializes the ElementIterator with the
  127. * element passed in.
  128. *
  129. * @param w a Writer
  130. * @param root an Element
  131. */
  132. protected AbstractWriter(Writer w, Element root) {
  133. this(w, root, 0, root.getEndOffset());
  134. }
  135. /**
  136. * Creates a new AbstractWriter.
  137. * Initializes the ElementIterator with the
  138. * element passed in.
  139. *
  140. * @param w a Writer
  141. * @param root an Element
  142. * @param pos The location in the document to fetch the
  143. * content.
  144. * @param len The amount to write out.
  145. */
  146. protected AbstractWriter(Writer w, Element root, int pos, int len) {
  147. this.doc = root.getDocument();
  148. it = new ElementIterator(root);
  149. out = w;
  150. startOffset = pos;
  151. endOffset = pos + len;
  152. canWrapLines = true;
  153. }
  154. /**
  155. * Returns the first offset to be output.
  156. *
  157. * @since 1.3
  158. */
  159. public int getStartOffset() {
  160. return startOffset;
  161. }
  162. /**
  163. * Returns the last offset to be output.
  164. *
  165. * @since 1.3
  166. */
  167. public int getEndOffset() {
  168. return endOffset;
  169. }
  170. /**
  171. * Fetches the ElementIterator.
  172. *
  173. * @return the ElementIterator.
  174. */
  175. protected ElementIterator getElementIterator() {
  176. return it;
  177. }
  178. /**
  179. * Returns the Writer that is used to output the content.
  180. *
  181. * @since 1.3
  182. */
  183. protected Writer getWriter() {
  184. return out;
  185. }
  186. /**
  187. * Fetches the document.
  188. *
  189. * @return the Document.
  190. */
  191. protected Document getDocument() {
  192. return doc;
  193. }
  194. /**
  195. * This method determines whether the current element
  196. * is in the range specified. When no range is specified,
  197. * the range is initialized to be the entire document.
  198. * inRange() returns true if the range specified intersects
  199. * with the element's range.
  200. *
  201. * @param next an Element.
  202. * @return boolean that indicates whether the element
  203. * is in the range.
  204. */
  205. protected boolean inRange(Element next) {
  206. int startOffset = getStartOffset();
  207. int endOffset = getEndOffset();
  208. if ((next.getStartOffset() >= startOffset &&
  209. next.getStartOffset() < endOffset) ||
  210. (startOffset >= next.getStartOffset() &&
  211. startOffset < next.getEndOffset())) {
  212. return true;
  213. }
  214. return false;
  215. }
  216. /**
  217. * This abstract method needs to be implemented
  218. * by subclasses. Its responsibility is to
  219. * iterate over the elements and use the write()
  220. * methods to generate output in the desired format.
  221. */
  222. abstract protected void write() throws IOException, BadLocationException;
  223. /**
  224. * Returns the text associated with the element.
  225. * The assumption here is that the element is a
  226. * leaf element. Throws a BadLocationException
  227. * when encountered.
  228. *
  229. * @param elem an <code>Element</code>
  230. * @exception BadLocationException if pos represents an invalid
  231. * location within the document
  232. * @return the text as a <code>String</code>
  233. */
  234. protected String getText(Element elem) throws BadLocationException {
  235. return doc.getText(elem.getStartOffset(),
  236. elem.getEndOffset() - elem.getStartOffset());
  237. }
  238. /**
  239. * Writes out text. If a range is specified when the constructor
  240. * is invoked, then only the appropriate range of text is written
  241. * out.
  242. *
  243. * @param elem an Element.
  244. * @exception IOException on any I/O error
  245. * @exception BadLocationException if pos represents an invalid
  246. * location within the document.
  247. */
  248. protected void text(Element elem) throws BadLocationException,
  249. IOException {
  250. int start = Math.max(getStartOffset(), elem.getStartOffset());
  251. int end = Math.min(getEndOffset(), elem.getEndOffset());
  252. if (start < end) {
  253. if (segment == null) {
  254. segment = new Segment();
  255. }
  256. getDocument().getText(start, end - start, segment);
  257. if (segment.count > 0) {
  258. write(segment.array, segment.offset, segment.count);
  259. }
  260. }
  261. }
  262. /**
  263. * Enables subclasses to set the number of characters they
  264. * want written per line. The default is 100.
  265. *
  266. * @param l the maximum line length.
  267. */
  268. protected void setLineLength(int l) {
  269. maxLineLength = l;
  270. }
  271. /**
  272. * Returns the maximum line length.
  273. *
  274. * @since 1.3
  275. */
  276. protected int getLineLength() {
  277. return maxLineLength;
  278. }
  279. /**
  280. * Sets the current line length.
  281. *
  282. * @since 1.3.
  283. */
  284. protected void setCurrentLineLength(int length) {
  285. currLength = length;
  286. isLineEmpty = (currLength == 0);
  287. }
  288. /**
  289. * Returns the current line length.
  290. *
  291. * @since 1.3.
  292. */
  293. protected int getCurrentLineLength() {
  294. return currLength;
  295. }
  296. /**
  297. * Returns true if the current line should be considered empty. This
  298. * is true when <code>getCurrentLineLength</code> == 0 ||
  299. * <code>indent</code> has been invoked on an empty line.
  300. *
  301. * @since 1.3
  302. */
  303. protected boolean isLineEmpty() {
  304. return isLineEmpty;
  305. }
  306. /**
  307. * Sets whether or not lines can be wrapped. This can be toggled
  308. * during the writing of lines. For example, outputting HTML might
  309. * set this to false when outputting a quoted string.
  310. *
  311. * @since 1.3
  312. */
  313. protected void setCanWrapLines(boolean newValue) {
  314. canWrapLines = newValue;
  315. }
  316. /**
  317. * Returns whether or not the lines can be wrapped. If this is false
  318. * no lineSeparator's will be output.
  319. *
  320. * @since 1.3
  321. */
  322. protected boolean getCanWrapLines() {
  323. return canWrapLines;
  324. }
  325. /**
  326. * Enables subclasses to specify how many spaces an indent
  327. * maps to. When indentation takes place, the indent level
  328. * is multiplied by this mapping. The default is 2.
  329. *
  330. * @param space an int representing the space to indent mapping.
  331. */
  332. protected void setIndentSpace(int space) {
  333. indentSpace = space;
  334. }
  335. /**
  336. * Returns the amount of space to indent.
  337. *
  338. * @since 1.3
  339. */
  340. protected int getIndentSpace() {
  341. return indentSpace;
  342. }
  343. /**
  344. * Sets the String used to reprsent newlines. This is initialized
  345. * in the constructor from either the Document, or the System property
  346. * line.separator.
  347. *
  348. * @since 1.3
  349. */
  350. public void setLineSeparator(String value) {
  351. lineSeparator = value;
  352. }
  353. /**
  354. * Returns the string used to represent newlines.
  355. *
  356. * @since 1.3
  357. */
  358. public String getLineSeparator() {
  359. return lineSeparator;
  360. }
  361. /**
  362. * Increments the indent level. If indenting would cause
  363. * <code>getIndentSpace()</code> *<code>getIndentLevel()</code> to be >
  364. * than <code>getLineLength()</code> this will not cause an indent.
  365. */
  366. protected void incrIndent() {
  367. // Only increment to a certain point.
  368. if (offsetIndent > 0) {
  369. offsetIndent++;
  370. }
  371. else {
  372. if (++indentLevel * getIndentSpace() >= getLineLength()) {
  373. offsetIndent++;
  374. --indentLevel;
  375. }
  376. }
  377. }
  378. /**
  379. * Decrements the indent level.
  380. */
  381. protected void decrIndent() {
  382. if (offsetIndent > 0) {
  383. --offsetIndent;
  384. }
  385. else {
  386. indentLevel--;
  387. }
  388. }
  389. /**
  390. * Returns the current indentation level. That is, the number of times
  391. * <code>incrIndent</code> has been invoked minus the number of times
  392. * <code>decrIndent</code> has been invoked.
  393. *
  394. * @since 1.3
  395. */
  396. protected int getIndentLevel() {
  397. return indentLevel;
  398. }
  399. /**
  400. * Does indentation. The number of spaces written
  401. * out is indent level times the space to map mapping. If the current
  402. * line is empty, this will not make it so that the current line is
  403. * still considered empty.
  404. *
  405. * @exception IOException on any I/O error
  406. */
  407. protected void indent() throws IOException {
  408. int max = getIndentLevel() * getIndentSpace();
  409. if (indentChars == null || max > indentChars.length) {
  410. indentChars = new char[max];
  411. for (int counter = 0; counter < max; counter++) {
  412. indentChars[counter] = ' ';
  413. }
  414. }
  415. int length = getCurrentLineLength();
  416. boolean wasEmpty = isLineEmpty();
  417. output(indentChars, 0, max);
  418. if (wasEmpty && length == 0) {
  419. isLineEmpty = true;
  420. }
  421. }
  422. /**
  423. * Writes out a character. This is implemented to invoke
  424. * the <code>write</code> method that takes a char[].
  425. *
  426. * @param ch a char.
  427. * @exception IOException on any I/O error
  428. */
  429. protected void write(char ch) throws IOException {
  430. if (tempChars == null) {
  431. tempChars = new char[128];
  432. }
  433. tempChars[0] = ch;
  434. write(tempChars, 0, 1);
  435. }
  436. /**
  437. * Writes out a string. This is implemented to invoke the
  438. * <code>write</code> method that takes a char[].
  439. *
  440. * @param content a String.
  441. * @exception IOException on any I/O error
  442. */
  443. protected void write(String content) throws IOException {
  444. if (content == null) {
  445. return;
  446. }
  447. int size = content.length();
  448. if (tempChars == null || tempChars.length < size) {
  449. tempChars = new char[size];
  450. }
  451. content.getChars(0, size, tempChars, 0);
  452. write(tempChars, 0, size);
  453. }
  454. /**
  455. * Writes the line separator. This invokes <code>output</code> directly
  456. * as well as setting the <code>lineLength</code> to 0.
  457. *
  458. * @since 1.3
  459. */
  460. protected void writeLineSeparator() throws IOException {
  461. String newline = getLineSeparator();
  462. int length = newline.length();
  463. if (newlineChars == null || newlineChars.length < length) {
  464. newlineChars = new char[length];
  465. }
  466. newline.getChars(0, length, newlineChars, 0);
  467. output(newlineChars, 0, length);
  468. setCurrentLineLength(0);
  469. }
  470. /**
  471. * All write methods call into this one. If <code>getCanWrapLines()</code>
  472. * returns false, this will call <code>output</code> with each sequence
  473. * of <code>chars</code> that doesn't contain a NEWLINE, followed
  474. * by a call to <code>writeLineSeparator</code>. On the other hand,
  475. * if <code>getCanWrapLines()</code> returns true, this will split the
  476. * string, as necessary, so <code>getLineLength</code> is honored.
  477. * The only exception is if the current string contains no whitespace,
  478. * and won't fit in which case the line length will exceed
  479. * <code>getLineLength</code>.
  480. *
  481. * @since 1.3
  482. */
  483. protected void write(char[] chars, int startIndex, int length)
  484. throws IOException {
  485. if (!getCanWrapLines()) {
  486. // We can not break string, just track if a newline
  487. // is in it.
  488. int lastIndex = startIndex;
  489. int endIndex = startIndex + length;
  490. int newlineIndex = indexOf(chars, NEWLINE, startIndex, endIndex);
  491. while (newlineIndex != -1) {
  492. if (newlineIndex > lastIndex) {
  493. output(chars, lastIndex, newlineIndex - lastIndex);
  494. }
  495. writeLineSeparator();
  496. lastIndex = newlineIndex + 1;
  497. newlineIndex = indexOf(chars, '\n', lastIndex, endIndex);
  498. }
  499. if (lastIndex < endIndex) {
  500. output(chars, lastIndex, endIndex - lastIndex);
  501. }
  502. }
  503. else {
  504. // We can break chars if the length exceeds maxLength.
  505. int lastIndex = startIndex;
  506. int endIndex = startIndex + length;
  507. int lineLength = getCurrentLineLength();
  508. int maxLength = getLineLength();
  509. if (lineLength >= maxLength && !isLineEmpty()) {
  510. // This can happen if some tags have been written out.
  511. writeLineSeparator();
  512. }
  513. while (lastIndex < endIndex) {
  514. int newlineIndex = indexOf(chars, NEWLINE, lastIndex,
  515. endIndex);
  516. boolean needsNewline = false;
  517. lineLength = getCurrentLineLength();
  518. if (newlineIndex != -1 && (lineLength +
  519. (newlineIndex - lastIndex)) < maxLength) {
  520. if (newlineIndex > lastIndex) {
  521. output(chars, lastIndex, newlineIndex - lastIndex);
  522. }
  523. lastIndex = newlineIndex + 1;
  524. needsNewline = true;
  525. }
  526. else if (newlineIndex == -1 && (lineLength +
  527. (endIndex - lastIndex)) < maxLength) {
  528. if (endIndex > lastIndex) {
  529. output(chars, lastIndex, endIndex - lastIndex);
  530. }
  531. lastIndex = endIndex;
  532. }
  533. else {
  534. // Need to break chars, find a place to split chars at,
  535. // from lastIndex to endIndex,
  536. // or maxLength - lineLength whichever is smaller
  537. int breakPoint = -1;
  538. int maxBreak = Math.min(endIndex - lastIndex,
  539. maxLength - lineLength - 1);
  540. int counter = 0;
  541. while (counter < maxBreak) {
  542. if (Character.isWhitespace(chars[counter +
  543. lastIndex])) {
  544. breakPoint = counter;
  545. }
  546. counter++;
  547. }
  548. if (breakPoint != -1) {
  549. // Found a place to break at.
  550. breakPoint += lastIndex + 1;
  551. output(chars, lastIndex, breakPoint - lastIndex);
  552. lastIndex = breakPoint;
  553. }
  554. else {
  555. // No where good to break.
  556. if (isLineEmpty()) {
  557. // If the current output line is empty, find the
  558. // next whitespace, or write out the whole string.
  559. // maxBreak will be negative if current line too
  560. // long.
  561. counter = Math.max(0, maxBreak);
  562. maxBreak = endIndex - lastIndex;
  563. while (counter < maxBreak) {
  564. if (Character.isWhitespace(chars[counter +
  565. lastIndex])) {
  566. breakPoint = counter;
  567. break;
  568. }
  569. counter++;
  570. }
  571. if (breakPoint == -1) {
  572. output(chars, lastIndex, endIndex - lastIndex);
  573. breakPoint = endIndex;
  574. }
  575. else {
  576. breakPoint += lastIndex;
  577. if (chars[breakPoint] == NEWLINE) {
  578. output(chars, lastIndex, breakPoint++ -
  579. lastIndex);
  580. }
  581. else {
  582. output(chars, lastIndex, ++breakPoint -
  583. lastIndex);
  584. }
  585. }
  586. lastIndex = breakPoint;
  587. }
  588. // else Iterate through again.
  589. }
  590. // Force a newline since line length too long.
  591. needsNewline = true;
  592. }
  593. if (needsNewline || lastIndex < endIndex) {
  594. writeLineSeparator();
  595. if (lastIndex < endIndex) {
  596. indent();
  597. }
  598. }
  599. }
  600. }
  601. }
  602. /**
  603. * Writes out the set of attributes as " <name>=<value>"
  604. * pairs. It throws an IOException when encountered.
  605. *
  606. * @param attr an AttributeSet.
  607. * @exception IOException on any I/O error
  608. */
  609. protected void writeAttributes(AttributeSet attr) throws IOException {
  610. Enumeration names = attr.getAttributeNames();
  611. while (names.hasMoreElements()) {
  612. Object name = names.nextElement();
  613. write(" " + name + "=" + attr.getAttribute(name));
  614. }
  615. }
  616. /**
  617. * The last stop in writing out content. All the write methods eventually
  618. * make it to this method, which invokes <code>write</code> on the
  619. * Writer.
  620. * <p>This method also updates the line length based on
  621. * <code>length</code>. If this is invoked to output a newline, the
  622. * current line length will need to be reset as will no longer be
  623. * valid. If it is up to the caller to do this. Use
  624. * <code>writeLineSeparator</code> to write out a newline, which will
  625. * property update the current line length.
  626. *
  627. * @since 1.3
  628. */
  629. protected void output(char[] content, int start, int length)
  630. throws IOException {
  631. getWriter().write(content, start, length);
  632. setCurrentLineLength(getCurrentLineLength() + length);
  633. }
  634. /**
  635. * Support method to locate an occurence of a particular character.
  636. */
  637. private int indexOf(char[] chars, char sChar, int startIndex,
  638. int endIndex) {
  639. while(startIndex < endIndex) {
  640. if (chars[startIndex] == sChar) {
  641. return startIndex;
  642. }
  643. startIndex++;
  644. }
  645. return -1;
  646. }
  647. }