001/* AbstractWriter.java --
002   Copyright (C) 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038package javax.swing.text;
039
040import java.io.IOException;
041import java.io.Writer;
042import java.util.Arrays;
043import 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 */
050public 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}