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 * 190 * @since 1.3 191 */ 192 public int getStartOffset() { 193 return startOffset; 194 } 195 196 /** 197 * Returns the last offset to be output. 198 * 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 * 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 */ 261 abstract protected void write() throws IOException, BadLocationException; 262 263 /** 264 * Returns the text associated with the element. 265 * The assumption here is that the element is a 266 * leaf element. Throws a BadLocationException 267 * when encountered. 268 * 269 * @param elem an <code>Element</code> 270 * @exception BadLocationException if pos represents an invalid 271 * location within the document 272 * @return the text as a <code>String</code> 273 */ 274 protected String getText(Element elem) throws BadLocationException { 275 return doc.getText(elem.getStartOffset(), 276 elem.getEndOffset() - elem.getStartOffset()); 277 } 278 279 280 /** 281 * Writes out text. If a range is specified when the constructor 282 * is invoked, then only the appropriate range of text is written 283 * out. 284 * 285 * @param elem an Element. 286 * @exception IOException on any I/O error 287 * @exception BadLocationException if pos represents an invalid 288 * location within the document. 289 */ 290 protected void text(Element elem) throws BadLocationException, 291 IOException { 292 int start = Math.max(getStartOffset(), elem.getStartOffset()); 293 int end = Math.min(getEndOffset(), elem.getEndOffset()); 294 if (start < end) { 295 if (segment == null) { 296 segment = new Segment(); 297 } 298 getDocument().getText(start, end - start, segment); 299 if (segment.count > 0) { 300 write(segment.array, segment.offset, segment.count); 301 } 302 } 303 } 304 305 /** 306 * Enables subclasses to set the number of characters they 307 * want written per line. The default is 100. 308 * 309 * @param l the maximum line length. 310 */ 311 protected void setLineLength(int l) { 312 maxLineLength = l; 313 } 314 315 /** 316 * Returns the maximum line length. 317 * 318 * @since 1.3 319 */ 320 protected int getLineLength() { 321 return maxLineLength; 322 } 323 324 /** 325 * Sets the current line length. 326 * 327 * @since 1.3 328 */ 329 protected void setCurrentLineLength(int length) { 330 currLength = length; 331 isLineEmpty = (currLength == 0); 332 } 333 334 /** 335 * Returns the current line length. 336 * 337 * @since 1.3 338 */ 339 protected int getCurrentLineLength() { 340 return currLength; 341 } 342 343 /** 344 * Returns true if the current line should be considered empty. This 345 * is true when <code>getCurrentLineLength</code> == 0 || 346 * <code>indent</code> has been invoked on an empty line. 347 * 348 * @since 1.3 349 */ 350 protected boolean isLineEmpty() { 351 return isLineEmpty; 352 } 353 354 /** 355 * Sets whether or not lines can be wrapped. This can be toggled 356 * during the writing of lines. For example, outputting HTML might 357 * set this to false when outputting a quoted string. 358 * 359 * @since 1.3 360 */ 361 protected void setCanWrapLines(boolean newValue) { 362 canWrapLines = newValue; 363 } 364 365 /** 366 * Returns whether or not the lines can be wrapped. If this is false 367 * no lineSeparator's will be output. 368 * 369 * @since 1.3 370 */ 371 protected boolean getCanWrapLines() { 372 return canWrapLines; 373 } 374 375 /** 376 * Enables subclasses to specify how many spaces an indent 377 * maps to. When indentation takes place, the indent level 378 * is multiplied by this mapping. The default is 2. 379 * 380 * @param space an int representing the space to indent mapping. 381 */ 382 protected void setIndentSpace(int space) { 383 indentSpace = space; 384 } 385 386 /** 387 * Returns the amount of space to indent. 388 * 389 * @since 1.3 390 */ 391 protected int getIndentSpace() { 392 return indentSpace; 393 } 394 395 /** 396 * Sets the String used to represent newlines. This is initialized 397 * in the constructor from either the Document, or the System property 398 * line.separator. 399 * 400 * @since 1.3 401 */ 402 public void setLineSeparator(String value) { 403 lineSeparator = value; 404 } 405 406 /** 407 * Returns the string used to represent newlines. 408 * 409 * @since 1.3 410 */ 411 public String getLineSeparator() { 412 return lineSeparator; 413 } 414 415 /** 416 * Increments the indent level. If indenting would cause 417 * <code>getIndentSpace()</code> *<code>getIndentLevel()</code> to be > 418 * than <code>getLineLength()</code> this will not cause an indent. 419 */ 420 protected void incrIndent() { 421 // Only increment to a certain point. 422 if (offsetIndent > 0) { 423 offsetIndent++; 424 } 425 else { 426 if (++indentLevel * getIndentSpace() >= getLineLength()) { 427 offsetIndent++; 428 --indentLevel; 429 } 430 } 431 } 432 433 /** 434 * Decrements the indent level. 435 */ 436 protected void decrIndent() { 437 if (offsetIndent > 0) { 438 --offsetIndent; 439 } 440 else { 441 indentLevel--; 442 } 443 } 444 445 /** 446 * Returns the current indentation level. That is, the number of times 447 * <code>incrIndent</code> has been invoked minus the number of times 448 * <code>decrIndent</code> has been invoked. 449 * 450 * @since 1.3 451 */ 452 protected int getIndentLevel() { 453 return indentLevel; 454 } 455 456 /** 457 * Does indentation. The number of spaces written 458 * out is indent level times the space to map mapping. If the current 459 * line is empty, this will not make it so that the current line is 460 * still considered empty. 461 * 462 * @exception IOException on any I/O error 463 */ 464 protected void indent() throws IOException { 465 int max = getIndentLevel() * getIndentSpace(); 466 if (indentChars == null || max > indentChars.length) { 467 indentChars = new char[max]; 468 for (int counter = 0; counter < max; counter++) { 469 indentChars[counter] = ' '; 470 } 471 } 472 int length = getCurrentLineLength(); 473 boolean wasEmpty = isLineEmpty(); 474 output(indentChars, 0, max); 475 if (wasEmpty && length == 0) { 476 isLineEmpty = true; 477 } 478 } 479 480 /** 481 * Writes out a character. This is implemented to invoke 482 * the <code>write</code> method that takes a char[]. 483 * 484 * @param ch a char. 485 * @exception IOException on any I/O error 486 */ 487 protected void write(char ch) throws IOException { 488 if (tempChars == null) { 489 tempChars = new char[128]; 490 } 491 tempChars[0] = ch; 492 write(tempChars, 0, 1); 493 } 494 495 /** 496 * Writes out a string. This is implemented to invoke the 497 * <code>write</code> method that takes a char[]. 498 * 499 * @param content a String. 500 * @exception IOException on any I/O error 501 */ 502 protected void write(String content) throws IOException { 503 if (content == null) { 504 return; 505 } 506 int size = content.length(); 507 if (tempChars == null || tempChars.length < size) { 508 tempChars = new char[size]; 509 } 510 content.getChars(0, size, tempChars, 0); 511 write(tempChars, 0, size); 512 } 513 514 /** 515 * Writes the line separator. This invokes <code>output</code> directly 516 * as well as setting the <code>lineLength</code> to 0. 517 * 518 * @since 1.3 519 */ 520 protected void writeLineSeparator() throws IOException { 521 String newline = getLineSeparator(); 522 int length = newline.length(); 523 if (newlineChars == null || newlineChars.length < length) { 524 newlineChars = new char[length]; 525 } 526 newline.getChars(0, length, newlineChars, 0); 527 output(newlineChars, 0, length); 528 setCurrentLineLength(0); 529 } 530 531 /** 532 * All write methods call into this one. If <code>getCanWrapLines()</code> 533 * returns false, this will call <code>output</code> with each sequence 534 * of <code>chars</code> that doesn't contain a NEWLINE, followed 535 * by a call to <code>writeLineSeparator</code>. On the other hand, 536 * if <code>getCanWrapLines()</code> returns true, this will split the 537 * string, as necessary, so <code>getLineLength</code> is honored. 538 * The only exception is if the current string contains no whitespace, 539 * and won't fit in which case the line length will exceed 540 * <code>getLineLength</code>. 541 * 542 * @since 1.3 543 */ 544 protected void write(char[] chars, int startIndex, int length) 545 throws IOException { 546 if (!getCanWrapLines()) { 547 // We can not break string, just track if a newline 548 // is in it. 549 int lastIndex = startIndex; 550 int endIndex = startIndex + length; 551 int newlineIndex = indexOf(chars, NEWLINE, startIndex, endIndex); 552 while (newlineIndex != -1) { 553 if (newlineIndex > lastIndex) { 554 output(chars, lastIndex, newlineIndex - lastIndex); 555 } 556 writeLineSeparator(); 557 lastIndex = newlineIndex + 1; 558 newlineIndex = indexOf(chars, '\n', lastIndex, endIndex); 559 } 560 if (lastIndex < endIndex) { 561 output(chars, lastIndex, endIndex - lastIndex); 562 } 563 } 564 else { 565 // We can break chars if the length exceeds maxLength. 566 int lastIndex = startIndex; 567 int endIndex = startIndex + length; 568 int lineLength = getCurrentLineLength(); 569 int maxLength = getLineLength(); 570 571 while (lastIndex < endIndex) { 572 int newlineIndex = indexOf(chars, NEWLINE, lastIndex, 573 endIndex); 574 boolean needsNewline = false; 575 boolean forceNewLine = false; 576 577 lineLength = getCurrentLineLength(); 578 if (newlineIndex != -1 && (lineLength + 579 (newlineIndex - lastIndex)) < maxLength) { 580 if (newlineIndex > lastIndex) { 581 output(chars, lastIndex, newlineIndex - lastIndex); 582 } 583 lastIndex = newlineIndex + 1; 584 forceNewLine = true; 585 } 586 else if (newlineIndex == -1 && (lineLength + 587 (endIndex - lastIndex)) < maxLength) { 588 if (endIndex > lastIndex) { 589 output(chars, lastIndex, endIndex - lastIndex); 590 } 591 lastIndex = endIndex; 592 } 593 else { 594 // Need to break chars, find a place to split chars at, 595 // from lastIndex to endIndex, 596 // or maxLength - lineLength whichever is smaller 597 int breakPoint = -1; 598 int maxBreak = Math.min(endIndex - lastIndex, 599 maxLength - lineLength - 1); 600 int counter = 0; 601 while (counter < maxBreak) { 602 if (Character.isWhitespace(chars[counter + 603 lastIndex])) { 604 breakPoint = counter; 605 } 606 counter++; 607 } 608 if (breakPoint != -1) { 609 // Found a place to break at. 610 breakPoint += lastIndex + 1; 611 output(chars, lastIndex, breakPoint - lastIndex); 612 lastIndex = breakPoint; 613 needsNewline = true; 614 } 615 else { 616 // No where good to break. 617 618 // find the next whitespace, or write out the 619 // whole string. 620 // maxBreak will be negative if current line too 621 // long. 622 counter = Math.max(0, maxBreak); 623 maxBreak = endIndex - lastIndex; 624 while (counter < maxBreak) { 625 if (Character.isWhitespace(chars[counter + 626 lastIndex])) { 627 breakPoint = counter; 628 break; 629 } 630 counter++; 631 } 632 if (breakPoint == -1) { 633 output(chars, lastIndex, endIndex - lastIndex); 634 breakPoint = endIndex; 635 } 636 else { 637 breakPoint += lastIndex; 638 if (chars[breakPoint] == NEWLINE) { 639 output(chars, lastIndex, breakPoint++ - 640 lastIndex); 641 forceNewLine = true; 642 } 643 else { 644 output(chars, lastIndex, ++breakPoint - 645 lastIndex); 646 needsNewline = true; 647 } 648 } 649 lastIndex = breakPoint; 650 } 651 } 652 if (forceNewLine || needsNewline || lastIndex < endIndex) { 653 writeLineSeparator(); 654 if (lastIndex < endIndex || !forceNewLine) { 655 indent(); 656 } 657 } 658 } 659 } 660 } 661 662 /** 663 * Writes out the set of attributes as " <name>=<value>" 664 * pairs. It throws an IOException when encountered. 665 * 666 * @param attr an AttributeSet. 667 * @exception IOException on any I/O error 668 */ 669 protected void writeAttributes(AttributeSet attr) throws IOException { 670 671 Enumeration<?> names = attr.getAttributeNames(); 672 while (names.hasMoreElements()) { 673 Object name = names.nextElement(); 674 write(" " + name + "=" + attr.getAttribute(name)); 675 } 676 } 677 678 /** 679 * The last stop in writing out content. All the write methods eventually 680 * make it to this method, which invokes <code>write</code> on the 681 * Writer. 682 * <p>This method also updates the line length based on 683 * <code>length</code>. If this is invoked to output a newline, the 684 * current line length will need to be reset as will no longer be 685 * valid. If it is up to the caller to do this. Use 686 * <code>writeLineSeparator</code> to write out a newline, which will 687 * property update the current line length. 688 * 689 * @since 1.3 690 */ 691 protected void output(char[] content, int start, int length) 692 throws IOException { 693 getWriter().write(content, start, length); 694 setCurrentLineLength(getCurrentLineLength() + length); 695 } 696 697 /** 698 * Support method to locate an occurrence of a particular character. 699 */ 700 private int indexOf(char[] chars, char sChar, int startIndex, 701 int endIndex) { 702 while(startIndex < endIndex) { 703 if (chars[startIndex] == sChar) { 704 return startIndex; 705 } 706 startIndex++; 707 } 708 return -1; 709 } 710 }