/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.ant;

import org.apache.tools.ant.*;
import proguard.*;

import java.io.IOException;
import java.util.*;

/**
 * This Task allows to define a ProGuard configuration from Ant.
 *
 * @author Eric Lafortune
 */
public class ConfigurationTask extends Task
{
    protected final Configuration configuration = new Configuration();


    /**
     * Adds the contents of this configuration task to the given configuration.
     * @param configuration the configuration to be extended.
     */
    public void appendTo(Configuration configuration)
    {
        // Append all of these configuration entries to the given configuration.
        configuration.programJars                  = extendClassPath(configuration.programJars,
                                                                     this.configuration.programJars);

        configuration.libraryJars                  = extendClassPath(configuration.libraryJars,
                                                                     this.configuration.libraryJars);

        configuration.keep                         = extendClassSpecifications(configuration.keep,
                                                                               this.configuration.keep);

        configuration.keepDirectories              = extendList(configuration.keepDirectories,
                                                                this.configuration.keepDirectories);

        configuration.whyAreYouKeeping             = extendClassSpecifications(configuration.whyAreYouKeeping,
                                                                               this.configuration.whyAreYouKeeping);

        configuration.optimizations                = extendClassSpecifications(configuration.optimizations,
                                                                               this.configuration.optimizations);

        configuration.assumeNoSideEffects          = extendClassSpecifications(configuration.assumeNoSideEffects,
                                                                               this.configuration.assumeNoSideEffects);

        configuration.assumeNoExternalSideEffects  = extendClassSpecifications(configuration.assumeNoExternalSideEffects,
                                                                              this.configuration.assumeNoExternalSideEffects);

        configuration.assumeNoEscapingParameters   = extendClassSpecifications(configuration.assumeNoEscapingParameters,
                                                                               this.configuration.assumeNoEscapingParameters);

        configuration.assumeNoExternalReturnValues = extendClassSpecifications(configuration.assumeNoExternalReturnValues,
                                                                               this.configuration.assumeNoExternalReturnValues);

        configuration.keepPackageNames             = extendList(configuration.keepPackageNames,
                                                                this.configuration.keepPackageNames);

        configuration.keepAttributes               = extendList(configuration.keepAttributes,
                                                                this.configuration.keepAttributes);

        configuration.adaptClassStrings            = extendList(configuration.adaptClassStrings,
                                                                this.configuration.adaptClassStrings);

        configuration.adaptResourceFileNames       = extendList(configuration.adaptResourceFileNames,
                                                                this.configuration.adaptResourceFileNames);

        configuration.adaptResourceFileContents    = extendList(configuration.adaptResourceFileContents,
                                                                this.configuration.adaptResourceFileContents);

        configuration.note                         = extendList(configuration.note,
                                                                this.configuration.note);

        configuration.warn                         = extendList(configuration.warn,
                                                                this.configuration.warn);
    }


    // Ant task nested elements.

    public void addConfiguredInjar(ClassPathElement classPathElement)
    {
        configuration.programJars = extendClassPath(configuration.programJars,
                                                    classPathElement,
                                                    false);
    }


    public void addConfiguredOutjar(ClassPathElement classPathElement)
    {
        configuration.programJars = extendClassPath(configuration.programJars,
                                                    classPathElement,
                                                    true);
    }


    public void addConfiguredLibraryjar(ClassPathElement classPathElement)
    {
        configuration.libraryJars = extendClassPath(configuration.libraryJars,
                                                    classPathElement,
                                                    false);
    }


    public void addConfiguredKeepdirectory(FilterElement filterElement)
    {
        configuration.keepDirectories = extendFilter(configuration.keepDirectories,
                                                     filterElement);
    }


    public void addConfiguredKeepdirectories(FilterElement filterElement)
    {
        configuration.keepDirectories = extendFilter(configuration.keepDirectories,
                                                     filterElement);
    }


    public void addConfiguredKeep(KeepSpecificationElement keepSpecificationElement)
    {
        configuration.keep = extendKeepSpecifications(configuration.keep,
                                                      keepSpecificationElement,
                                                      true,
                                                      false);
    }


