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 */ AbstractWriter(Writer writer, Document doc)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 */ AbstractWriter(Writer writer, Document doc, int pos, int len)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 */ AbstractWriter(Writer writer, Element elt)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 */ AbstractWriter(Writer writer, Element elt, int pos, int len)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 */ getElementIterator()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 */ getWriter()154 protected Writer getWriter() 155 { 156 return writer; 157 } 158 159 /** 160 * Return this writer's Document. 161 */ getDocument()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 */ write()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 */ getText(Element elt)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 */ output(char[] data, int start, int len)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 */ writeLineSeparator()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 */ write(char ch)219 protected void write(char ch) throws IOException 220 { 221 write(new char[] { ch }, 0, 1); 222 } 223 224 /** 225 * Write a String. 226 */ write(String s)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 */ write(char[] data, int start, int len)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 */ indent()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 */ getStartOffset()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 */ getEndOffset()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 */ inRange(Element elt)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 */ text(Element elt)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 */ setLineLength(int maxLineLength)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 */ getLineLength()351 protected int getLineLength() 352 { 353 return maxLineLength; 354 } 355 356 /** 357 * Set the current line length. 358 * @since 1.3 359 */ setCurrentLineLength(int lineLength)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 */ getCurrentLineLength()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 */ isLineEmpty()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 */ setCanWrapLines(boolean canWrapLines)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 */ getCanWrapLines()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 */ setIndentSpace(int indentSpace)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 */ getIndentSpace()417 protected int getIndentSpace() 418 { 419 return indentSpace; 420 } 421 422 /** 423 * Set the current line separator. 424 * @since 1.3 425 */ setLineSeparator(String lineSeparator)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 */ getLineSeparator()436 public String getLineSeparator() 437 { 438 return lineSeparator; 439 } 440 441 /** 442 * Increment the indent level. 443 */ incrIndent()444 protected void incrIndent() 445 { 446 ++indentLevel; 447 } 448 449 /** 450 * Decrement the indent level. 451 */ decrIndent()452 protected void decrIndent() 453 { 454 --indentLevel; 455 } 456 457 /** 458 * Return the current indent level. 459 * @since 1.3 460 */ getIndentLevel()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 */ writeAttributes(AttributeSet attrs)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 } 482