001 /* AbstractWriter.java -- 002 Copyright (C) 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 package javax.swing.text; 039 040 import java.io.IOException; 041 import java.io.Writer; 042 import java.util.Arrays; 043 import java.util.Enumeration; 044 045 /** 046 * This is an abstract base class for writing Document instances to a 047 * Writer. A concrete subclass must implement a method to iterate 048 * over the Elements of the Document and correctly format them. 049 */ 050 public abstract class AbstractWriter 051 { 052 /** 053 * The default line separator character. 054 * @specnote although this is a constant, it is not static in the JDK 055 */ 056 protected static final char NEWLINE = '\n'; 057 058 // Where we write. 059 private Writer writer; 060 // How we iterate over the document. 061 private ElementIterator iter; 062 // The document over which we iterate. 063 private Document document; 064 // Maximum number of characters per line. 065 private int maxLineLength = 100; 066 // Number of characters we have currently written. 067 private int lineLength; 068 // True if we can apply line wrapping. 069 private boolean canWrapLines; // FIXME default? 070 // The number of spaces per indentation level. 071 private int indentSpace = 2; 072 // The current indentation level. 073 private int indentLevel; 074 // True if we have indented this line. 075 private boolean indented; 076 // Starting offset in document. 077 private int startOffset; 078 // Ending offset in document. 079 private int endOffset; 080 // The line separator string. 081 private String lineSeparator = "" + NEWLINE; 082 // The characters making up the line separator. 083 private char[] lineSeparatorChars = lineSeparator.toCharArray(); 084 085 /** 086 * Create a new AbstractWriter with the indicated Writer and 087 * Document. The full range of the Document will be used. The 088 * internal ElementIterator will be initialized with the Document's 089 * root node. 090 */ 091 protected AbstractWriter(Writer writer, Document doc) 092 { 093 this.writer = writer; 094 this.iter = new ElementIterator(doc); 095 this.document = doc; 096 this.startOffset = 0; 097 this.endOffset = doc.getLength(); 098 } 099 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 }