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 */ AbstractWriter(Writer w, Document doc)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 */ AbstractWriter(Writer w, Document doc, int pos, int len)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 = null; 145 try { 146 newline = System.getProperty("line.separator"); 147 } catch (SecurityException se) {} 148 if (newline == null) { 149 // Should not get here, but if we do it means we could not 150 // find a newline string, use \n in this case. 151 newline = "\n"; 152 } 153 setLineSeparator(newline); 154 } 155 canWrapLines = true; 156 } 157 158 /** 159 * Creates a new AbstractWriter. 160 * Initializes the ElementIterator with the 161 * element passed in. 162 * 163 * @param w a Writer 164 * @param root an Element 165 */ AbstractWriter(Writer w, Element root)166 protected AbstractWriter(Writer w, Element root) { 167 this(w, root, 0, root.getEndOffset()); 168 } 169 170 /** 171 * Creates a new AbstractWriter. 172 * Initializes the ElementIterator with the 173 * element passed in. 174 * 175 * @param w a Writer 176 * @param root an Element 177 * @param pos The location in the document to fetch the 178 * content. 179 * @param len The amount to write out. 180 */ AbstractWriter(Writer w, Element root, int pos, int len)181 protected AbstractWriter(Writer w, Element root, int pos, int len) { 182 this.doc = root.getDocument(); 183 it = new ElementIterator(root); 184 out = w; 185 startOffset = pos; 186 endOffset = pos + len; 187 canWrapLines = true; 188 } 189 190 /** 191 * Returns the first offset to be output. 192 * 193 * @since 1.3 194 */ getStartOffset()195 public int getStartOffset() { 196 return startOffset; 197 } 198 199 /** 200 * Returns the last offset to be output. 201 * 202 * @since 1.3 203 */ getEndOffset()204 public int getEndOffset() { 205 return endOffset; 206 } 207 208 /** 209 * Fetches the ElementIterator. 210 * 211 * @return the ElementIterator. 212 */ getElementIterator()213 protected ElementIterator getElementIterator() { 214 return it; 215 } 216 217 /** 218 * Returns the Writer that is used to output the content. 219 * 220 * @since 1.3 221 */ getWriter()222 protected Writer getWriter() { 223 return out; 224 } 225 226 /** 227 * Fetches the document. 228 * 229 * @return the Document. 230 */ getDocument()231 protected Document getDocument() { 232 return doc; 233 } 234 235 /** 236 * This method determines whether the current element 237 * is in the range specified. When no range is specified, 238 * the range is initialized to be the entire document. 239 * inRange() returns true if the range specified intersects 240 * with the element's range. 241 * 242 * @param next an Element. 243 * @return boolean that indicates whether the element 244 * is in the range. 245 */ inRange(Element next)246 protected boolean inRange(Element next) { 247 int startOffset = getStartOffset(); 248 int endOffset = getEndOffset(); 249 if ((next.getStartOffset() >= startOffset && 250 next.getStartOffset() < endOffset) || 251 (startOffset >= next.getStartOffset() && 252 startOffset < next.getEndOffset())) { 253 return true; 254 } 255 return false; 256 } 257 258 /** 259 * This abstract method needs to be implemented 260 * by subclasses. Its responsibility is to 261 * iterate over the elements and use the write() 262 * methods to generate output in the desired format. 263 */ write()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 */ getText(Element elem)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 */ text(Element elem)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 */ setLineLength(int l)314 protected void setLineLength(int l) { 315 maxLineLength = l; 316 } 317 318 /** 319 * Returns the maximum line length. 320 * 321 * @since 1.3 322 */ getLineLength()323 protected int getLineLength() { 324 return maxLineLength; 325 } 326 327 /** 328 * Sets the current line length. 329 * 330 * @since 1.3 331 */ setCurrentLineLength(int length)332 protected void setCurrentLineLength(int length) { 333 currLength = length; 334 isLineEmpty = (currLength == 0); 335 } 336 337 /** 338 * Returns the current line length. 339 * 340 * @since 1.3 341 */ getCurrentLineLength()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 * 351 * @since 1.3 352 */ isLineEmpty()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 * 362 * @since 1.3 363 */ setCanWrapLines(boolean newValue)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 * 372 * @since 1.3 373 */ getCanWrapLines()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 */ setIndentSpace(int space)385 protected void setIndentSpace(int space) { 386 indentSpace = space; 387 } 388 389 /** 390 * Returns the amount of space to indent. 391 * 392 * @since 1.3 393 */ getIndentSpace()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 * 403 * @since 1.3 404 */ setLineSeparator(String value)405 public void setLineSeparator(String value) { 406 lineSeparator = value; 407 } 408 409 /** 410 * Returns the string used to represent newlines. 411 * 412 * @since 1.3 413 */ getLineSeparator()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 */ incrIndent()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 */ decrIndent()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 * 453 * @since 1.3 454 */ getIndentLevel()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 */ indent()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 */ write(char ch)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 */ write(String content)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 * 521 * @since 1.3 522 */ writeLineSeparator()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 * @since 1.3 546 */ write(char[] chars, int startIndex, int length)547 protected void write(char[] chars, int startIndex, int length) 548 throws IOException { 549 if (!getCanWrapLines()) { 550 // We can not break string, just track if a newline 551 // is in it. 552 int lastIndex = startIndex; 553 int endIndex = startIndex + length; 554 int newlineIndex = indexOf(chars, NEWLINE, startIndex, endIndex); 555 while (newlineIndex != -1) { 556 if (newlineIndex > lastIndex) { 557 output(chars, lastIndex, newlineIndex - lastIndex); 558 } 559 writeLineSeparator(); 560 lastIndex = newlineIndex + 1; 561 newlineIndex = indexOf(chars, '\n', lastIndex, endIndex); 562 } 563 if (lastIndex < endIndex) { 564 output(chars, lastIndex, endIndex - lastIndex); 565 } 566 } 567 else { 568 // We can break chars if the length exceeds maxLength. 569 int lastIndex = startIndex; 570 int endIndex = startIndex + length; 571 int lineLength = getCurrentLineLength(); 572 int maxLength = getLineLength(); 573 574 while (lastIndex < endIndex) { 575 int newlineIndex = indexOf(chars, NEWLINE, lastIndex, 576 endIndex); 577 boolean needsNewline = false; 578 boolean forceNewLine = false; 579 580 lineLength = getCurrentLineLength(); 581 if (newlineIndex != -1 && (lineLength + 582 (newlineIndex - lastIndex)) < maxLength) { 583 if (newlineIndex > lastIndex) { 584 output(chars, lastIndex, newlineIndex - lastIndex); 585 } 586 lastIndex = newlineIndex + 1; 587 forceNewLine = true; 588 } 589 else if (newlineIndex == -1 && (lineLength + 590 (endIndex - lastIndex)) < maxLength) { 591 if (endIndex > lastIndex) { 592 output(chars, lastIndex, endIndex - lastIndex); 593 } 594 lastIndex = endIndex; 595 } 596 else { 597 // Need to break chars, find a place to split chars at, 598 // from lastIndex to endIndex, 599 // or maxLength - lineLength whichever is smaller 600 int breakPoint = -1; 601 int maxBreak = Math.min(endIndex - lastIndex, 602 maxLength - lineLength - 1); 603 int counter = 0; 604 while (counter < maxBreak) { 605 if (Character.isWhitespace(chars[counter + 606 lastIndex])) { 607 breakPoint = counter; 608 } 609 counter++; 610 } 611 if (breakPoint != -1) { 612 // Found a place to break at. 613 breakPoint += lastIndex + 1; 614 output(chars, lastIndex, breakPoint - lastIndex); 615 lastIndex = breakPoint; 616 needsNewline = true; 617 } 618 else { 619 // No where good to break. 620 621 // find the next whitespace, or write out the 622 // whole string. 623 // maxBreak will be negative if current line too 624 // long. 625 counter = Math.max(0, maxBreak); 626 maxBreak = endIndex - lastIndex; 627 while (counter < maxBreak) { 628 if (Character.isWhitespace(chars[counter + 629 lastIndex])) { 630 breakPoint = counter; 631 break; 632 } 633 counter++; 634 } 635 if (breakPoint == -1) { 636 output(chars, lastIndex, endIndex - lastIndex); 637 breakPoint = endIndex; 638 } 639 else { 640 breakPoint += lastIndex; 641 if (chars[breakPoint] == NEWLINE) { 642 output(chars, lastIndex, breakPoint++ - 643 lastIndex); 644 forceNewLine = true; 645 } 646 else { 647 output(chars, lastIndex, ++breakPoint - 648 lastIndex); 649 needsNewline = true; 650 } 651 } 652 lastIndex = breakPoint; 653 } 654 } 655 if (forceNewLine || needsNewline || lastIndex < endIndex) { 656 writeLineSeparator(); 657 if (lastIndex < endIndex || !forceNewLine) { 658 indent(); 659 } 660 } 661 } 662 } 663 } 664 665 /** 666 * Writes out the set of attributes as " <name>=<value>" 667 * pairs. It throws an IOException when encountered. 668 * 669 * @param attr an AttributeSet. 670 * @exception IOException on any I/O error 671 */ writeAttributes(AttributeSet attr)672 protected void writeAttributes(AttributeSet attr) throws IOException { 673 674 Enumeration names = attr.getAttributeNames(); 675 while (names.hasMoreElements()) { 676 Object name = names.nextElement(); 677 write(" " + name + "=" + attr.getAttribute(name)); 678 } 679 } 680 681 /** 682 * The last stop in writing out content. All the write methods eventually 683 * make it to this method, which invokes <code>write</code> on the 684 * Writer. 685 * <p>This method also updates the line length based on 686 * <code>length</code>. If this is invoked to output a newline, the 687 * current line length will need to be reset as will no longer be 688 * valid. If it is up to the caller to do this. Use 689 * <code>writeLineSeparator</code> to write out a newline, which will 690 * property update the current line length. 691 * 692 * @since 1.3 693 */ output(char[] content, int start, int length)694 protected void output(char[] content, int start, int length) 695 throws IOException { 696 getWriter().write(content, start, length); 697 setCurrentLineLength(getCurrentLineLength() + length); 698 } 699 700 /** 701 * Support method to locate an occurrence of a particular character. 702 */ indexOf(char[] chars, char sChar, int startIndex, int endIndex)703 private int indexOf(char[] chars, char sChar, int startIndex, 704 int endIndex) { 705 while(startIndex < endIndex) { 706 if (chars[startIndex] == sChar) { 707 return startIndex; 708 } 709 startIndex++; 710 } 711 return -1; 712 } 713 } 714