1. /*
  2. * @(#)AbstractWriter.java 1.14 00/02/02
  3. *
  4. * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.text;
  11. import java.io.Writer;
  12. import java.io.IOException;
  13. import java.util.Enumeration;
  14. /**
  15. * AbstractWriter is an abstract class that actually
  16. * does the work of writing out the element tree
  17. * including the attributes. In terms of how much is
  18. * written out per line, the writer defaults to 100.
  19. * But this value can be set by subclasses.
  20. *
  21. * @author Sunita Mani
  22. * @version 1.14, 02/02/00
  23. */
  24. public abstract class AbstractWriter {
  25. private ElementIterator it;
  26. private Writer out;
  27. private int indentLevel = 0;
  28. private int indentSpace = 2;
  29. private Document doc = null;
  30. private int maxLineLength = 100;
  31. private int currLength = 0;
  32. private int startOffset = 0;
  33. private int endOffset = 0;
  34. // If (indentLevel * indentSpace) becomes >= maxLineLength, this will
  35. // get incremened instead of indentLevel to avoid indenting going greater
  36. // than line length.
  37. private int offsetIndent = 0;
  38. /**
  39. * String used for end of line. If the Document has the property
  40. * EndOfLineStringProperty, it will be used for newlines. Otherwise
  41. * the System property line.separator will be used. The line separator
  42. * can also be set.
  43. */
  44. private String lineSeparator;
  45. /**
  46. * True indicates that when writing, the line can be split, false
  47. * indicates that even if the line is > than max line length it should
  48. * not be split.
  49. */
  50. private boolean canWrapLines;
  51. /**
  52. * True while the current line is empty. This will remain true after
  53. * indenting.
  54. */
  55. private boolean isLineEmpty;
  56. /**
  57. * Used when indenting. Will contain the spaces.
  58. */
  59. private char[] indentChars;
  60. /**
  61. * Used when writing out a string.
  62. */
  63. private char[] tempChars;
  64. /**
  65. * This is used in <code>writeLineSeparator</code> instead of
  66. * tempChars. If tempChars were used it would mean write couldn't invoke
  67. * <code>writeLineSeparator</code> as it might have been passed
  68. * tempChars.
  69. */
  70. private char[] newlineChars;
  71. /**
  72. * Used for writing text.
  73. */
  74. private Segment segment;
  75. /**
  76. * How the text packages models newlines.
  77. * @see #getLineSeparator
  78. */
  79. protected static final char NEWLINE = '\n';
  80. /**
  81. * Creates a new AbstractWriter.
  82. * Initializes the ElementIterator with the default
  83. * root of the document.
  84. *
  85. * @param a Writer.
  86. * @param a Document
  87. */
  88. protected AbstractWriter(Writer w, Document doc) {
  89. this(w, doc, 0, doc.getLength());
  90. }
  91. /**
  92. * Creates a new AbstractWriter.
  93. * Initializes the ElementIterator with the
  94. * element passed in.
  95. *
  96. * @param a Writer
  97. * @param an Element
  98. * @param pos The location in the document to fetch the
  99. * content.
  100. * @param len The amount to write out.
  101. */
  102. protected AbstractWriter(Writer w, Document doc, int pos, int len) {
  103. this.doc = doc;
  104. it = new ElementIterator(doc.getDefaultRootElement());
  105. out = w;
  106. startOffset = pos;
  107. endOffset = pos + len;
  108. Object docNewline = doc.getProperty(DefaultEditorKit.
  109. EndOfLineStringProperty);
  110. if (docNewline instanceof String) {
  111. setLineSeparator((String)docNewline);
  112. }
  113. else {
  114. String newline = null;
  115. try {
  116. newline = System.getProperty("line.separator");
  117. } catch (SecurityException se) {}
  118. if (newline == null) {
  119. // Should not get here, but if we do it means we could not
  120. // find a newline string, use \n in this case.
  121. newline = "\n";
  122. }
  123. setLineSeparator(newline);
  124. }
  125. canWrapLines = true;
  126. }
  127. /**
  128. * Creates a new AbstractWriter.
  129. * Initializes the ElementIterator with the
  130. * element passed in.
  131. *
  132. * @param a Writer
  133. * @param an Element
  134. */
  135. protected AbstractWriter(Writer w, Element root) {
  136. this(w, root, 0, root.getEndOffset());
  137. }
  138. /**
  139. * Creates a new AbstractWriter.
  140. * Initializes the ElementIterator with the
  141. * element passed in.
  142. *
  143. * @param a Writer
  144. * @param an Element
  145. * @param pos The location in the document to fetch the
  146. * content.
  147. * @param len The amount to write out.
  148. */
  149. protected AbstractWriter(Writer w, Element root, int pos, int len) {
  150. this.doc = root.getDocument();
  151. it = new ElementIterator(root);
  152. out = w;
  153. startOffset = pos;
  154. endOffset = pos + len;
  155. canWrapLines = true;
  156. }
  157. /**
  158. * Returns the first offset to be output.
  159. *
  160. * @since 1.3
  161. */
  162. public int getStartOffset() {
  163. return startOffset;
  164. }
  165. /**
  166. * Returns the last offset to be output.
  167. *
  168. * @since 1.3
  169. */
  170. public int getEndOffset() {
  171. return endOffset;
  172. }
  173. /**
  174. * Fetches the ElementIterator.
  175. *
  176. * @return the ElementIterator.
  177. */
  178. protected ElementIterator getElementIterator() {
  179. return it;
  180. }
  181. /**
  182. * Returns the Writer that is used to output the content.
  183. *
  184. * @since 1.3
  185. */
  186. protected Writer getWriter() {
  187. return out;
  188. }
  189. /**
  190. * Fetches the document.
  191. *
  192. * @return the Document.
  193. */
  194. protected Document getDocument() {
  195. return doc;
  196. }
  197. /**
  198. * This method determines whether the current element
  199. * is in the range specified. When no range is specified,
  200. * the range is initialized to be the entire document.
  201. * inRange() returns true if the range specified intersects
  202. * with the element's range.
  203. *
  204. * @param an Element.
  205. * @return boolean that indicates whether the element
  206. * is in the range.
  207. */
  208. protected boolean inRange(Element next) {
  209. int startOffset = getStartOffset();
  210. int endOffset = getEndOffset();
  211. if ((next.getStartOffset() >= startOffset &&
  212. next.getStartOffset() < endOffset) ||
  213. (startOffset >= next.getStartOffset() &&
  214. startOffset < next.getEndOffset())) {
  215. return true;
  216. }
  217. return false;
  218. }
  219. /**
  220. * This abstract method needs to be implemented
  221. * by subclasses. Its responsibility is to
  222. * iterate over the elements and use the write()
  223. * methods to generate output in the desired format.
  224. */
  225. abstract protected void write() throws IOException, BadLocationException;
  226. /**
  227. * Returns the text associated with the element.
  228. * The assumption here is that the element is a
  229. * leaf element. Throws a BadLocationException
  230. * when encountered.
  231. *
  232. * @param an Element.
  233. * @exception BadLocationException if pos represents an invalid
  234. * location within the document.
  235. * @returns the text as a String.
  236. */
  237. protected String getText(Element elem) throws BadLocationException {
  238. return doc.getText(elem.getStartOffset(),
  239. elem.getEndOffset() - elem.getStartOffset());
  240. }
  241. /**
  242. * Writes out text. If a range is specified when the constructor
  243. * is invoked, then only the appropriate range of text is written
  244. * out.
  245. *
  246. * @param an Element.
  247. * @exception IOException on any I/O error
  248. * @exception BadLocationException if pos represents an invalid
  249. * location within the document.
  250. */
  251. protected void text(Element elem) throws BadLocationException,
  252. IOException {
  253. int start = Math.max(getStartOffset(), elem.getStartOffset());
  254. int end = Math.min(getEndOffset(), elem.getEndOffset());
  255. if (start < end) {
  256. if (segment == null) {
  257. segment = new Segment();
  258. }
  259. getDocument().getText(start, end - start, segment);
  260. if (segment.count > 0) {
  261. write(segment.array, segment.offset, segment.count);
  262. }
  263. }
  264. }
  265. /**
  266. * Enables subclasses to set the number of characters they
  267. * want written per line. The default is 100.
  268. *
  269. * @param the maximum line length.
  270. */
  271. protected void setLineLength(int l) {
  272. maxLineLength = l;
  273. }
  274. /**
  275. * Returns the maximum line length.
  276. *
  277. * @since 1.3
  278. */
  279. protected int getLineLength() {
  280. return maxLineLength;
  281. }
  282. /**
  283. * Sets the current line length.
  284. *
  285. * @since 1.3.
  286. */
  287. protected void setCurrentLineLength(int length) {
  288. currLength = length;
  289. isLineEmpty = (currLength == 0);
  290. }
  291. /**
  292. * Returns the current line length.
  293. *
  294. * @since 1.3.
  295. */
  296. protected int getCurrentLineLength() {
  297. return currLength;
  298. }
  299. /**
  300. * Returns true if the current line should be considered empty. This
  301. * is true when <code>getCurrentLineLength</code> == 0 ||
  302. * <code>indent</code> has been invoked on an empty line.
  303. *
  304. * @since 1.3
  305. */
  306. protected boolean isLineEmpty() {
  307. return isLineEmpty;
  308. }
  309. /**
  310. * Sets whether or not lines can be wrapped. This can be toggled
  311. * during the writing of lines. For example, outputting HTML might
  312. * set this to false when outputting a quoted string.
  313. *
  314. * @since 1.3
  315. */
  316. protected void setCanWrapLines(boolean newValue) {
  317. canWrapLines = newValue;
  318. }
  319. /**
  320. * Returns whether or not the lines can be wrapped. If this is false
  321. * no lineSeparator's will be output.
  322. *
  323. * @since 1.3
  324. */
  325. protected boolean getCanWrapLines() {
  326. return canWrapLines;
  327. }
  328. /**
  329. * Enables subclasses to specify how many spaces an indent
  330. * maps to. When indentation takes place, the indent level
  331. * is multiplied by this mapping. The default is 2.
  332. *
  333. * @param an int representing the space to indent mapping.
  334. */
  335. protected void setIndentSpace(int space) {
  336. indentSpace = space;
  337. }
  338. /**
  339. * Returns the amount of space to indent.
  340. *
  341. * @since 1.3
  342. */
  343. protected int getIndentSpace() {
  344. return indentSpace;
  345. }
  346. /**
  347. * Sets the String used to reprsent newlines. This is initialized
  348. * in the constructor from either the Document, or the System property
  349. * line.separator.
  350. *
  351. * @since 1.3
  352. */
  353. public void setLineSeparator(String value) {
  354. lineSeparator = value;
  355. }
  356. /**
  357. * Returns the string used to represent newlines.
  358. *
  359. * @since 1.3
  360. */
  361. public String getLineSeparator() {
  362. return lineSeparator;
  363. }
  364. /**
  365. * Increments the indent level. If indenting would cause
  366. * <code>getIndentSpace()</code> *<code>getIndentLevel()</code> to be >
  367. * than <code>getLineLength()</code> this will not cause an indent.
  368. */
  369. protected void incrIndent() {
  370. // Only increment to a certain point.
  371. if (offsetIndent > 0) {
  372. offsetIndent++;
  373. }
  374. else {
  375. if (++indentLevel * getIndentSpace() >= getLineLength()) {
  376. offsetIndent++;
  377. --indentLevel;
  378. }
  379. }
  380. }
  381. /**
  382. * Decrements the indent level.
  383. */
  384. protected void decrIndent() {
  385. if (offsetIndent > 0) {
  386. --offsetIndent;
  387. }
  388. else {
  389. indentLevel--;
  390. }
  391. }
  392. /**
  393. * Returns the current indentation level. That is, the number of times
  394. * <code>incrIndent</code> has been invoked minus the number of times
  395. * <code>decrIndent</code> has been invoked.
  396. *
  397. * @since 1.3
  398. */
  399. protected int getIndentLevel() {
  400. return indentLevel;
  401. }
  402. /**
  403. * Does indentation. The number of spaces written
  404. * out is indent level times the space to map mapping. If the current
  405. * line is empty, this will not make it so that the current line is
  406. * still considered empty.
  407. *
  408. * @exception IOException on any I/O error
  409. */
  410. protected void indent() throws IOException {
  411. int max = getIndentLevel() * getIndentSpace();
  412. if (indentChars == null || max > indentChars.length) {
  413. indentChars = new char[max];
  414. for (int counter = 0; counter < max; counter++) {
  415. indentChars[counter] = ' ';
  416. }
  417. }
  418. int length = getCurrentLineLength();
  419. boolean wasEmpty = isLineEmpty();
  420. output(indentChars, 0, max);
  421. if (wasEmpty && length == 0) {
  422. isLineEmpty = true;
  423. }
  424. }
  425. /**
  426. * Writes out a character. This is implemented to invoke
  427. * the <code>write</code> method that takes a char[].
  428. *
  429. * @param a char.
  430. * @exception IOException on any I/O error
  431. */
  432. protected void write(char ch) throws IOException {
  433. if (tempChars == null) {
  434. tempChars = new char[128];
  435. }
  436. tempChars[0] = ch;
  437. write(tempChars, 0, 1);
  438. }
  439. /**
  440. * Writes out a string. This is implemented to invoke the
  441. * <code>write</code> method that takes a char[].
  442. *
  443. * @param a String.
  444. * @exception IOException on any I/O error
  445. */
  446. protected void write(String content) throws IOException {
  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 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. }