1/*
2 * Copyright (c) 1998, 2015, 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
26package javax.swing.text;
27
28import java.io.Writer;
29import java.io.IOException;
30import 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
42public 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    protected abstract 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 &gt;
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 " &lt;name&gt;=&lt;value&gt;"
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}
722