Source for javax.swing.text.AbstractWriter

   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(), elt.getEndOffset());
 187:   }
 188: 
 189:   /**
 190:    * This method calls Writer.write on the indicated data, and updates
 191:    * the current line length.  This method does not look for newlines
 192:    * in the written data; the caller is responsible for that.
 193:    *
 194:    * @since 1.3
 195:    */
 196:   protected void output(char[] data, int start, int len) throws IOException
 197:   {
 198:     writer.write(data, start, len);
 199:     lineLength += len;
 200:   }
 201: 
 202:   /**
 203:    * Write a line separator using the output method, and then reset
 204:    * the current line length.
 205:    *
 206:    * @since 1.3
 207:    */
 208:   protected void writeLineSeparator() throws IOException
 209:   {
 210:     output(lineSeparatorChars, 0, lineSeparatorChars.length);
 211:     lineLength = 0;
 212:     indented = false;
 213:   }
 214: 
 215:   /**
 216:    * Write a single character.
 217:    */
 218:   protected void write(char ch) throws IOException
 219:   {
 220:     write(new char[] { ch }, 0, 1);
 221:   }
 222: 
 223:   /**
 224:    * Write a String.
 225:    */
 226:   protected void write(String s) throws IOException
 227:   {
 228:     char[] v = s.toCharArray();
 229:     write(v, 0, v.length);
 230:   }
 231: 
 232:   /**
 233:    * Write a character array to the output Writer, properly handling
 234:    * newlines and, if needed, wrapping lines as they are output.
 235:    * @since 1.3
 236:    */
 237:   protected void write(char[] data, int start, int len) throws IOException
 238:   {
 239:     if (getCanWrapLines())
 240:       {
 241:     // FIXME: should we be handling newlines specially here?
 242:     for (int i = 0; i < len; )
 243:       {
 244:         int start_i = i;
 245:         // Find next space.
 246:         while (i < len && data[start + i] != ' ')
 247:           ++i;
 248:         if (i < len && lineLength + i - start_i >= maxLineLength)
 249:           writeLineSeparator();
 250:         else if (i < len)
 251:           {
 252:         // Write the trailing space.
 253:         ++i;
 254:           }
 255:         // Write out the text.
 256:         output(data, start + start_i, start + i - start_i);
 257:       }
 258:       }
 259:     else
 260:       {
 261:     int saved_i = start;
 262:     for (int i = start; i < start + len; ++i)
 263:       {
 264:         if (data[i] == NEWLINE)
 265:           {
 266:         output(data, saved_i, i - saved_i);
 267:         writeLineSeparator();
 268:           }
 269:       }
 270:     if (saved_i < start + len - 1)
 271:       output(data, saved_i, start + len - saved_i);
 272:       }
 273:   }
 274: 
 275:   /**
 276:    * Indent this line by emitting spaces, according to the current
 277:    * indent level and the current number of spaces per indent.  After
 278:    * this method is called, the current line is no longer considered
 279:    * to be empty, even if no spaces are actually written.
 280:    */
 281:   protected void indent() throws IOException
 282:   {
 283:     int spaces = indentLevel * indentSpace;
 284:     if (spaces > 0)
 285:       {
 286:     char[] v = new char[spaces];
 287:     Arrays.fill(v, ' ');
 288:     write(v, 0, v.length);
 289:       }
 290:     indented = true;
 291:   }
 292: 
 293:   /**
 294:    * Return the index of the Document at which output starts.
 295:    * @since 1.3
 296:    */
 297:   public int getStartOffset()
 298:   {
 299:     return startOffset;
 300:   }
 301: 
 302:   /**
 303:    * Return the index of the Document at which output ends.
 304:    * @since 1.3
 305:    */
 306:   public int getEndOffset()
 307:   {
 308:     return endOffset;
 309:   }
 310: 
 311:   /**
 312:    * Return true if the Element's range overlaps our desired output
 313:    * range; false otherwise.
 314:    */
 315:   protected boolean inRange(Element elt)
 316:   {
 317:     int eltStart = elt.getStartOffset();
 318:     int eltEnd = elt.getEndOffset();
 319:     return ((eltStart >= startOffset && eltStart < endOffset)
 320:         || (eltEnd >= startOffset && eltEnd < endOffset));
 321:   }
 322: 
 323:   /**
 324:    * Output the text of the indicated Element, properly clipping it to
 325:    * the range of the Document specified when the AbstractWriter was
 326:    * created.
 327:    */
 328:   protected void text(Element elt) throws BadLocationException, IOException
 329:   {
 330:     int eltStart = elt.getStartOffset();
 331:     int eltEnd = elt.getEndOffset();
 332: 
 333:     eltStart = Math.max(eltStart, startOffset);
 334:     eltEnd = Math.min(eltEnd, endOffset);
 335:     write(document.getText(eltStart, eltEnd));
 336:   }
 337: 
 338:   /**
 339:    * Set the maximum line length.
 340:    */
 341:   protected void setLineLength(int maxLineLength)
 342:   {
 343:     this.maxLineLength = maxLineLength;
 344:   }
 345: 
 346:   /**
 347:    * Return the maximum line length.
 348:    * @since 1.3
 349:    */
 350:   protected int getLineLength()
 351:   {
 352:     return maxLineLength;
 353:   }
 354: 
 355:   /**
 356:    * Set the current line length.
 357:    * @since 1.3
 358:    */
 359:   protected void setCurrentLineLength(int lineLength)
 360:   {
 361:     this.lineLength = lineLength;
 362:   }
 363: 
 364:   /**
 365:    * Return the current line length.
 366:    * @since 1.3
 367:    */
 368:   protected int getCurrentLineLength()
 369:   {
 370:     return lineLength;
 371:   }
 372: 
 373:   /**
 374:    * Return true if the line is empty, false otherwise.  The line is
 375:    * empty if nothing has been written since the last newline, and
 376:    * indent has not been invoked.
 377:    */
 378:   protected boolean isLineEmpty()
 379:   {
 380:     return lineLength == 0 && ! indented;
 381:   }
 382: 
 383:   /**
 384:    * Set the flag indicating whether lines will wrap.  This affects
 385:    * the behavior of write().
 386:    * @since 1.3
 387:    */
 388:   protected void setCanWrapLines(boolean canWrapLines)
 389:   {
 390:     this.canWrapLines = canWrapLines;
 391:   }
 392: 
 393:   /**
 394:    * Return true if lines printed via write() will wrap, false
 395:    * otherwise.
 396:    * @since 1.3
 397:    */
 398:   protected boolean getCanWrapLines()
 399:   {
 400:     return canWrapLines;
 401:   }
 402: 
 403:   /**
 404:    * Set the number of spaces per indent level.
 405:    * @since 1.3
 406:    */
 407:   protected void setIndentSpace(int indentSpace)
 408:   {
 409:     this.indentSpace = indentSpace;
 410:   }
 411: 
 412:   /**
 413:    * Return the number of spaces per indent level.
 414:    * @since 1.3
 415:    */
 416:   protected int getIndentSpace()
 417:   {
 418:     return indentSpace;
 419:   }
 420: 
 421:   /**
 422:    * Set the current line separator.
 423:    * @since 1.3
 424:    */
 425:   public void setLineSeparator(String lineSeparator)
 426:   {
 427:     this.lineSeparator = lineSeparator;
 428:     this.lineSeparatorChars = lineSeparator.toCharArray();
 429:   }
 430: 
 431:   /**
 432:    * Return the current line separator.
 433:    * @since 1.3
 434:    */
 435:   public String getLineSeparator()
 436:   {
 437:     return lineSeparator;
 438:   }
 439: 
 440:   /**
 441:    * Increment the indent level.
 442:    */
 443:   protected void incrIndent()
 444:   {
 445:     ++indentLevel;
 446:   }
 447: 
 448:   /**
 449:    * Decrement the indent level.
 450:    */
 451:   protected void decrIndent()
 452:   {
 453:     --indentLevel;
 454:   }
 455: 
 456:   /**
 457:    * Return the current indent level.
 458:    * @since 1.3
 459:    */
 460:   protected int getIndentLevel()
 461:   {
 462:     return indentLevel;
 463:   }
 464: 
 465:   /**
 466:    * Print the given AttributeSet as a sequence of assignment-like
 467:    * strings, e.g. "key=value".
 468:    */
 469:   protected void writeAttributes(AttributeSet attrs) throws IOException
 470:   {
 471:     Enumeration e = attrs.getAttributeNames();
 472:     while (e.hasMoreElements())
 473:       {
 474:     Object name = e.nextElement();
 475:     Object val = attrs.getAttribute(name);
 476:     write(name + "=" + val);
 477:     writeLineSeparator();
 478:       }
 479:   }
 480: }