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    }