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(), 
 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: }