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 }