Frames | No Frames |
1: /* AbstractWriter.java -- 2: Copyright (C) 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package javax.swing.text; 39: 40: import java.io.IOException; 41: import java.io.Writer; 42: import java.util.Arrays; 43: import java.util.Enumeration; 44: 45: /** 46: * This is an abstract base class for writing Document instances to a 47: * Writer. A concrete subclass must implement a method to iterate 48: * over the Elements of the Document and correctly format them. 49: */ 50: public abstract class AbstractWriter 51: { 52: /** 53: * The default line separator character. 54: * @specnote although this is a constant, it is not static in the JDK 55: */ 56: protected static final char NEWLINE = '\n'; 57: 58: // Where we write. 59: private Writer writer; 60: // How we iterate over the document. 61: private ElementIterator iter; 62: // The document over which we iterate. 63: private Document document; 64: // Maximum number of characters per line. 65: private int maxLineLength = 100; 66: // Number of characters we have currently written. 67: private int lineLength; 68: // True if we can apply line wrapping. 69: private boolean canWrapLines; // FIXME default? 70: // The number of spaces per indentation level. 71: private int indentSpace = 2; 72: // The current indentation level. 73: private int indentLevel; 74: // True if we have indented this line. 75: private boolean indented; 76: // Starting offset in document. 77: private int startOffset; 78: // Ending offset in document. 79: private int endOffset; 80: // The line separator string. 81: private String lineSeparator = "" + NEWLINE; 82: // The characters making up the line separator. 83: private char[] lineSeparatorChars = lineSeparator.toCharArray(); 84: 85: /** 86: * Create a new AbstractWriter with the indicated Writer and 87: * Document. The full range of the Document will be used. The 88: * internal ElementIterator will be initialized with the Document's 89: * root node. 90: */ 91: protected AbstractWriter(Writer writer, Document doc) 92: { 93: this.writer = writer; 94: this.iter = new ElementIterator(doc); 95: this.document = doc; 96: this.startOffset = 0; 97: this.endOffset = doc.getLength(); 98: } 99: 100: /** 101: * Create a new AbstractWriter with the indicated Writer and 102: * Document. The full range of the Document will be used. The 103: * internal ElementIterator will be initialized with the Document's 104: * root node. 105: */ 106: protected AbstractWriter(Writer writer, Document doc, int pos, int len) 107: { 108: this.writer = writer; 109: this.iter = new ElementIterator(doc); 110: this.document = doc; 111: this.startOffset = pos; 112: this.endOffset = pos + len; 113: } 114: 115: /** 116: * Create a new AbstractWriter with the indicated Writer and 117: * Element. The full range of the Element will be used. 118: */ 119: protected AbstractWriter(Writer writer, Element elt) 120: { 121: this.writer = writer; 122: this.iter = new ElementIterator(elt); 123: this.document = elt.getDocument(); 124: this.startOffset = elt.getStartOffset(); 125: this.endOffset = elt.getEndOffset(); 126: } 127: 128: /** 129: * Create a new AbstractWriter with the indicated Writer and 130: * Element. The full range of the Element will be used. The range 131: * will be limited to the indicated range of the Document. 132: */ 133: protected AbstractWriter(Writer writer, Element elt, int pos, int len) 134: { 135: this.writer = writer; 136: this.iter = new ElementIterator(elt); 137: this.document = elt.getDocument(); 138: this.startOffset = pos; 139: this.endOffset = pos + len; 140: } 141: 142: /** 143: * Return the ElementIterator for this writer. 144: */ 145: protected ElementIterator getElementIterator() 146: { 147: return iter; 148: } 149: 150: /** 151: * Return the Writer to which we are writing. 152: * @since 1.3 153: */ 154: protected Writer getWriter() 155: { 156: return writer; 157: } 158: 159: /** 160: * Return this writer's Document. 161: */ 162: protected Document getDocument() 163: { 164: return document; 165: } 166: 167: /** 168: * This method must be overridden by a concrete subclass. It is 169: * responsible for iterating over the Elements of the Document and 170: * writing them out. 171: */ 172: protected abstract void write() throws IOException, BadLocationException; 173: 174: /** 175: * Return the text of the Document that is associated with the given 176: * Element. If the Element is not a leaf Element, this will throw 177: * BadLocationException. 178: * 179: * @throws BadLocationException if the element is not a leaf 180: */ 181: protected String getText(Element elt) throws BadLocationException 182: { 183: if (! elt.isLeaf()) 184: throw new BadLocationException("Element is not a leaf", 185: elt.getStartOffset()); 186: return document.getText(elt.getStartOffset(), 187: elt.getEndOffset() - elt.getStartOffset()); 188: } 189: 190: /** 191: * This method calls Writer.write on the indicated data, and updates 192: * the current line length. This method does not look for newlines 193: * in the written data; the caller is responsible for that. 194: * 195: * @since 1.3 196: */ 197: protected void output(char[] data, int start, int len) throws IOException 198: { 199: writer.write(data, start, len); 200: lineLength += len; 201: } 202: 203: /** 204: * Write a line separator using the output method, and then reset 205: * the current line length. 206: * 207: * @since 1.3 208: */ 209: protected void writeLineSeparator() throws IOException 210: { 211: output(lineSeparatorChars, 0, lineSeparatorChars.length); 212: lineLength = 0; 213: indented = false; 214: } 215: 216: /** 217: * Write a single character. 218: */ 219: protected void write(char ch) throws IOException 220: { 221: write(new char[] { ch }, 0, 1); 222: } 223: 224: /** 225: * Write a String. 226: */ 227: protected void write(String s) throws IOException 228: { 229: char[] v = s.toCharArray(); 230: write(v, 0, v.length); 231: } 232: 233: /** 234: * Write a character array to the output Writer, properly handling 235: * newlines and, if needed, wrapping lines as they are output. 236: * @since 1.3 237: */ 238: protected void write(char[] data, int start, int len) throws IOException 239: { 240: if (getCanWrapLines()) 241: { 242: // FIXME: should we be handling newlines specially here? 243: for (int i = 0; i < len; ) 244: { 245: int start_i = i; 246: // Find next space. 247: while (i < len && data[start + i] != ' ') 248: ++i; 249: if (i < len && lineLength + i - start_i >= maxLineLength) 250: writeLineSeparator(); 251: else if (i < len) 252: { 253: // Write the trailing space. 254: ++i; 255: } 256: // Write out the text. 257: output(data, start + start_i, start + i - start_i); 258: } 259: } 260: else 261: { 262: int saved_i = start; 263: for (int i = start; i < start + len; ++i) 264: { 265: if (data[i] == NEWLINE) 266: { 267: output(data, saved_i, i - saved_i); 268: writeLineSeparator(); 269: } 270: } 271: if (saved_i < start + len - 1) 272: output(data, saved_i, start + len - saved_i); 273: } 274: } 275: 276: /** 277: * Indent this line by emitting spaces, according to the current 278: * indent level and the current number of spaces per indent. After 279: * this method is called, the current line is no longer considered 280: * to be empty, even if no spaces are actually written. 281: */ 282: protected void indent() throws IOException 283: { 284: int spaces = indentLevel * indentSpace; 285: if (spaces > 0) 286: { 287: char[] v = new char[spaces]; 288: Arrays.fill(v, ' '); 289: write(v, 0, v.length); 290: } 291: indented = true; 292: } 293: 294: /** 295: * Return the index of the Document at which output starts. 296: * @since 1.3 297: */ 298: public int getStartOffset() 299: { 300: return startOffset; 301: } 302: 303: /** 304: * Return the index of the Document at which output ends. 305: * @since 1.3 306: */ 307: public int getEndOffset() 308: { 309: return endOffset; 310: } 311: 312: /** 313: * Return true if the Element's range overlaps our desired output 314: * range; false otherwise. 315: */ 316: protected boolean inRange(Element elt) 317: { 318: int eltStart = elt.getStartOffset(); 319: int eltEnd = elt.getEndOffset(); 320: return ((eltStart >= startOffset && eltStart < endOffset) 321: || (eltEnd >= startOffset && eltEnd < endOffset)); 322: } 323: 324: /** 325: * Output the text of the indicated Element, properly clipping it to 326: * the range of the Document specified when the AbstractWriter was 327: * created. 328: */ 329: protected void text(Element elt) throws BadLocationException, IOException 330: { 331: int eltStart = elt.getStartOffset(); 332: int eltEnd = elt.getEndOffset(); 333: 334: eltStart = Math.max(eltStart, startOffset); 335: eltEnd = Math.min(eltEnd, endOffset); 336: write(document.getText(eltStart, eltEnd)); 337: } 338: 339: /** 340: * Set the maximum line length. 341: */ 342: protected void setLineLength(int maxLineLength) 343: { 344: this.maxLineLength = maxLineLength; 345: } 346: 347: /** 348: * Return the maximum line length. 349: * @since 1.3 350: */ 351: protected int getLineLength() 352: { 353: return maxLineLength; 354: } 355: 356: /** 357: * Set the current line length. 358: * @since 1.3 359: */ 360: protected void setCurrentLineLength(int lineLength) 361: { 362: this.lineLength = lineLength; 363: } 364: 365: /** 366: * Return the current line length. 367: * @since 1.3 368: */ 369: protected int getCurrentLineLength() 370: { 371: return lineLength; 372: } 373: 374: /** 375: * Return true if the line is empty, false otherwise. The line is 376: * empty if nothing has been written since the last newline, and 377: * indent has not been invoked. 378: */ 379: protected boolean isLineEmpty() 380: { 381: return lineLength == 0 && ! indented; 382: } 383: 384: /** 385: * Set the flag indicating whether lines will wrap. This affects 386: * the behavior of write(). 387: * @since 1.3 388: */ 389: protected void setCanWrapLines(boolean canWrapLines) 390: { 391: this.canWrapLines = canWrapLines; 392: } 393: 394: /** 395: * Return true if lines printed via write() will wrap, false 396: * otherwise. 397: * @since 1.3 398: */ 399: protected boolean getCanWrapLines() 400: { 401: return canWrapLines; 402: } 403: 404: /** 405: * Set the number of spaces per indent level. 406: * @since 1.3 407: */ 408: protected void setIndentSpace(int indentSpace) 409: { 410: this.indentSpace = indentSpace; 411: } 412: 413: /** 414: * Return the number of spaces per indent level. 415: * @since 1.3 416: */ 417: protected int getIndentSpace() 418: { 419: return indentSpace; 420: } 421: 422: /** 423: * Set the current line separator. 424: * @since 1.3 425: */ 426: public void setLineSeparator(String lineSeparator) 427: { 428: this.lineSeparator = lineSeparator; 429: this.lineSeparatorChars = lineSeparator.toCharArray(); 430: } 431: 432: /** 433: * Return the current line separator. 434: * @since 1.3 435: */ 436: public String getLineSeparator() 437: { 438: return lineSeparator; 439: } 440: 441: /** 442: * Increment the indent level. 443: */ 444: protected void incrIndent() 445: { 446: ++indentLevel; 447: } 448: 449: /** 450: * Decrement the indent level. 451: */ 452: protected void decrIndent() 453: { 454: --indentLevel; 455: } 456: 457: /** 458: * Return the current indent level. 459: * @since 1.3 460: */ 461: protected int getIndentLevel() 462: { 463: return indentLevel; 464: } 465: 466: /** 467: * Print the given AttributeSet as a sequence of assignment-like 468: * strings, e.g. "key=value". 469: */ 470: protected void writeAttributes(AttributeSet attrs) throws IOException 471: { 472: Enumeration e = attrs.getAttributeNames(); 473: while (e.hasMoreElements()) 474: { 475: Object name = e.nextElement(); 476: Object val = attrs.getAttribute(name); 477: write(name + "=" + val); 478: writeLineSeparator(); 479: } 480: } 481: }