001/*
002 *   Copyright (C) Christian Schulte, 2005-206
003 *   All rights reserved.
004 *
005 *   Redistribution and use in source and binary forms, with or without
006 *   modification, are permitted provided that the following conditions
007 *   are met:
008 *
009 *     o Redistributions of source code must retain the above copyright
010 *       notice, this list of conditions and the following disclaimer.
011 *
012 *     o Redistributions in binary form must reproduce the above copyright
013 *       notice, this list of conditions and the following disclaimer in
014 *       the documentation and/or other materials provided with the
015 *       distribution.
016 *
017 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 *   $JOMC: LineEditor.java 4613 2012-09-22 10:07:08Z schulte $
029 *
030 */
031package org.jomc.util;
032
033import java.io.BufferedReader;
034import java.io.IOException;
035import java.io.StringReader;
036
037/**
038 * Interface to line based editing.
039 *
040 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
041 * @version $JOMC: LineEditor.java 4613 2012-09-22 10:07:08Z schulte $
042 *
043 * @see #edit(java.lang.String)
044 */
045public class LineEditor
046{
047
048    /** Editor to chain. */
049    private LineEditor editor;
050
051    /** Line separator. */
052    private String lineSeparator;
053
054    /**
055     * Current line number.
056     * @since 1.2
057     */
058    private long lineNumber;
059
060    /** Creates a new {@code LineEditor} instance. */
061    public LineEditor()
062    {
063        this( null, null );
064    }
065
066    /**
067     * Creates a new {@code LineEditor} instance taking a string to use for separating lines.
068     *
069     * @param lineSeparator String to use for separating lines.
070     */
071    public LineEditor( final String lineSeparator )
072    {
073        this( null, lineSeparator );
074    }
075
076    /**
077     * Creates a new {@code LineEditor} instance taking an editor to chain.
078     *
079     * @param editor The editor to chain.
080     */
081    public LineEditor( final LineEditor editor )
082    {
083        this( editor, null );
084    }
085
086    /**
087     * Creates a new {@code LineEditor} instance taking an editor to chain and a string to use for separating lines.
088     *
089     * @param editor The editor to chain.
090     * @param lineSeparator String to use for separating lines.
091     */
092    public LineEditor( final LineEditor editor, final String lineSeparator )
093    {
094        super();
095        this.editor = editor;
096        this.lineSeparator = lineSeparator;
097        this.lineNumber = 0L;
098    }
099
100    /**
101     * Gets the line separator of the editor.
102     *
103     * @return The line separator of the editor.
104     */
105    public final String getLineSeparator()
106    {
107        if ( this.lineSeparator == null )
108        {
109            this.lineSeparator = System.getProperty( "line.separator", "\n" );
110        }
111
112        return this.lineSeparator;
113    }
114
115    /**
116     * Gets the current line number.
117     *
118     * @return The current line number.
119     *
120     * @since 1.2
121     */
122    public final long getLineNumber()
123    {
124        return this.lineNumber;
125    }
126
127    /**
128     * Edits text.
129     * <p>This method splits the given string into lines and passes every line to method {@code editLine} in order of
130     * occurrence. On end of input, method {@code editLine} is called with a {@code null} argument.</p>
131     *
132     * @param text The text to edit or {@code null}.
133     *
134     * @return The edited text or {@code null}.
135     *
136     * @throws IOException if editing fails.
137     */
138    public final String edit( final String text ) throws IOException
139    {
140        String edited = text;
141        this.lineNumber = 0L;
142        BufferedReader reader = null;
143        boolean suppressExceptionOnClose = true;
144
145        try
146        {
147            if ( edited != null )
148            {
149                final StringBuilder buf = new StringBuilder( edited.length() + 16 );
150                boolean appended = false;
151
152                if ( edited.length() > 0 )
153                {
154                    reader = new BufferedReader( new StringReader( edited ) );
155
156                    String line = null;
157                    while ( ( line = reader.readLine() ) != null )
158                    {
159                        this.lineNumber++;
160                        final String replacement = this.editLine( line );
161                        if ( replacement != null )
162                        {
163                            buf.append( replacement ).append( this.getLineSeparator() );
164                            appended = true;
165                        }
166                    }
167                }
168                else
169                {
170                    this.lineNumber++;
171                    final String replacement = this.editLine( edited );
172                    if ( replacement != null )
173                    {
174                        buf.append( replacement ).append( this.getLineSeparator() );
175                        appended = true;
176                    }
177                }
178
179                final String replacement = this.editLine( null );
180                if ( replacement != null )
181                {
182                    buf.append( replacement );
183                    appended = true;
184                }
185
186                edited = appended ? buf.toString() : null;
187            }
188
189            if ( this.editor != null )
190            {
191                edited = this.editor.edit( edited );
192            }
193
194            suppressExceptionOnClose = false;
195            return edited;
196        }
197        finally
198        {
199            try
200            {
201                if ( reader != null )
202                {
203                    reader.close();
204                }
205            }
206            catch ( final IOException e )
207            {
208                if ( !suppressExceptionOnClose )
209                {
210                    throw e;
211                }
212            }
213        }
214    }
215
216    /**
217     * Edits a line.
218     *
219     * @param line The line to edit or {@code null}, indicating the end of input.
220     *
221     * @return The string to replace {@code line} with or {@code null}, to replace {@code line} with nothing.
222     *
223     * @throws IOException if editing fails.
224     */
225    protected String editLine( final String line ) throws IOException
226    {
227        return line;
228    }
229
230}