/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2002 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.tools.ant.taskdefs.optional;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.util.regexp.Regexp;
import org.apache.tools.ant.types.RegularExpression;
import org.apache.tools.ant.types.Substitution;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.util.FileUtils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintWriter;

import java.util.Vector;

/***
 * <pre>
 * Task to do regular expression string replacements in a text
 * file.  The input file(s) must be able to be properly processed by
 * a Reader instance.  That is, they must be text only, no binary.
 *
 * The syntax of the regular expression depends on the implemtation that
 * you choose to use. The system property <code>ant.regexp.regexpimpl</code>
 * will be the classname of the implementation that will be used (the default
 * is <code>org.apache.tools.ant.util.regexp.JakartaOroRegexp</code> and
 * requires the Jakarta Oro Package).
 *
 * <pre>
 * For jdk  &lt;= 1.3, there are two available implementations:
 *   org.apache.tools.ant.util.regexp.JakartaOroRegexp (the default)
 *        Requires  the jakarta-oro package
 *
 *   org.apache.tools.ant.util.regexp.JakartaRegexpRegexp
 *        Requires the jakarta-regexp package
 *
 * For jdk &gt;= 1.4 an additional implementation is available:
 *   org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp
 *        Requires the jdk 1.4 built in regular expression package.
 * </pre>
 *
 * Usage:
 *
 *   Call Syntax:
 *
 *     &lt;replaceregexp file="file"
 *                    match="pattern" 
 *                    replace="pattern" 
 *                    flags="options"?
 *                    byline="true|false"? &gt;
 *       regexp?
 *       substitution?
 *       fileset*
 *     &lt;/replaceregexp&gt;
 *
 *    NOTE: You must have either the file attribute specified, or at least one fileset subelement
 *    to operation on.  You may not have the file attribute specified if you nest fileset elements
 *    inside this task.  Also, you cannot specify both match and a regular expression subelement at
 *    the same time, nor can you specify the replace attribute and the substitution subelement at
 *    the same time.
 *
 *   Attributes:
 *
 *     file    --&gt; A single file to operation on (mutually exclusive with the fileset subelements)
 *     match   --&gt; The Regular expression to match 
 *     replace --&gt; The Expression replacement string 
 *     flags   --&gt; The options to give to the replacement 
 *                 g = Substitute all occurrences. default is to replace only the first one
 *                 i = Case insensitive match
 *
 *     byline  --&gt; Should this file be processed a single line at a time (default is false)
 *                 "true" indicates to perform replacement on a line by line basis
 *                 "false" indicates to perform replacement on the whole file at once.
 *
 *  Example:
 *
 *     The following call could be used to replace an old property name in a ".properties"
 *     file with a new name.  In the replace attribute, you can refer to any part of the
 *     match expression in parenthesis using backslash followed by a number like '\1'.
 *
 *     &lt;replaceregexp file="test.properties"
 *                    match="MyProperty=(.*)"
 *                    replace="NewProperty=\1"
 *                    byline="true" /&gt;
 *
 * </pre>
 *
 * @author <a href="mailto:mattinger@mindless.com">Matthew Inger</a>
 */
public class ReplaceRegExp extends Task
{

    private File file;
    private String flags;
    private boolean byline;
    private Vector filesets; // Keep jdk 1.1 compliant so others can use this
    private RegularExpression regex;
    private Substitution subs;

    private FileUtils fileUtils = FileUtils.newFileUtils();

    /***
     * Default Constructor
     */
    public ReplaceRegExp()
    {
        super();
        this.file = null;
        this.filesets = new Vector();
        this.flags = "";
        this.byline = false;

        this.regex = null;
        this.subs = null;
    }

    public void setFile(File file)
    {
        this.file = file;
    }

    public void setMatch(String match)
    {
        if (regex != null) {
            throw new BuildException("Only one regular expression is allowed");
        }

        regex = new RegularExpression();
        regex.setPattern(match);
    }

    public void setReplace(String replace)
    {
        if (subs != null) {
            throw new BuildException("Only one substitution expression is allowed");
        }

        subs = new Substitution();
        subs.setExpression(replace);
    }

    public void setFlags(String flags)
    {
        this.flags = flags;
    }

    public void setByLine(String byline)
    {
        Boolean res = Boolean.valueOf(byline);
        if (res == null) {
            res = Boolean.FALSE;
        }
        this.byline = res.booleanValue();
    }

    public void addFileset(FileSet set)
    {
        filesets.addElement(set);
    }

    public RegularExpression createRegexp()
    {
        if (regex != null) {
            throw new BuildException("Only one regular expression is allowed.");
        }

        regex = new RegularExpression();
        return regex;
    }

