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