/*
 * 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.starteam;

import java.util.Hashtable;
import java.util.StringTokenizer;

import com.starbase.starteam.Folder;
import com.starbase.starteam.Label;
import com.starbase.starteam.StarTeamFinder;
import com.starbase.starteam.View;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;

/**
 * FileBasedTask.java
 * This abstract class is the base for any tasks that are tree-based, that
 * is, for tasks which iterate over a tree of folders in StarTeam which
 * is reflected in a tree of folder the local machine.
 *
 * This class provides the tree-iteration functionality.  Derived classes
 * will implement their specific task functionality by the visitor pattern,
 * specifically by implementing the method
 * <code>visit(Folder rootStarteamFolder, java.io.File rootLocalFolder)</code>
 *
 * Created: Sat Dec 15 16:55:19 2001
 *
 * @author <a href="mailto:stevec@ignitesports.com">Steve Cohen</a>
 * @version 1.0
 * @see <A HREF="http://www.starbase.com/">StarBase Web Site</A>
 */

public abstract class TreeBasedTask extends StarTeamTask {


    ///////////////////////////////////////////////////////////////
    // default values for attributes.
    ///////////////////////////////////////////////////////////////
    /**
     * This constant sets the filter to include all files. This default has
     * the same result as <CODE>setIncludes("*")</CODE>.
     *
     * @see #getIncludes()
     * @see #setIncludes(String includes)
     */
    public final static String DEFAULT_INCLUDESETTING = "*";

    /**
     * This disables the exclude filter by default. In other words, no files
     * are excluded. This setting is equivalent to
     * <CODE>setExcludes(null)</CODE>.
     *
     * @see #getExcludes()
     * @see #setExcludes(String excludes)
     */
    public final static String DEFAULT_EXCLUDESETTING = null;

    //ATTRIBUTES settable from ant.

    /**
     * The root folder of the operation in StarTeam.
     */
    private String rootStarteamFolder = "/";

    /**
     * The local folder corresponding to starteamFolder.  If not specified
     * the Star Team defalt folder will be used.
     */
    private String rootLocalFolder = null;

    /**
     * All files that fit this pattern are checked out.
     */
    private String includes = DEFAULT_INCLUDESETTING;

    /**
     * All files fitting this pattern are ignored.
     */
    private String excludes = DEFAULT_EXCLUDESETTING;

    /**
     * StarTeam label on which to perform task.
     */
    private String label = null;

    /**
     * Set recursion to false to check out files in only the given folder
     * and not in its subfolders.
     */
    private boolean recursive = true;

    /**
     * If forced set to true, files in the target directory will
     * be processed regardless of status in the repository.
     * Usually this should be  true if rootlocalfolder is set
     * because status will be relative to the default folder, not
     * to the one being processed.
     */
    private boolean forced = false;


    ///////////////////////////////////////////////////////////////
    // GET/SET methods.
    // Setters, of course are where ant user passes in values.
    ///////////////////////////////////////////////////////////////

    /**
     * Set the root folder in the Starteam repository for this operation
     * @param rootStarteamFolder the root folder
     */
    public void setRootStarteamFolder(String rootStarteamFolder) {
        this.rootStarteamFolder = rootStarteamFolder;
    }

    /**
     * returns the root folder in the Starteam repository
     * used for this operation
     * @return the root folder in use
     */
    public String getRootStarteamFolder() {
        return this.rootStarteamFolder;
    }

    /**
     * Set the local folder corresponding to the
     * starteam folder for this operation.
     * If not specified, the StarTeam default will be used
     * the default is used.
     * @param rootLocalFolder the local folder that will mirror
     *                        this.rootStarteamFolder
     */
    public void setRootLocalFolder(String rootLocalFolder) {
        this.rootLocalFolder = rootLocalFolder;
    }

    /**
     * Returns the local folder specified by the user,
     * corresponding to the starteam folder for this operation.
     * or null if not specified
     * @return the local folder that mirrors this.rootStarteamFolder
     */
    public String getRootLocalFolder() {
        return this.rootLocalFolder;
    }

    /**
     * sets the pattern of files to be included.  See setExcludes() for a
     * description
     * @param includes A string of filter patterns to include. Separate the
     *                 patterns by spaces.
     * @see #getIncludes()
     * @see #setExcludes(String excludes)
     * @see #getExcludes()
     */
    public void setIncludes(String includes) {
        this.includes = includes;
    }

