001    /*
002     *   Copyright (c) 2009 The JOMC Project
003     *   Copyright (c) 2005 Christian Schulte <cs@jomc.org>
004     *   All rights reserved.
005     *
006     *   Redistribution and use in source and binary forms, with or without
007     *   modification, are permitted provided that the following conditions
008     *   are met:
009     *
010     *     o Redistributions of source code must retain the above copyright
011     *       notice, this list of conditions and the following disclaimer.
012     *
013     *     o Redistributions in binary form must reproduce the above copyright
014     *       notice, this list of conditions and the following disclaimer in
015     *       the documentation and/or other materials provided with the
016     *       distribution.
017     *
018     *   THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS"
019     *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020     *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021     *   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR
022     *   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023     *   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024     *   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025     *   OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026     *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
027     *   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
028     *   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029     *
030     *   $Id: SectionEditor.java 509 2009-09-21 13:54:49Z schulte2005 $
031     *
032     */
033    package org.jomc.util;
034    
035    import java.util.Stack;
036    
037    /**
038     * Interface to section based editing.
039     * <p>Section based editing is a two phase process of parsing the editor's input into a corresponding hierarchy of
040     * {@code Section} instances, followed by rendering the parsed sections to produce the output of the editor. Method
041     * {@code editLine} returns {@code null} during parsing and the output of the editor on end of input, rendered by
042     * calling method {@code getOutput}. Parsing is backed by methods {@code getSection} and {@code isSectionFinished}.</p>
043     *
044     * @author <a href="mailto:cs@jomc.org">Christian Schulte</a>
045     * @version $Id: SectionEditor.java 509 2009-09-21 13:54:49Z schulte2005 $
046     *
047     * @see #edit(java.lang.String)
048     */
049    public class SectionEditor extends LineEditor
050    {
051    
052        /** Marker indicating the start of a section. */
053        private static final String DEFAULT_SECTION_START = "SECTION-START[";
054    
055        /** Marker indicating the end of a section. */
056        private static final String DEFAULT_SECTION_END = "SECTION-END";
057    
058        /** Stack of sections. */
059        private Stack<Section> stack;
060    
061        /** Creates a new {@code SectionEditor} instance. */
062        public SectionEditor()
063        {
064            super();
065        }
066    
067        /**
068         * Creates a new {@code SectionEditor} instance taking a string to use for separating lines.
069         *
070         * @param lineSeparator String to use for separating lines.
071         */
072        public SectionEditor( final String lineSeparator )
073        {
074            super( lineSeparator );
075        }
076    
077        /**
078         * Creates a new {@code SectionEditor} instance taking an editor to chain.
079         *
080         * @param editor The editor to chain.
081         */
082        public SectionEditor( final LineEditor editor )
083        {
084            super( editor );
085        }
086    
087        /**
088         * Creates a new {@code SectionEditor} instance taking an editor to chain and a string to use for separating lines.
089         *
090         * @param editor The editor to chain.
091         * @param lineSeparator String to use for separating lines.
092         */
093        public SectionEditor( final LineEditor editor, final String lineSeparator )
094        {
095            super( editor, lineSeparator );
096        }
097    
098        @Override
099        protected final String editLine( final String line )
100        {
101            if ( this.stack == null )
102            {
103                final Section root = new Section();
104                root.setName( this.getClass().getName() );
105                root.setMode( Section.MODE_HEAD );
106    
107                this.stack = new Stack<Section>();
108                this.stack.push( root );
109            }
110    
111            final Section current = this.stack.peek();
112            String replacement = null;
113    
114            if ( line != null )
115            {
116                final Section child = this.getSection( line );
117                if ( child != null )
118                {
119                    child.setStartingLine( line );
120                    child.setMode( Section.MODE_HEAD );
121    
122                    if ( current.getMode() == Section.MODE_HEAD )
123                    {
124                        current.setMode( Section.MODE_TAIL );
125                    }
126                    else if ( current.getMode() == Section.MODE_TAIL )
127                    {
128                        final StringBuilder tail = current.getTailContent();
129                        current.setLevel( current.getLevel() + 1 );
130                        current.getHeadContent().setLength( 0 );
131                        current.getHeadContent().append( tail );
132                        tail.setLength( 0 );
133                    }
134                    else
135                    {
136                        throw new AssertionError();
137                    }
138    
139                    current.getChildren().add( child );
140    
141                    this.stack.push( child );
142                }
143                else if ( this.isSectionFinished( line ) )
144                {
145                    this.stack.pop().setEndingLine( line );
146                }
147                else
148                {
149                    current.addContent( line + this.getLineSeparator() );
150                }
151            }
152            else
153            {
154                final Section root = this.stack.pop();
155    
156                if ( !this.stack.isEmpty() )
157                {
158                    throw new IllegalArgumentException( root.getStartingLine() );
159                }
160    
161                replacement = this.getOutput( root );
162                this.stack = null;
163            }
164    
165            return replacement;
166        }
167    
168        /**
169         * Parses the given line to mark the start of a new section.
170         *
171         * @param line The line to parse.
172         *
173         * @return The section starting at {@code line} or {@code null} if {@code line} does not mark the start of a
174         * section.
175         */
176        protected Section getSection( final String line )
177        {
178            Section s = null;
179    
180            if ( line != null )
181            {
182                final int startIndex = line.indexOf( DEFAULT_SECTION_START );
183                if ( startIndex != -1 )
184                {
185                    final String name = line.substring( startIndex + DEFAULT_SECTION_START.length(),
186                                                        line.indexOf( ']', startIndex + DEFAULT_SECTION_START.length() ) );
187    
188                    s = new Section();
189                    s.setName( name );
190                }
191            }
192    
193            return s;
194        }
195    
196        /**
197         * Parses the given line to mark the end of a section.
198         *
199         * @param line The line to parse.
200         *
201         * @return {@code true} if {@code line} marks the end of a section; {@code false} if {@code line} does not mark the
202         * end of a section.
203         */
204        protected boolean isSectionFinished( final String line )
205        {
206            boolean end = false;
207    
208            if ( line != null )
209            {
210                end = line.indexOf( DEFAULT_SECTION_END ) != -1;
211            }
212    
213            return end;
214        }
215    
216        /**
217         * Gets the output of the editor.
218         * <p>This method returns the unchanged input by rendering the given sections. Overwriting classes may call this
219         * method after having updated the given sections for rendering edited content.</p>
220         *
221         * @param root The root of the parsed sections to render the editor's output with.
222         *
223         * @return The output of the editor.
224         *
225         * @throws NullPointerException if {@code root} is {@code null}.
226         *
227         * @see Section#getSections()
228         */
229        protected String getOutput( final Section root )
230        {
231            if ( root == null )
232            {
233                throw new NullPointerException( "root" );
234            }
235    
236            return this.renderSections( root, new StringBuilder() ).toString();
237        }
238    
239        /**
240         * Appends the content of a given section to a given buffer.
241         *
242         * @param section The section to render.
243         * @param buffer The buffer to append the content of {@code section} to.
244         *
245         * @return {@code buffer} with content of {@code section} appended.
246         */
247        private StringBuilder renderSections( final Section section, final StringBuilder buffer )
248        {
249            final int l = section.getLevel();
250            for ( int i = 0; i <= l; i++ )
251            {
252                section.setLevel( i );
253                if ( section.getStartingLine() != null )
254                {
255                    buffer.append( section.getStartingLine() ).append( this.getLineSeparator() );
256                }
257    
258                buffer.append( section.getHeadContent() );
259    
260                for ( Section child : section.getChildren() )
261                {
262                    renderSections( child, buffer );
263                }
264    
265                buffer.append( section.getTailContent() );
266    
267                if ( section.getEndingLine() != null )
268                {
269                    buffer.append( section.getEndingLine() ).append( this.getLineSeparator() );
270                }
271            }
272            section.setLevel( l );
273            return buffer;
274        }
275    
276    }