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 &gt;
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 " &lt;name&gt;=&lt;value&gt;"
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