    /**
     * Gets the patterns from the include filter. Rather that duplicate the
     * details of AntStarTeanCheckOut's filtering here, refer to these
     * links:
     *
     * @return A string of filter patterns separated by spaces.
     * @see #setIncludes(String includes)
     * @see #setExcludes(String excludes)
     * @see #getExcludes()
     */
    public String getIncludes() {
        return includes;
    }

    /**
     * Sets the exclude filter. When filtering files, AntStarTeamCheckOut
     * uses an unmodified version of <CODE>DirectoryScanner</CODE>'s
     * <CODE>match</CODE> method, so here are the patterns straight from the
     * Ant source code:
     * <BR><BR>
     * Matches a string against a pattern. The pattern contains two special
     * characters:
     * <BR>'*' which means zero or more characters,
     * <BR>'?' which means one and only one character.
     * <BR><BR>
     *  For example, if you want to check out all files except .XML and
     * .HTML files, you would put the following line in your program:
     * <CODE>setExcludes("*.XML,*.HTML");</CODE>
     * Finally, note that filters have no effect on the <B>directories</B>
     * that are scanned; you could not skip over all files in directories
     * whose names begin with "project," for instance.
     * <BR><BR>
     * Treatment of overlapping inlcudes and excludes: To give a simplistic
     * example suppose that you set your include filter to "*.htm *.html"
     * and your exclude filter to "index.*". What happens to index.html?
     * AntStarTeamCheckOut will not check out index.html, as it matches an
     * exclude filter ("index.*"), even though it matches the include
     * filter, as well.
     * <BR><BR>
     * Please also read the following sections before using filters:
     *
     * @param excludes A string of filter patterns to exclude. Separate the
     *                 patterns by spaces.
     * @see #setIncludes(String includes)
     * @see #getIncludes()
     * @see #getExcludes()
     */
    public void setExcludes(String excludes) {
        this.excludes = excludes;
    }

    /**
     * Gets the patterns from the exclude filter. Rather that duplicate the
     * details of AntStarTeanCheckOut's filtering here, refer to these
     * links:
     *
     * @return A string of filter patterns separated by spaces.
     * @see #setExcludes(String excludes)
     * @see #setIncludes(String includes)
     * @see #getIncludes()
     */
    public String getExcludes() {
        return excludes;
    }

    /**
     * protected function to allow subclasses to set the label (or not).
     * sets the StarTeam label
     *
     * @param label name of the StarTeam label to be set
     */
    protected void _setLabel(String label) {
        if (null != label) {
            label = label.trim();
            if (label.length() > 0) {
                this.label = label;
            }
        }
    }

    /**
     * Get the value of recursive.
     * @return value of recursive.
     */
    public boolean isRecursive() {
        return this.recursive;
    }

    /**
     * Set the value of recursive.
     * @param v  Value to assign to recursive.
     */
    public void setRecursive(boolean v) {
        this.recursive = v;
    }

    /**
     * Get the value of forced.
     * @return value of forced.
     */
    public boolean isForced() {
        return this.forced;
    }

    /**
     * Set the value of forced.
     * @param v  Value to assign to forced.
     */
    public void setForced(boolean v) {
        this.forced = v;
    }

    ///////////////////////////////////////////////////////////////
    // INCLUDE-EXCLUDE processing
    ///////////////////////////////////////////////////////////////

    /**
     * Look if the file should be processed by the task.
     * Don't process it if it fits no include filters or if
     * it fits an exclude filter.
     * @param pName the item name to look for being included.
     * @return whether the file should be checked out or not.
     */
    protected boolean shouldProcess(String pName) {
        boolean includeIt = matchPatterns(getIncludes(), pName);
        boolean excludeIt = matchPatterns(getExcludes(), pName);
        return (includeIt && !excludeIt);
    }