    public void addConfiguredKeepclassmembers(KeepSpecificationElement keepSpecificationElement)
    {
        configuration.keep = extendKeepSpecifications(configuration.keep,
                                                      keepSpecificationElement,
                                                      false,
                                                      false);
    }


    public void addConfiguredKeepclasseswithmembers(KeepSpecificationElement keepSpecificationElement)
    {
        configuration.keep = extendKeepSpecifications(configuration.keep,
                                                      keepSpecificationElement,
                                                      true,
                                                      true);
    }


    public void addConfiguredKeepnames(KeepSpecificationElement keepSpecificationElement)
    {
        // Set the shrinking flag, based on the name (backward compatibility).
        keepSpecificationElement.setAllowshrinking(true);

        configuration.keep = extendKeepSpecifications(configuration.keep,
                                                      keepSpecificationElement,
                                                      true,
                                                      false);
    }


    public void addConfiguredKeepclassmembernames(KeepSpecificationElement keepSpecificationElement)
    {
        // Set the shrinking flag, based on the name (backward compatibility).
        keepSpecificationElement.setAllowshrinking(true);

        configuration.keep = extendKeepSpecifications(configuration.keep,
                                                      keepSpecificationElement,
                                                      false,
                                                      false);
    }


    public void addConfiguredKeepclasseswithmembernames(KeepSpecificationElement keepSpecificationElement)
    {
        // Set the shrinking flag, based on the name (backward compatibility).
        keepSpecificationElement.setAllowshrinking(true);

        configuration.keep = extendKeepSpecifications(configuration.keep,
                                                      keepSpecificationElement,
                                                      true,
                                                      true);
    }


    public void addConfiguredWhyareyoukeeping(ClassSpecificationElement classSpecificationElement)
    {
        configuration.whyAreYouKeeping = extendClassSpecifications(configuration.whyAreYouKeeping,
                                                                   classSpecificationElement);
    }


    public void addConfiguredAssumenosideeffects(ClassSpecificationElement classSpecificationElement)
    {
        configuration.assumeNoSideEffects = extendClassSpecifications(configuration.assumeNoSideEffects,
                                                                      classSpecificationElement);
    }


    public void addConfiguredAssumenoexternalsideeffects(ClassSpecificationElement classSpecificationElement)
    {
        configuration.assumeNoExternalSideEffects = extendClassSpecifications(configuration.assumeNoExternalSideEffects,
                                                                      classSpecificationElement);
    }


    public void addConfiguredAssumenoescapingparameters(ClassSpecificationElement classSpecificationElement)
    {
        configuration.assumeNoEscapingParameters = extendClassSpecifications(configuration.assumeNoEscapingParameters,
                                                                             classSpecificationElement);
    }


    public void addConfiguredAssumenoexternalreturnvalues(ClassSpecificationElement classSpecificationElement)
    {
        configuration.assumeNoExternalReturnValues = extendClassSpecifications(configuration.assumeNoExternalReturnValues,
                                                                               classSpecificationElement);
    }


    public void addConfiguredOptimizations(FilterElement filterElement)
    {
        addConfiguredOptimization(filterElement);
    }


    public void addConfiguredOptimization(FilterElement filterElement)
    {
        configuration.optimizations = extendFilter(configuration.optimizations,
                                                   filterElement);
    }


    public void addConfiguredKeeppackagename(FilterElement filterElement)
    {
        configuration.keepPackageNames = extendFilter(configuration.keepPackageNames,
                                                      filterElement,
                                                      true);
    }


    public void addConfiguredKeeppackagenames(FilterElement filterElement)
    {
        configuration.keepPackageNames = extendFilter(configuration.keepPackageNames,
                                                      filterElement,
                                                      true);
    }


    public void addConfiguredKeepattributes(FilterElement filterElement)
    {
        addConfiguredKeepattribute(filterElement);
    }


    public void addConfiguredKeepattribute(FilterElement filterElement)
    {
        configuration.keepAttributes = extendFilter(configuration.keepAttributes,
                                                    filterElement);
    }


    public void addConfiguredAdaptclassstrings(FilterElement filterElement)
    {
        configuration.adaptClassStrings = extendFilter(configuration.adaptClassStrings,
                                                       filterElement, true);
    }


