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 891 2009-11-02 03:40:00Z schulte2005 $ 031 * 032 */ 033 package org.jomc.util; 034 035 import java.io.IOException; 036 import java.text.MessageFormat; 037 import java.util.ResourceBundle; 038 import java.util.Stack; 039 040 /** 041 * Interface to section based editing. 042 * <p>Section based editing is a two phase process of parsing the editor's input into a corresponding hierarchy of 043 * {@code Section} instances, followed by rendering the parsed sections to produce the output of the editor. Method 044 * {@code editLine} returns {@code null} during parsing and the output of the editor on end of input, rendered by 045 * calling method {@code getOutput}. Parsing is backed by methods {@code getSection} and {@code isSectionFinished}.</p> 046 * 047 * @author <a href="mailto:cs@jomc.org">Christian Schulte</a> 048 * @version $Id: SectionEditor.java 891 2009-11-02 03:40:00Z schulte2005 $ 049 * 050 * @see #edit(java.lang.String) 051 */ 052 public class SectionEditor extends LineEditor 053 { 054 055 /** Marker indicating the start of a section. */ 056 private static final String DEFAULT_SECTION_START = "SECTION-START["; 057 058 /** Marker indicating the end of a section. */ 059 private static final String DEFAULT_SECTION_END = "SECTION-END"; 060 061 /** Stack of sections. */ 062 private Stack<Section> stack; 063 064 /** Creates a new {@code SectionEditor} instance. */ 065 public SectionEditor() 066 { 067 super(); 068 } 069 070 /** 071 * Creates a new {@code SectionEditor} instance taking a string to use for separating lines. 072 * 073 * @param lineSeparator String to use for separating lines. 074 */ 075 public SectionEditor( final String lineSeparator ) 076 { 077 super( lineSeparator ); 078 } 079 080 /** 081 * Creates a new {@code SectionEditor} instance taking an editor to chain. 082 * 083 * @param editor The editor to chain. 084 */ 085 public SectionEditor( final LineEditor editor ) 086 { 087 super( editor ); 088 } 089 090 /** 091 * Creates a new {@code SectionEditor} instance taking an editor to chain and a string to use for separating lines. 092 * 093 * @param editor The editor to chain. 094 * @param lineSeparator String to use for separating lines. 095 */ 096 public SectionEditor( final LineEditor editor, final String lineSeparator ) 097 { 098 super( editor, lineSeparator ); 099 } 100 101 @Override 102 protected final String editLine( final String line ) throws IOException 103 { 104 if ( this.stack == null ) 105 { 106 final Section root = new Section(); 107 root.setMode( Section.MODE_HEAD ); 108 this.stack = new Stack<Section>(); 109 this.stack.push( root ); 110 } 111 112 Section current = this.stack.peek(); 113 String replacement = null; 114 115 if ( line != null ) 116 { 117 final Section child = this.getSection( line ); 118 119 if ( child != null ) 120 { 121 child.setStartingLine( line ); 122 child.setMode( Section.MODE_HEAD ); 123 124 if ( current.getMode() == Section.MODE_TAIL && current.getTailContent().length() > 0 ) 125 { 126 final Section s = new Section(); 127 s.getHeadContent().append( current.getTailContent() ); 128 current.getTailContent().setLength( 0 ); 129 current.getSections().add( s ); 130 current = s; 131 this.stack.push( current ); 132 } 133 134 current.getSections().add( child ); 135 current.setMode( Section.MODE_TAIL ); 136 this.stack.push( child ); 137 } 138 else if ( this.isSectionFinished( line ) ) 139 { 140 final Section s = this.stack.pop(); 141 s.setEndingLine( line ); 142 143 if ( this.stack.isEmpty() ) 144 { 145 this.stack = null; 146 throw new IOException( this.getMessage( "unexpectedEndOfSection", new Object[] 147 { 148 s.getName() == null ? "/" : s.getName() 149 } ) ); 150 151 } 152 153 if ( this.stack.peek().getName() == null && this.stack.size() > 1 ) 154 { 155 this.stack.pop(); 156 } 157 } 158 else 159 { 160 switch ( current.getMode() ) 161 { 162 case Section.MODE_HEAD: 163 current.getHeadContent().append( line ).append( this.getLineSeparator() ); 164 break; 165 166 case Section.MODE_TAIL: 167 current.getTailContent().append( line ).append( this.getLineSeparator() ); 168 break; 169 170 default: 171 throw new AssertionError( current.getMode() ); 172 173 } 174 } 175 } 176 else 177 { 178 final Section root = this.stack.pop(); 179 180 if ( !this.stack.isEmpty() ) 181 { 182 this.stack = null; 183 throw new IOException( this.getMessage( "unexpectedEndOfSection", new Object[] 184 { 185 root.getName() == null ? "/" : root.getName() 186 } ) ); 187 188 } 189 190 replacement = this.getOutput( root ); 191 this.stack = null; 192 } 193 194 return replacement; 195 } 196 197 /** 198 * Parses the given line to mark the start of a new section. 199 * 200 * @param line The line to parse. 201 * 202 * @return The section starting at {@code line} or {@code null} if {@code line} does not mark the start of a 203 * section. 204 */ 205 protected Section getSection( final String line ) 206 { 207 Section s = null; 208 209 if ( line != null ) 210 { 211 final int startIndex = line.indexOf( DEFAULT_SECTION_START ); 212 if ( startIndex != -1 ) 213 { 214 final String name = line.substring( startIndex + DEFAULT_SECTION_START.length(), 215 line.indexOf( ']', startIndex + DEFAULT_SECTION_START.length() ) ); 216 217 s = new Section(); 218 s.setName( name ); 219 } 220 } 221 222 return s; 223 } 224 225 /** 226 * Parses the given line to mark the end of a section. 227 * 228 * @param line The line to parse. 229 * 230 * @return {@code true} if {@code line} marks the end of a section; {@code false} if {@code line} does not mark the 231 * end of a section. 232 */ 233 protected boolean isSectionFinished( final String line ) 234 { 235 return line != null && line.indexOf( DEFAULT_SECTION_END ) != -1; 236 } 237 238 /** 239 * Edits a section. 240 * <p>This method does not change any content by default. Overriding classes may use this method for editing 241 * sections prior to rendering.</p> 242 * 243 * @param section The section to edit. 244 * 245 * @throws NullPointerException if {@code section} is {@code null}. 246 * @throws IOException if editing fails. 247 */ 248 protected void editSection( final Section section ) throws IOException 249 { 250 if ( section == null ) 251 { 252 throw new NullPointerException( "section" ); 253 } 254 } 255 256 /** 257 * Edits a section recursively. 258 * 259 * @param section The section to edit recursively. 260 * 261 * @throws NullPointerException if {@code section} is {@code null}. 262 * @throws IOException if editing fails. 263 */ 264 private void editSections( final Section section ) throws IOException 265 { 266 if ( section == null ) 267 { 268 throw new NullPointerException( "section" ); 269 } 270 271 this.editSection( section ); 272 for ( Section child : section.getSections() ) 273 { 274 this.editSections( child ); 275 } 276 } 277 278 /** 279 * Gets the output of the editor. 280 * <p>This method calls method {@code editSection()} for each section of the editor prior to rendering the sections 281 * to produce the output of the editor.</p> 282 * 283 * @param section The section to start rendering the editor's output with. 284 * 285 * @return The output of the editor. 286 * 287 * @throws NullPointerException if {@code section} is {@code null}. 288 * @throws IOException if editing or rendering fails. 289 */ 290 protected String getOutput( final Section section ) throws IOException 291 { 292 if ( section == null ) 293 { 294 throw new NullPointerException( "section" ); 295 } 296 297 this.editSections( section ); 298 return this.renderSections( section, new StringBuilder() ).toString(); 299 } 300 301 /** 302 * Appends the content of a given section to a given buffer. 303 * 304 * @param section The section to render. 305 * @param buffer The buffer to append the content of {@code section} to. 306 * 307 * @return {@code buffer} with content of {@code section} appended. 308 */ 309 private StringBuilder renderSections( final Section section, final StringBuilder buffer ) 310 { 311 if ( section.getStartingLine() != null ) 312 { 313 buffer.append( section.getStartingLine() ).append( this.getLineSeparator() ); 314 } 315 316 buffer.append( section.getHeadContent() ); 317 318 for ( Section child : section.getSections() ) 319 { 320 this.renderSections( child, buffer ); 321 } 322 323 buffer.append( section.getTailContent() ); 324 325 if ( section.getEndingLine() != null ) 326 { 327 buffer.append( section.getEndingLine() ).append( this.getLineSeparator() ); 328 } 329 330 return buffer; 331 } 332 333 private String getMessage( final String key, final Object arguments ) 334 { 335 return new MessageFormat( ResourceBundle.getBundle( SectionEditor.class.getName(). 336 replace( '.', '/' ) ).getString( key ) ).format( arguments ); 337 338 } 339 340 }