/*
* Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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 = null;
try {
newline =
System.
getProperty("line.separator");
} catch (
SecurityException se) {}
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 >
* 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 " <name>=<value>"
* 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;
}
}