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 }