001    /**
002     *
003     * Copyright 2006 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    /*
019     * This code has been borrowed from the Apache Xerces project. We're copying the code to
020     * keep from adding a dependency on Xerces in the Geronimo kernel.
021     */
022    
023    package org.apache.geronimo.system.configuration;
024    
025    import java.io.Writer;
026    import java.io.StringWriter;
027    import java.io.IOException;
028    
029    
030    /**
031     * Extends {@link Printer} and adds support for indentation and line
032     * wrapping.
033     *
034     * @version $Revision: 410741 $ $Date: 2006-05-31 21:35:48 -0700 (Wed, 31 May 2006) $
035     * @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
036     */
037    public class IndentPrinter
038        extends Printer
039    {
040    
041    
042        /**
043         * Holds the currently accumulating text line. This buffer will constantly
044         * be reused by deleting its contents instead of reallocating it.
045         */
046        private StringBuffer    _line;
047    
048    
049        /**
050         * Holds the currently accumulating text that follows {@link #_line}.
051         * When the end of the part is identified by a call to {@link #printSpace}
052         * or {@link #breakLine}, this part is added to the accumulated line.
053         */
054        private StringBuffer    _text;
055    
056    
057        /**
058         * Counts how many white spaces come between the accumulated line and the
059         * current accumulated text. Multiple spaces at the end of the a line
060         * will not be printed.
061         */
062        private int             _spaces;
063    
064    
065        /**
066         * Holds the indentation for the current line that is now accumulating in
067         * memory and will be sent for printing shortly.
068         */
069        private int             _thisIndent;
070        
071        
072        /**
073         * Holds the indentation for the next line to be printed. After this line is
074         * printed, {@link #_nextIndent} is assigned to {@link #_thisIndent}.
075         */
076        private int             _nextIndent;
077    
078    
079        IndentPrinter( Writer writer, OutputFormat format)
080        {
081            super( writer, format );
082            // Initialize everything for a first/second run.
083            _line = new StringBuffer( 80 );
084            _text = new StringBuffer( 20 );
085            _spaces = 0;
086            _thisIndent = _nextIndent = 0;
087        }
088    
089    
090        /**
091         * Called by any of the DTD handlers to enter DTD mode.
092         * Once entered, all output will be accumulated in a string
093         * that can be printed as part of the document's DTD.
094         * This method may be called any number of time but will only
095         * have affect the first time it's called. To exist DTD state
096         * and get the accumulated DTD, call {@link #leaveDTD}.
097         */
098        public void enterDTD()
099        {
100            // Can only enter DTD state once. Once we're out of DTD
101            // state, can no longer re-enter it.
102            if ( dtdWriter == null ) {
103                _line.append( _text );
104                _text = new StringBuffer( 20 );
105                flushLine( false );
106                dtdWriter = new StringWriter();
107                docWriter = writer;
108                writer = dtdWriter;
109            }
110        }
111        
112        
113        /**
114         * Called by the root element to leave DTD mode and if any
115         * DTD parts were printer, will return a string with their
116         * textual content.
117         */
118        public String leaveDTD()
119        {
120            // Only works if we're going out of DTD mode.
121            if ( writer == dtdWriter ) {
122                _line.append( _text );
123                _text = new StringBuffer( 20 );
124                flushLine( false );
125                writer = docWriter;
126                return dtdWriter.toString();
127            } else
128                return null;
129        }
130        
131        
132        /**
133         * Called to print additional text. Each time this method is called
134         * it accumulates more text. When a space is printed ({@link
135         * #printSpace}) all the accumulated text becomes one part and is
136         * added to the accumulate line. When a line is long enough, it can
137         * be broken at its text boundary.
138         *
139         * @param text The text to print
140         */
141        public void printText( String text )
142        {
143            _text.append( text );
144        }
145        
146        
147        public void printText( StringBuffer text )
148        {
149            _text.append( text );
150        }
151    
152    
153        public void printText( char ch )
154        {
155            _text.append( ch );
156        }
157    
158    
159        public void printText( char[] chars, int start, int length )
160        {
161            _text.append( chars, start, length );
162        }
163        
164    
165        /**
166         * Called to print a single space between text parts that may be
167         * broken into separate lines. Must not be called to print a space
168         * when preserving spaces. The text accumulated so far with {@link
169         * #printText} will be added to the accumulated line, and a space
170         * separator will be counted. If the line accumulated so far is
171         * long enough, it will be printed.
172         */
173        public void printSpace()
174        {
175            // The line consists of the text accumulated in _line,
176            // followed by one or more spaces as counted by _spaces,
177            // followed by more space accumulated in _text:
178            // -  Text is printed and accumulated into _text.
179            // -  A space is printed, so _text is added to _line and
180            //    a space is counted.
181            // -  More text is printed and accumulated into _text.
182            // -  A space is printed, the previous spaces are added
183            //    to _line, the _text is added to _line, and a new
184            //    space is counted.
185            
186            // If text was accumulated with printText(), then the space
187            // means we have to move that text into the line and
188            // start accumulating new text with printText().
189            if ( _text.length() > 0 ) {
190                // If the text breaks a line bounary, wrap to the next line.
191                // The printed line size consists of the indentation we're going
192                // to use next, the accumulated line so far, some spaces and the
193                // accumulated text so far.
194                if ( format.getLineWidth() > 0 &&
195                     _thisIndent + _line.length() + _spaces + _text.length() > format.getLineWidth() ) {
196                    flushLine( false );
197                    try {
198                        // Print line and new line, then zero the line contents.
199                        writer.write( format.getLineSeparator() );
200                    } catch ( IOException except ) {
201                        // We don't throw an exception, but hold it
202                        // until the end of the document.
203                        if ( exception == null )
204                            exception = except;
205                    }
206                }
207                
208                // Add as many spaces as we accumulaed before.
209                // At the end of this loop, _spaces is zero.
210                while ( _spaces > 0 ) {
211                    _line.append( ' ' );
212                    --_spaces;
213                }
214                _line.append( _text );
215                _text = new StringBuffer( 20 );
216            }
217            // Starting a new word: accumulate the text between the line
218            // and this new word; not a new word: just add another space.
219            ++_spaces;
220        }
221    
222    
223        /**
224         * Called to print a line consisting of the text accumulated so
225         * far. This is equivalent to calling {@link #printSpace} but
226         * forcing the line to print and starting a new line ({@link
227         * #printSpace} will only start a new line if the current line
228         * is long enough).
229         */
230        public void breakLine()
231        {
232            breakLine( false );
233        }
234    
235    
236        public void breakLine( boolean preserveSpace )
237        {
238            // Equivalent to calling printSpace and forcing a flushLine.
239            if ( _text.length() > 0 ) {
240                while ( _spaces > 0 ) {
241                    _line.append( ' ' );
242                    --_spaces;
243                }
244                _line.append( _text );
245                _text = new StringBuffer( 20 );
246            }
247            flushLine( preserveSpace );
248            try {
249                // Print line and new line, then zero the line contents.
250                writer.write( format.getLineSeparator() );
251            } catch ( IOException except ) {
252                // We don't throw an exception, but hold it
253                // until the end of the document.
254                if ( exception == null )
255                    exception = except;
256            }
257        }
258        
259    
260        /**
261         * Flushes the line accumulated so far to the writer and get ready
262         * to accumulate the next line. This method is called by {@link
263         * #printText} and {@link #printSpace} when the accumulated line plus
264         * accumulated text are two long to fit on a given line. At the end of
265         * this method {@link #_line} is empty and {@link #_spaces} is zero.
266         */
267        public void flushLine( boolean preserveSpace )
268        {
269            int     indent;
270            
271            if ( _line.length() > 0 ) {
272                try {
273                    
274                    if ( format.getIndenting() && ! preserveSpace ) {
275                        // Make sure the indentation does not blow us away.
276                        indent = _thisIndent;
277                        if ( ( 2 * indent ) > format.getLineWidth() && format.getLineWidth() > 0 )
278                            indent = format.getLineWidth() / 2;
279                        // Print the indentation as spaces and set the current
280                        // indentation to the next expected indentation.
281                        while ( indent > 0 ) {
282                            writer.write( ' ' );
283                            --indent;
284                        }
285                    }
286                    _thisIndent = _nextIndent;
287                    
288                    // There is no need to print the spaces at the end of the line,
289                    // they are simply stripped and replaced with a single line
290                    // separator.
291                    _spaces = 0;
292                    writer.write( _line.toString() );
293                    
294                    _line = new StringBuffer( 40 );
295                } catch ( IOException except ) {
296                    // We don't throw an exception, but hold it
297                    // until the end of the document.
298                    if ( exception == null )
299                        exception = except;
300                }
301            }
302        }
303        
304        
305        /**
306         * Flush the output stream. Must be called when done printing
307         * the document, otherwise some text might be buffered.
308         */
309        public void flush()
310        {
311            if ( _line.length() > 0 || _text.length() > 0 )
312                breakLine();
313            try {
314                writer.flush();
315            } catch ( IOException except ) {
316                // We don't throw an exception, but hold it
317                // until the end of the document.
318                if ( exception == null )
319                    exception = except;
320            }
321        }
322    
323    
324        /**
325         * Increment the indentation for the next line.
326         */
327        public void indent()
328        {
329            _nextIndent += format.getIndent();
330        }
331    
332    
333        /**
334         * Decrement the indentation for the next line.
335         */
336        public void unindent()
337        {
338            _nextIndent -= format.getIndent();
339            if ( _nextIndent < 0 )
340                _nextIndent = 0;
341            // If there is no current line and we're de-identing then
342            // this indentation level is actually the next level.
343            if ( ( _line.length() + _spaces + _text.length() ) == 0 )
344                _thisIndent = _nextIndent;
345        }
346    
347    
348        public int getNextIndent()
349        {
350            return _nextIndent;
351        }
352    
353    
354        public void setNextIndent( int indent )
355        {
356            _nextIndent = indent;
357        }
358    
359    
360        public void setThisIndent( int indent )
361        {
362            _thisIndent = indent;
363        }
364    
365    
366    }