001/*
002 *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
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 5091 2016-04-04 15:40:17Z 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 5091 2016-04-04 15:40:17Z schulte $
042 *
043 * @see #edit(java.lang.String)
044 */
045public class LineEditor
046{
047
048    /**
049     * Editor to chain.
050     */
051    private LineEditor editor;
052
053    /**
054     * Line separator.
055     */
056    private String lineSeparator;
057
058    /**
059     * Current line number.
060     *
061     * @since 1.2
062     */
063    private long lineNumber;
064
065    /**
066     * Creates a new {@code LineEditor} instance.
067     */
068    public LineEditor()
069    {
070        this( null, null );
071    }
072
073    /**
074     * Creates a new {@code LineEditor} instance taking a string to use for separating lines.
075     *
076     * @param lineSeparator String to use for separating lines.
077     */
078    public LineEditor( final String lineSeparator )
079    {
080        this( null, lineSeparator );
081    }
082
083    /**
084     * Creates a new {@code LineEditor} instance taking an editor to chain.
085     *
086     * @param editor The editor to chain.
087     */
088    public LineEditor( final LineEditor editor )
089    {
090        this( editor, null );
091    }
092
093    /**
094     * Creates a new {@code LineEditor} instance taking an editor to chain and a string to use for separating lines.
095     *
096     * @param editor The editor to chain.
097     * @param lineSeparator String to use for separating lines.
098     */
099    public LineEditor( final LineEditor editor, final String lineSeparator )
100    {
101        super();
102        this.editor = editor;
103        this.lineSeparator = lineSeparator;
104        this.lineNumber = 0L;
105    }
106
107    /**
108     * Gets the line separator of the editor.
109     *
110     * @return The line separator of the editor.
111     */
112    public final String getLineSeparator()
113    {
114        if ( this.lineSeparator == null )
115        {
116            this.lineSeparator = System.getProperty( "line.separator", "\n" );
117        }
118
119        return this.lineSeparator;
120    }
121
122    /**
123     * Gets the current line number.
124     *
125     * @return The current line number.
126     *
127     * @since 1.2
128     */
129    public final long getLineNumber()
130    {
131        return this.lineNumber;
132    }
133
134    /**
135     * Edits text.
136     * <p>
137     * This method splits the given string into lines and passes every line to method {@code editLine} in order of
138     * occurrence. On end of input, method {@code editLine} is called with a {@code null} argument.
139     * </p>
140     *
141     * @param text The text to edit or {@code null}.
142     *
143     * @return The edited text or {@code null}.
144     *
145     * @throws IOException if editing fails.
146     */
147    public final String edit( final String text ) throws IOException
148    {
149        String edited = text;
150        this.lineNumber = 0L;
151        BufferedReader reader = null;
152        boolean suppressExceptionOnClose = true;
153
154        try
155        {
156            if ( edited != null )
157            {
158                final StringBuilder buf = new StringBuilder( edited.length() + 16 );
159                boolean appended = false;
160
161                if ( edited.length() > 0 )
162                {
163                    reader = new BufferedReader( new StringReader( edited ) );
164
165                    String line = null;
166                    while ( ( line = reader.readLine() ) != null )
167                    {
168                        this.lineNumber++;
169                        final String replacement = this.editLine( line );
170                        if ( replacement != null )
171                        {
172                            buf.append( replacement ).append( this.getLineSeparator() );
173                            appended = true;
174                        }
175                    }
176                }
177                else
178                {
179                    this.lineNumber++;
180                    final String replacement = this.editLine( edited );
181                    if ( replacement != null )
182                    {
183                        buf.append( replacement ).append( this.getLineSeparator() );
184                        appended = true;
185                    }
186                }
187
188                final String replacement = this.editLine( null );
189                if ( replacement != null )
190                {
191                    buf.append( replacement );
192                    appended = true;
193                }
194
195                edited = appended ? buf.toString() : null;
196            }
197
198            if ( this.editor != null )
199            {
200                edited = this.editor.edit( edited );
201            }
202
203            suppressExceptionOnClose = false;
204            return edited;
205        }
206        finally
207        {
208            try
209            {
210                if ( reader != null )
211                {
212                    reader.close();
213                }
214            }
215            catch ( final IOException e )
216            {
217                if ( !suppressExceptionOnClose )
218                {
219                    throw e;
220                }
221            }
222        }
223    }
224
225    /**
226     * Edits a line.
227     *
228     * @param line The line to edit or {@code null}, indicating the end of input.
229     *
230     * @return The string to replace {@code line} with or {@code null}, to replace {@code line} with nothing.
231     *
232     * @throws IOException if editing fails.
233     */
234    protected String editLine( final String line ) throws IOException
235    {
236        return line;
237    }
238
239}