    public Substitution createSubstitution()
    {
        if (subs != null) {
            throw new BuildException("Only one substitution expression is allowed");
        }

        subs = new Substitution();
        return subs;
    }
    

    protected String doReplace(RegularExpression r,
                               Substitution s,
                               String input,
                               int options)
    {
        String res = input;
        Regexp regexp = r.getRegexp(project);

        if (regexp.matches(input, options))
        {
            res = regexp.substitute(input, s.getExpression(project), options);
        }

        return res;
    }

    /***
     * Perform the replace on the entire file
     */
    protected void doReplace(File f, int options)
        throws IOException
    {
        File parentDir = new File(new File(f.getAbsolutePath()).getParent());
        File temp = fileUtils.createTempFile("replace", ".txt", parentDir);

        FileReader r = null;
        FileWriter w = null;

        try
        {
            r = new FileReader(f);
            w = new FileWriter(temp);

            BufferedReader br = new BufferedReader(r);
            BufferedWriter bw = new BufferedWriter(w);
            PrintWriter pw = new PrintWriter(bw);

            boolean changes = false;

            log("Replacing pattern '" + regex.getPattern(project) + "' with '" + subs.getExpression(project) + 
                "' in '" + f.getPath() + "'" + 
                (byline ? " by line" : "") +
                (flags.length() > 0 ? " with flags: '" + flags + "'" : "") +
                ".",
                Project.MSG_WARN);

            if (byline)
            {
                LineNumberReader lnr = new LineNumberReader(br);
                String line = null;
                
                while ((line = lnr.readLine()) != null)
                {
                    String res = doReplace(regex, subs, line, options);
                    if (! res.equals(line)) {
                        changes = true;
                    }

                    pw.println(res);
                }
                pw.flush();
            }
            else
            {
                int flen = (int)(f.length());
                char tmpBuf[] = new char[flen];
                int numread = 0;
                int totread = 0;
                while (numread != -1 && totread < flen)
                {
                    numread = br.read(tmpBuf, totread, flen);
                    totread += numread;
                }

                String buf = new String(tmpBuf);
                
                String res = doReplace(regex, subs, buf, options);
                if (! res.equals(buf)) {
                    changes = true;
                }

                pw.println(res);
                pw.flush();
            }

            r.close();
            r = null;
            w.close();
            w = null;

            if (changes)
            {
                f.delete();
                temp.renameTo(f);
            }
            else
            {
                temp.delete();
            }
        }
        finally
        {
            try { if (r != null) {
                    r.close();
                  } }
            catch (Exception e) { };

            try { if (w != null) {
                    r.close();
                  } }
            catch (Exception e) { };
        }
    }

    public void execute()
        throws BuildException
    {
        if (regex == null) {
            throw new BuildException("No expression to match.");
        }
        if (subs == null) {
            throw new BuildException("Nothing to replace expression with.");
        }

        if (file != null && filesets.size() > 0) {
            throw new BuildException("You cannot supply the 'file' attribute and filesets at the same time.");
        }


        int options = 0;
        
        if (flags.indexOf('g') != -1) {
            options |= Regexp.REPLACE_ALL;
        }

        if (flags.indexOf('i') != -1) {
            options |= Regexp.MATCH_CASE_INSENSITIVE;
        }

        if (flags.indexOf('m') != -1) {
            options |= Regexp.MATCH_MULTILINE;
        }

        if (flags.indexOf('s') != -1) {
            options |= Regexp.MATCH_SINGLELINE;
        }

        if (file != null && file.exists())
        {
            try
            {
                doReplace(file, options);
            }
            catch (IOException e)
            {
                log("An error occurred processing file: '" + file.getAbsolutePath() + "': " + e.toString(),
                    Project.MSG_ERR);
            }
        }
        else if (file != null)
        {
            log("The following file is missing: '" + file.getAbsolutePath() + "'",
                Project.MSG_ERR);
        }

        int sz = filesets.size();
        for (int i=0;i<sz;i++)
        {
            FileSet fs = (FileSet)(filesets.elementAt(i));
            DirectoryScanner ds = fs.getDirectoryScanner(getProject());
            
            String files[] = ds.getIncludedFiles();
            for (int j=0;j<files.length;j++)
            {
                File f = new File(files[j]);
                if (f.exists())
                {
                    try
                    {
                        doReplace(f, options);
                    }
                    catch (Exception e)
                    {
                        log("An error occurred processing file: '" + f.getAbsolutePath() + "': " + e.toString(),
                            Project.MSG_ERR);
                    }
                }
                else
                {
                    log("The following file is missing: '" + file.getAbsolutePath() + "'",
                        Project.MSG_ERR);
                }
            }
        }
    }

}