    public void addConfiguredAdaptresourcefilenames(FilterElement filterElement)
    {
        configuration.adaptResourceFileNames = extendFilter(configuration.adaptResourceFileNames,
                                                            filterElement);
    }


    public void addConfiguredAdaptresourcefilecontents(FilterElement filterElement)
    {
        configuration.adaptResourceFileContents = extendFilter(configuration.adaptResourceFileContents,
                                                               filterElement);
    }


    public void addConfiguredDontnote(FilterElement filterElement)
    {
        configuration.note = extendFilter(configuration.note, filterElement, true);
    }


    public void addConfiguredDontwarn(FilterElement filterElement)
    {
        configuration.warn = extendFilter(configuration.warn, filterElement, true);
    }


    public void addConfiguredConfiguration(ConfigurationElement configurationElement)
    {
        configurationElement.appendTo(configuration);
    }


    // Implementations for Task.

    public void addText(String text) throws BuildException
    {
        try
        {
            Project project = getProject();

            // Replace Ant-style properties ('${...}').
            String arg = project.replaceProperties(text);

            // Get the combined system properties and Ant properties, for
            // replacing ProGuard-style properties ('<...>').
            Properties properties = new Properties();
            properties.putAll(project.getProperties());

            ConfigurationParser parser = new ConfigurationParser(arg,
                                                                 "embedded configuration",
                                                                 project.getBaseDir(),
                                                                 properties);

            try
            {
                parser.parse(configuration);
            }
            catch (ParseException ex)
            {
                throw new BuildException(ex.getMessage());
            }
            finally
            {
                parser.close();
            }
        }
        catch (IOException ex)
        {
            throw new BuildException(ex.getMessage());
        }
    }


    // Small utility methods.

    private ClassPath extendClassPath(ClassPath        classPath,
                                      ClassPathElement classPathElement,
                                      boolean          output)
    {
        if (classPath == null)
        {
            classPath = new ClassPath();
        }

        classPathElement.appendClassPathEntriesTo(classPath,
                                                  output);

        return classPath;
    }


    private ClassPath extendClassPath(ClassPath classPath,
                                      ClassPath additionalClassPath)
    {
        if (additionalClassPath != null)
        {
            if (classPath == null)
            {
                classPath = new ClassPath();
            }

            classPath.addAll(additionalClassPath);
        }

        return classPath;
    }


    private List extendKeepSpecifications(List                     keepSpecifications,
                                          KeepSpecificationElement keepSpecificationElement,
                                          boolean                  markClasses,
                                          boolean                  markClassesConditionally)
    {
        if (keepSpecifications == null)
        {
            keepSpecifications = new ArrayList();
        }

        keepSpecificationElement.appendTo(keepSpecifications,
                                          markClasses,
                                          markClassesConditionally);

        return keepSpecifications;
    }


    private List extendClassSpecifications(List                      classSpecifications,
                                           ClassSpecificationElement classSpecificationElement)
    {
        if (classSpecifications == null)
        {
            classSpecifications = new ArrayList();
        }

        classSpecificationElement.appendTo(classSpecifications);

        return classSpecifications;
    }


    private List extendClassSpecifications(List classSpecifications,
                                           List additionalClassSpecifications)
    {
        if (additionalClassSpecifications != null)
        {
            if (classSpecifications == null)
            {
                classSpecifications = new ArrayList();
            }

            classSpecifications.addAll(additionalClassSpecifications);
        }

        return classSpecifications;
    }


    private List extendFilter(List          filter,
                              FilterElement filterElement)
    {
        return extendFilter(filter, filterElement, false);
    }


    private List extendFilter(List          filter,
                              FilterElement filterElement,
                              boolean       internal)
    {
        if (filter == null)
        {
            filter = new ArrayList();
        }

        filterElement.appendTo(filter, internal);

        return filter;
    }


    private List extendList(List list,
                            List additionalList)
    {
        if (additionalList != null)
        {
            if (list == null)
            {
                list = new ArrayList();
            }

            list.addAll(additionalList);
        }

        return list;
    }
}
