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    */
AbstractWriter(Writer writer, Document doc)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    */
AbstractWriter(Writer writer, Document doc, int pos, int len)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    */
AbstractWriter(Writer writer, Element elt)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    */
AbstractWriter(Writer writer, Element elt, int pos, int len)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    */
getElementIterator()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    */
getWriter()154   protected Writer getWriter()
155   {
156     return writer;
157   }
158 
159   /**
160    * Return this writer's Document.
161    */
getDocument()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    */
write()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    */
getText(Element elt)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    */
output(char[] data, int start, int len)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    */
writeLineSeparator()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    */
write(char ch)219   protected void write(char ch) throws IOException
220   {
221     write(new char[] { ch }, 0, 1);
222   }
223 
224   /**
225    * Write a String.
226    */
write(String s)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    */
write(char[] data, int start, int len)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    */
indent()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    */
getStartOffset()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    */
getEndOffset()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    */
inRange(Element elt)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    */
text(Element elt)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    */
setLineLength(int maxLineLength)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    */
getLineLength()351   protected int getLineLength()
352   {
353     return maxLineLength;
354   }
355 
356   /**
357    * Set the current line length.
358    * @since 1.3
359    */
setCurrentLineLength(int lineLength)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    */
getCurrentLineLength()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    */
isLineEmpty()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    */
setCanWrapLines(boolean canWrapLines)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    */
getCanWrapLines()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    */
setIndentSpace(int indentSpace)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    */
getIndentSpace()417   protected int getIndentSpace()
418   {
419     return indentSpace;
420   }
421 
422   /**
423    * Set the current line separator.
424    * @since 1.3
425    */
setLineSeparator(String lineSeparator)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    */
getLineSeparator()436   public String getLineSeparator()
437   {
438     return lineSeparator;
439   }
440 
441   /**
442    * Increment the indent level.
443    */
incrIndent()444   protected void incrIndent()
445   {
446     ++indentLevel;
447   }
448 
449   /**
450    * Decrement the indent level.
451    */
decrIndent()452   protected void decrIndent()
453   {
454     --indentLevel;
455   }
456 
457   /**
458    * Return the current indent level.
459    * @since 1.3
460    */
getIndentLevel()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    */
writeAttributes(AttributeSet attrs)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 }
482