    /**
     * Convenience method to see if a string match a one pattern
     * in given set of space-separated patterns.
     * @param patterns the space-separated list of patterns.
     * @param pName the name to look for matching.
     * @return whether the name match at least one pattern.
     */
    protected boolean matchPatterns(String patterns, String pName) {
        if (patterns == null) {
            return false;
        }
        StringTokenizer exStr = new StringTokenizer(patterns, ",");
        while (exStr.hasMoreTokens()) {
            if (DirectoryScanner.match(exStr.nextToken(), pName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method does the work of opening the supplied  Starteam view and
     * calling the <code>visit()</code> method to perform the task.
     *
     * @exception BuildException if any error occurs in the processing
     * @see <code>visit()</code>
     */

    public void execute() throws BuildException {
        try {
            testPreconditions();

            View snapshot = openView();

            // find the starteam folder specified to be the root of the
            // operation.  Throw if it can't be found.
            Folder starteamrootfolder =
                    StarTeamFinder.findFolder(snapshot.getRootFolder(),
                            this.rootStarteamFolder);

            if (null == starteamrootfolder) {
                throw new BuildException(
                        "Unable to find root folder in repository.");
            }

            // set the local folder.
            java.io.File localrootfolder;
            if (null == this.rootLocalFolder) {
                // use Star Team's default
                localrootfolder =
                        new java.io.File(starteamrootfolder.getPath());
            } else {
                // force StarTeam to use our folder
                localrootfolder = new java.io.File(getRootLocalFolder());
                log("overriding local folder to " + localrootfolder);
            }

            // Inspect everything in the root folder and then recursively
            visit(starteamrootfolder, localrootfolder);
        } catch (Exception e) {
            throw new BuildException(e);
        }
    }

    /**
     * Helper method calls on the StarTeam API to retrieve an ID number
     * for the specified view, corresponding to this.label.
     * @param v the <code>View</code> in which to search for <code>this.label</code>
     * @return the ID number corresponding to <code>this.label</code> or -1 if
     *         no label was provided.
     * @exception BuildException if <code>this.label</code> does not correspond
     *                           to any label in the supplied view
     */
    protected int getLabelID(View v) throws BuildException {
        if (null != this.label) {
            Label[] allLabels = v.getLabels();
            for (int i = 0; i < allLabels.length; i++) {
                if (allLabels[i].getName().equals(this.label)) {
                    return allLabels[i].getID();
                }
            }
            throw new BuildException("Error: label "
                    + this.label
                    + " does not exist in view");
        }
        return -1;
    }


    /**
     * Derived classes must override this class to define actual processing
     * to be performed on each folder in the tree defined for the task
     *
     * @param rootStarteamFolder the StarTeam folderto be visited
     * @param rootLocalFolder the local mapping of rootStarteamFolder
     */
    protected abstract void visit(Folder rootStarteamFolder,
                                  java.io.File rootLocalFolder)
            throws BuildException;


    /**
     * Derived classes must override this method to define tests for
     * any preconditons required by the task.  This method is called at
     * the beginning of the execute() method.
     *
     * @exception BuildException throw if any fatal error exists in the
     * parameters supplied.  If there is a non-fatal condition, just writing
     * to the log may be appropriate.
     * @see <code>execute()</code>
     */
    protected abstract void testPreconditions() throws BuildException;

    /**
     * Gets the collection of the local file names in the supplied directory.
     * We need to check this collection against what we find in Starteam to
     * understand what we need to do in order to synch with the repository.
     *
     * @param localFolder - the local folder to scan
     * @return an "identity" hashtable whose keys each represent a file or
     * directory in localFolder.
     */
    protected static Hashtable listLocalFiles(java.io.File localFolder) {

        Hashtable localFileList = new Hashtable();
        // we can't use java 2 collections so we will use an identity
        // Hashtable to  hold the file names.  We only care about the keys,
        // not the values (which will all be "").

        if (localFolder.exists()) {
            String[] localFiles = localFolder.list();
            for (int i = 0; i < localFiles.length; i++) {
                localFileList.put(localFolder.toString() +
                        java.io.File.separatorChar + localFiles[i], "");
            }
        }
        return localFileList;
    }

    /**
     * Removes from the collection of the local file names
     * the supplied name of a processed file.  When we are done, only
     * files not in StarTeam will remain in localFiles.
     * @param localFiles a <code>Hashtable</code> value
     * @param thisfile file to remove from list.
     * @return true if file was removed, false if it wasn't found.
     */
    protected boolean delistLocalFile(Hashtable localFiles, java.io.File thisfile) {
        return null != localFiles.remove(thisfile.toString());
    }
}





