jdk/src/java.desktop/share/classes/javax/swing/text/AbstractWriter.java
changeset 25859 3317bb8137f4
parent 25193 187a455af8f8
child 30462 507bcb03c954
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/swing/text/AbstractWriter.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,710 @@
+/*
+ * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.swing.text;
+
+import java.io.Writer;
+import java.io.IOException;
+import java.util.Enumeration;
+
+/**
+ * AbstractWriter is an abstract class that actually
+ * does the work of writing out the element tree
+ * including the attributes.  In terms of how much is
+ * written out per line, the writer defaults to 100.
+ * But this value can be set by subclasses.
+ *
+ * @author Sunita Mani
+ */
+
+public abstract class AbstractWriter {
+
+    private ElementIterator it;
+    private Writer out;
+    private int indentLevel = 0;
+    private int indentSpace = 2;
+    private Document doc = null;
+    private int maxLineLength = 100;
+    private int currLength = 0;
+    private int startOffset = 0;
+    private int endOffset = 0;
+    // If (indentLevel * indentSpace) becomes >= maxLineLength, this will
+    // get incremened instead of indentLevel to avoid indenting going greater
+    // than line length.
+    private int offsetIndent = 0;
+
+    /**
+     * String used for end of line. If the Document has the property
+     * EndOfLineStringProperty, it will be used for newlines. Otherwise
+     * the System property line.separator will be used. The line separator
+     * can also be set.
+     */
+    private String lineSeparator;
+
+    /**
+     * True indicates that when writing, the line can be split, false
+     * indicates that even if the line is > than max line length it should
+     * not be split.
+     */
+    private boolean canWrapLines;
+
+    /**
+     * True while the current line is empty. This will remain true after
+     * indenting.
+     */
+    private boolean isLineEmpty;
+
+    /**
+     * Used when indenting. Will contain the spaces.
+     */
+    private char[] indentChars;
+
+    /**
+     * Used when writing out a string.
+     */
+    private char[] tempChars;
+
+    /**
+     * This is used in <code>writeLineSeparator</code> instead of
+     * tempChars. If tempChars were used it would mean write couldn't invoke
+     * <code>writeLineSeparator</code> as it might have been passed
+     * tempChars.
+     */
+    private char[] newlineChars;
+
+    /**
+     * Used for writing text.
+     */
+    private Segment segment;
+
+    /**
+     * How the text packages models newlines.
+     * @see #getLineSeparator
+     */
+    protected static final char NEWLINE = '\n';
+
+
+    /**
+     * Creates a new AbstractWriter.
+     * Initializes the ElementIterator with the default
+     * root of the document.
+     *
+     * @param w a Writer.
+     * @param doc a Document
+     */
+    protected AbstractWriter(Writer w, Document doc) {
+        this(w, doc, 0, doc.getLength());
+    }
+
+    /**
+     * Creates a new AbstractWriter.
+     * Initializes the ElementIterator with the
+     * element passed in.
+     *
+     * @param w a Writer
+     * @param doc an Element
+     * @param pos The location in the document to fetch the
+     *   content.
+     * @param len The amount to write out.
+     */
+    protected AbstractWriter(Writer w, Document doc, int pos, int len) {
+        this.doc = doc;
+        it = new ElementIterator(doc.getDefaultRootElement());
+        out = w;
+        startOffset = pos;
+        endOffset = pos + len;
+        Object docNewline = doc.getProperty(DefaultEditorKit.
+                                       EndOfLineStringProperty);
+        if (docNewline instanceof String) {
+            setLineSeparator((String)docNewline);
+        }
+        else {
+            String newline = System.lineSeparator();
+            if (newline == null) {
+                // Should not get here, but if we do it means we could not
+                // find a newline string, use \n in this case.
+                newline = "\n";
+            }
+            setLineSeparator(newline);
+        }
+        canWrapLines = true;
+    }
+
+    /**
+     * Creates a new AbstractWriter.
+     * Initializes the ElementIterator with the
+     * element passed in.
+     *
+     * @param w a Writer
+     * @param root an Element
+     */
+    protected AbstractWriter(Writer w, Element root) {
+        this(w, root, 0, root.getEndOffset());
+    }
+
+    /**
+     * Creates a new AbstractWriter.
+     * Initializes the ElementIterator with the
+     * element passed in.
+     *
+     * @param w a Writer
+     * @param root an Element
+     * @param pos The location in the document to fetch the
+     *   content.
+     * @param len The amount to write out.
+     */
+    protected AbstractWriter(Writer w, Element root, int pos, int len) {
+        this.doc = root.getDocument();
+        it = new ElementIterator(root);
+        out = w;
+        startOffset = pos;
+        endOffset = pos + len;
+        canWrapLines = true;
+    }
+
+    /**
+     * Returns the first offset to be output.
+     *
+     * @since 1.3
+     */
+    public int getStartOffset() {
+        return startOffset;
+    }
+
+    /**
+     * Returns the last offset to be output.
+     *
+     * @since 1.3
+     */
+    public int getEndOffset() {
+        return endOffset;
+    }
+
+    /**
+     * Fetches the ElementIterator.
+     *
+     * @return the ElementIterator.
+     */
+    protected ElementIterator getElementIterator() {
+        return it;
+    }
+
+    /**
+     * Returns the Writer that is used to output the content.
+     *
+     * @since 1.3
+     */
+    protected Writer getWriter() {
+        return out;
+    }
+
+    /**
+     * Fetches the document.
+     *
+     * @return the Document.
+     */
+    protected Document getDocument() {
+        return doc;
+    }
+
+    /**
+     * This method determines whether the current element
+     * is in the range specified.  When no range is specified,
+     * the range is initialized to be the entire document.
+     * inRange() returns true if the range specified intersects
+     * with the element's range.
+     *
+     * @param  next an Element.
+     * @return boolean that indicates whether the element
+     *         is in the range.
+     */
+    protected boolean inRange(Element next) {
+        int startOffset = getStartOffset();
+        int endOffset = getEndOffset();
+        if ((next.getStartOffset() >= startOffset &&
+             next.getStartOffset()  < endOffset) ||
+            (startOffset >= next.getStartOffset() &&
+             startOffset < next.getEndOffset())) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * This abstract method needs to be implemented
+     * by subclasses.  Its responsibility is to
+     * iterate over the elements and use the write()
+     * methods to generate output in the desired format.
+     */
+    abstract protected void write() throws IOException, BadLocationException;
+
+    /**
+     * Returns the text associated with the element.
+     * The assumption here is that the element is a
+     * leaf element.  Throws a BadLocationException
+     * when encountered.
+     *
+     * @param     elem an <code>Element</code>
+     * @exception BadLocationException if pos represents an invalid
+     *            location within the document
+     * @return    the text as a <code>String</code>
+     */
+    protected String getText(Element elem) throws BadLocationException {
+        return doc.getText(elem.getStartOffset(),
+                           elem.getEndOffset() - elem.getStartOffset());
+    }
+
+
+    /**
+     * Writes out text.  If a range is specified when the constructor
+     * is invoked, then only the appropriate range of text is written
+     * out.
+     *
+     * @param     elem an Element.
+     * @exception IOException on any I/O error
+     * @exception BadLocationException if pos represents an invalid
+     *            location within the document.
+     */
+    protected void text(Element elem) throws BadLocationException,
+                                             IOException {
+        int start = Math.max(getStartOffset(), elem.getStartOffset());
+        int end = Math.min(getEndOffset(), elem.getEndOffset());
+        if (start < end) {
+            if (segment == null) {
+                segment = new Segment();
+            }
+            getDocument().getText(start, end - start, segment);
+            if (segment.count > 0) {
+                write(segment.array, segment.offset, segment.count);
+            }
+        }
+    }
+
+    /**
+     * Enables subclasses to set the number of characters they
+     * want written per line.   The default is 100.
+     *
+     * @param l the maximum line length.
+     */
+    protected void setLineLength(int l) {
+        maxLineLength = l;
+    }
+
+    /**
+     * Returns the maximum line length.
+     *
+     * @since 1.3
+     */
+    protected int getLineLength() {
+        return maxLineLength;
+    }
+
+    /**
+     * Sets the current line length.
+     *
+     * @since 1.3
+     */
+    protected void setCurrentLineLength(int length) {
+        currLength = length;
+        isLineEmpty = (currLength == 0);
+    }
+
+    /**
+     * Returns the current line length.
+     *
+     * @since 1.3
+     */
+    protected int getCurrentLineLength() {
+        return currLength;
+    }
+
+    /**
+     * Returns true if the current line should be considered empty. This
+     * is true when <code>getCurrentLineLength</code> == 0 ||
+     * <code>indent</code> has been invoked on an empty line.
+     *
+     * @since 1.3
+     */
+    protected boolean isLineEmpty() {
+        return isLineEmpty;
+    }
+
+    /**
+     * Sets whether or not lines can be wrapped. This can be toggled
+     * during the writing of lines. For example, outputting HTML might
+     * set this to false when outputting a quoted string.
+     *
+     * @since 1.3
+     */
+    protected void setCanWrapLines(boolean newValue) {
+        canWrapLines = newValue;
+    }
+
+    /**
+     * Returns whether or not the lines can be wrapped. If this is false
+     * no lineSeparator's will be output.
+     *
+     * @since 1.3
+     */
+    protected boolean getCanWrapLines() {
+        return canWrapLines;
+    }
+
+    /**
+     * Enables subclasses to specify how many spaces an indent
+     * maps to. When indentation takes place, the indent level
+     * is multiplied by this mapping.  The default is 2.
+     *
+     * @param space an int representing the space to indent mapping.
+     */
+    protected void setIndentSpace(int space) {
+        indentSpace = space;
+    }
+
+    /**
+     * Returns the amount of space to indent.
+     *
+     * @since 1.3
+     */
+    protected int getIndentSpace() {
+        return indentSpace;
+    }
+
+    /**
+     * Sets the String used to represent newlines. This is initialized
+     * in the constructor from either the Document, or the System property
+     * line.separator.
+     *
+     * @since 1.3
+     */
+    public void setLineSeparator(String value) {
+        lineSeparator = value;
+    }
+
+    /**
+     * Returns the string used to represent newlines.
+     *
+     * @since 1.3
+     */
+    public String getLineSeparator() {
+        return lineSeparator;
+    }
+
+    /**
+     * Increments the indent level. If indenting would cause
+     * <code>getIndentSpace()</code> *<code>getIndentLevel()</code> to be &gt;
+     * than <code>getLineLength()</code> this will not cause an indent.
+     */
+    protected void incrIndent() {
+        // Only increment to a certain point.
+        if (offsetIndent > 0) {
+            offsetIndent++;
+        }
+        else {
+            if (++indentLevel * getIndentSpace() >= getLineLength()) {
+                offsetIndent++;
+                --indentLevel;
+            }
+        }
+    }
+
+    /**
+     * Decrements the indent level.
+     */
+    protected void decrIndent() {
+        if (offsetIndent > 0) {
+            --offsetIndent;
+        }
+        else {
+            indentLevel--;
+        }
+    }
+
+    /**
+     * Returns the current indentation level. That is, the number of times
+     * <code>incrIndent</code> has been invoked minus the number of times
+     * <code>decrIndent</code> has been invoked.
+     *
+     * @since 1.3
+     */
+    protected int getIndentLevel() {
+        return indentLevel;
+    }
+
+    /**
+     * Does indentation. The number of spaces written
+     * out is indent level times the space to map mapping. If the current
+     * line is empty, this will not make it so that the current line is
+     * still considered empty.
+     *
+     * @exception IOException on any I/O error
+     */
+    protected void indent() throws IOException {
+        int max = getIndentLevel() * getIndentSpace();
+        if (indentChars == null || max > indentChars.length) {
+            indentChars = new char[max];
+            for (int counter = 0; counter < max; counter++) {
+                indentChars[counter] = ' ';
+            }
+        }
+        int length = getCurrentLineLength();
+        boolean wasEmpty = isLineEmpty();
+        output(indentChars, 0, max);
+        if (wasEmpty && length == 0) {
+            isLineEmpty = true;
+        }
+    }
+
+    /**
+     * Writes out a character. This is implemented to invoke
+     * the <code>write</code> method that takes a char[].
+     *
+     * @param     ch a char.
+     * @exception IOException on any I/O error
+     */
+    protected void write(char ch) throws IOException {
+        if (tempChars == null) {
+            tempChars = new char[128];
+        }
+        tempChars[0] = ch;
+        write(tempChars, 0, 1);
+    }
+
+    /**
+     * Writes out a string. This is implemented to invoke the
+     * <code>write</code> method that takes a char[].
+     *
+     * @param     content a String.
+     * @exception IOException on any I/O error
+     */
+    protected void write(String content) throws IOException {
+        if (content == null) {
+            return;
+        }
+        int size = content.length();
+        if (tempChars == null || tempChars.length < size) {
+            tempChars = new char[size];
+        }
+        content.getChars(0, size, tempChars, 0);
+        write(tempChars, 0, size);
+    }
+
+    /**
+     * Writes the line separator. This invokes <code>output</code> directly
+     * as well as setting the <code>lineLength</code> to 0.
+     *
+     * @since 1.3
+     */
+    protected void writeLineSeparator() throws IOException {
+        String newline = getLineSeparator();
+        int length = newline.length();
+        if (newlineChars == null || newlineChars.length < length) {
+            newlineChars = new char[length];
+        }
+        newline.getChars(0, length, newlineChars, 0);
+        output(newlineChars, 0, length);
+        setCurrentLineLength(0);
+    }
+
+    /**
+     * All write methods call into this one. If <code>getCanWrapLines()</code>
+     * returns false, this will call <code>output</code> with each sequence
+     * of <code>chars</code> that doesn't contain a NEWLINE, followed
+     * by a call to <code>writeLineSeparator</code>. On the other hand,
+     * if <code>getCanWrapLines()</code> returns true, this will split the
+     * string, as necessary, so <code>getLineLength</code> is honored.
+     * The only exception is if the current string contains no whitespace,
+     * and won't fit in which case the line length will exceed
+     * <code>getLineLength</code>.
+     *
+     * @since 1.3
+     */
+    protected void write(char[] chars, int startIndex, int length)
+                   throws IOException {
+        if (!getCanWrapLines()) {
+            // We can not break string, just track if a newline
+            // is in it.
+            int lastIndex = startIndex;
+            int endIndex = startIndex + length;
+            int newlineIndex = indexOf(chars, NEWLINE, startIndex, endIndex);
+            while (newlineIndex != -1) {
+                if (newlineIndex > lastIndex) {
+                    output(chars, lastIndex, newlineIndex - lastIndex);
+                }
+                writeLineSeparator();
+                lastIndex = newlineIndex + 1;
+                newlineIndex = indexOf(chars, '\n', lastIndex, endIndex);
+            }
+            if (lastIndex < endIndex) {
+                output(chars, lastIndex, endIndex - lastIndex);
+            }
+        }
+        else {
+            // We can break chars if the length exceeds maxLength.
+            int lastIndex = startIndex;
+            int endIndex = startIndex + length;
+            int lineLength = getCurrentLineLength();
+            int maxLength = getLineLength();
+
+            while (lastIndex < endIndex) {
+                int newlineIndex = indexOf(chars, NEWLINE, lastIndex,
+                                           endIndex);
+                boolean needsNewline = false;
+                boolean forceNewLine = false;
+
+                lineLength = getCurrentLineLength();
+                if (newlineIndex != -1 && (lineLength +
+                              (newlineIndex - lastIndex)) < maxLength) {
+                    if (newlineIndex > lastIndex) {
+                        output(chars, lastIndex, newlineIndex - lastIndex);
+                    }
+                    lastIndex = newlineIndex + 1;
+                    forceNewLine = true;
+                }
+                else if (newlineIndex == -1 && (lineLength +
+                                (endIndex - lastIndex)) < maxLength) {
+                    if (endIndex > lastIndex) {
+                        output(chars, lastIndex, endIndex - lastIndex);
+                    }
+                    lastIndex = endIndex;
+                }
+                else {
+                    // Need to break chars, find a place to split chars at,
+                    // from lastIndex to endIndex,
+                    // or maxLength - lineLength whichever is smaller
+                    int breakPoint = -1;
+                    int maxBreak = Math.min(endIndex - lastIndex,
+                                            maxLength - lineLength - 1);
+                    int counter = 0;
+                    while (counter < maxBreak) {
+                        if (Character.isWhitespace(chars[counter +
+                                                        lastIndex])) {
+                            breakPoint = counter;
+                        }
+                        counter++;
+                    }
+                    if (breakPoint != -1) {
+                        // Found a place to break at.
+                        breakPoint += lastIndex + 1;
+                        output(chars, lastIndex, breakPoint - lastIndex);
+                        lastIndex = breakPoint;
+                        needsNewline = true;
+                    }
+                    else {
+                        // No where good to break.
+
+                        // find the next whitespace, or write out the
+                        // whole string.
+                            // maxBreak will be negative if current line too
+                            // long.
+                            counter = Math.max(0, maxBreak);
+                            maxBreak = endIndex - lastIndex;
+                            while (counter < maxBreak) {
+                                if (Character.isWhitespace(chars[counter +
+                                                                lastIndex])) {
+                                    breakPoint = counter;
+                                    break;
+                                }
+                                counter++;
+                            }
+                            if (breakPoint == -1) {
+                                output(chars, lastIndex, endIndex - lastIndex);
+                                breakPoint = endIndex;
+                            }
+                            else {
+                                breakPoint += lastIndex;
+                                if (chars[breakPoint] == NEWLINE) {
+                                    output(chars, lastIndex, breakPoint++ -
+                                           lastIndex);
+                                forceNewLine = true;
+                                }
+                                else {
+                                    output(chars, lastIndex, ++breakPoint -
+                                              lastIndex);
+                                needsNewline = true;
+                                }
+                            }
+                            lastIndex = breakPoint;
+                        }
+                    }
+                if (forceNewLine || needsNewline || lastIndex < endIndex) {
+                    writeLineSeparator();
+                    if (lastIndex < endIndex || !forceNewLine) {
+                        indent();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Writes out the set of attributes as " &lt;name&gt;=&lt;value&gt;"
+     * pairs. It throws an IOException when encountered.
+     *
+     * @param     attr an AttributeSet.
+     * @exception IOException on any I/O error
+     */
+    protected void writeAttributes(AttributeSet attr) throws IOException {
+
+        Enumeration<?> names = attr.getAttributeNames();
+        while (names.hasMoreElements()) {
+            Object name = names.nextElement();
+            write(" " + name + "=" + attr.getAttribute(name));
+        }
+    }
+
+    /**
+     * The last stop in writing out content. All the write methods eventually
+     * make it to this method, which invokes <code>write</code> on the
+     * Writer.
+     * <p>This method also updates the line length based on
+     * <code>length</code>. If this is invoked to output a newline, the
+     * current line length will need to be reset as will no longer be
+     * valid. If it is up to the caller to do this. Use
+     * <code>writeLineSeparator</code> to write out a newline, which will
+     * property update the current line length.
+     *
+     * @since 1.3
+     */
+    protected void output(char[] content, int start, int length)
+                   throws IOException {
+        getWriter().write(content, start, length);
+        setCurrentLineLength(getCurrentLineLength() + length);
+    }
+
+    /**
+     * Support method to locate an occurrence of a particular character.
+     */
+    private int indexOf(char[] chars, char sChar, int startIndex,
+                        int endIndex) {
+        while(startIndex < endIndex) {
+            if (chars[startIndex] == sChar) {
+                return startIndex;
+            }
+            startIndex++;
+        }
+        return -1;
+    }
+}