/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999, 2000 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", "Tomcat", 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;

import java.util.*;
import java.io.*;
import java.util.zip.*;
import org.xml.sax.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import org.apache.tools.ant.types.Path;

public class AntExtensionHelper { 
    
    private static SAXParserFactory parserFactory = null;

    private org.xml.sax.Parser parser;
    private SAXParser saxParser;
    private Locator locator;

    private Project project = null;
    private File extensionDir = null;
    
    private AntClassLoader loader = null;
    
    public static void initExtensionTasks(Project project, 
                                          File extensionDir) { 
        new AntExtensionHelper(project, extensionDir).initExtensionTasks();
    }

    private AntExtensionHelper(Project project, File extensionDir) { 
        this.project = project;
        this.extensionDir = extensionDir;
    }
    
    private void initExtensionTasks() { 
        try {
            saxParser = getParserFactory().newSAXParser();
            parser = saxParser.getParser();
        }
        catch(ParserConfigurationException exc) {
            throw new BuildException("Parser has not been configured correctly", exc);
        }
        catch(SAXException exc) {
            Throwable t = exc.getException();
            if (t instanceof BuildException) {
                throw (BuildException) t;
            }
            throw new BuildException(exc.getMessage(), t);
        }
        
        String[] tskFiles = extensionDir.list(new TskExtensionFilenameFilter());
        
        for (int i = 0; i < tskFiles.length; i++) { 
            
            String taskFileName = extensionDir.getAbsolutePath() + 
                                  File.separator  + tskFiles[i];
            
            project.log("Loading Extension: " + taskFileName, Project.MSG_VERBOSE);
            
            loadFile(taskFileName);
            
        }
    }
    
    private void loadFile(String filename) { 
        ZipFile zipFile = null;
        
        loader = new AntClassLoader(project, new Path(project, filename));
        
        try { 
            zipFile = new ZipFile(filename);
            
            ZipEntry tasklibEntry = zipFile.getEntry("META-INF/tasklib.xml");
            if (tasklibEntry == null) { 
                throw new BuildException("Not a valid Ant-Tasklib: " + filename);
            }
            
            InputStream in = zipFile.getInputStream(tasklibEntry);
            
            try
            {
                saxParser.parse(in, new RootHandler());
            }
            catch(SAXParseException exc) {
                Location location = new Location(filename, 
                                                 exc.getLineNumber(), 
                                                 exc.getColumnNumber());

                Throwable t = exc.getException();
                if (t instanceof BuildException) {
                    BuildException be = (BuildException) t;
                    if (be.getLocation() == Location.UNKNOWN_LOCATION) {
                        be.setLocation(location);
                    }
                    throw be;
                }
                
                throw new BuildException(exc.getMessage(), t, location);
            }
            catch(SAXException exc) {
                Throwable t = exc.getException();
                if (t instanceof BuildException) {
                    throw (BuildException) t;
                }
                throw new BuildException(exc.getMessage(), t);
            }
            
            in.close();
        }
        catch (IOException ex) { 
            throw new BuildException(ex);
        }
        finally { 
            if (zipFile != null) { 
                try { 
                    zipFile.close();
                } catch (IOException ex) { 
                }
            }
        }
        
        loader = null;
    }    

    private static SAXParserFactory getParserFactory() {
        if (parserFactory == null) {
            parserFactory = SAXParserFactory.newInstance();
        }

        return parserFactory;
    }

    private static class TskExtensionFilenameFilter implements FilenameFilter { 
        public boolean accept(File f, String s) { 
            return s.toLowerCase().endsWith(".tsk");
        }
    }

    private class RootHandler extends HandlerBase {
        public void startElement(String tag, AttributeList attrs) throws SAXParseException {
            if (tag.equals("tasklib")) {
                new TasklibHandler(this).init(tag, attrs);
            } else {
                throw new SAXParseException("Config file is not of expected XML type", locator);
            }
        }

        public void setDocumentLocator(Locator locator) {
            AntExtensionHelper.this.locator = locator;
        }
    }

    private class AbstractHandler extends HandlerBase {
        protected DocumentHandler parentHandler;

        public AbstractHandler(DocumentHandler parentHandler) {
            this.parentHandler = parentHandler;

            // Start handling SAX events
            parser.setDocumentHandler(this);
        }

        public void startElement(String tag, AttributeList attrs) throws SAXParseException {
            throw new SAXParseException("Unexpected element \"" + tag + "\"", locator);
        }

        public void characters(char[] buf, int start, int end) throws SAXParseException {
            String s = new String(buf, start, end).trim();

            if (s.length() > 0) {
                throw new SAXParseException("Unexpected text \"" + s + "\"", locator);
            }
        }

        public void endElement(String name) throws SAXException {

            // Let parent resume handling SAX events
            parser.setDocumentHandler(parentHandler);
        }
    }

    private class TasklibHandler extends AbstractHandler {
        public TasklibHandler(DocumentHandler parentHandler) {
            super(parentHandler);
        }

        public void init(String tag, AttributeList attrs) throws SAXParseException {
            String name = null;
            String version = null;
            String home = null;
            
            for (int i = 0; i < attrs.getLength(); i++) {
                String key = attrs.getName(i);
                String value = attrs.getValue(i);

                if (key.equals("name")) {
                    name = value;
                } else if (key.equals("version")) {
                    version = value;
                } else if (key.equals("home")) {
                    home = value;
                } else {
                    throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator);
                }
            }
        }
        
        public void startElement(String name, AttributeList attrs) throws SAXParseException {
            if (name.equals("task")) {
                handleTask(name, attrs);
            } else {
                throw new SAXParseException("Unexpected element \"" + name + "\"", locator);
            }
        }
        
        private void handleTask(String name, AttributeList attrs) throws SAXParseException {
            new TaskHandler(this).init(name, attrs);
        }
    }
    
    private class TaskHandler extends AbstractHandler {
        public TaskHandler(DocumentHandler parentHandler) {
            super(parentHandler);
        }

        public void init(String tag, AttributeList attrs) throws SAXParseException {
            String taskName = null;
            String className = null;
            String version = null;
            String description = null;
            
            for (int i = 0; i < attrs.getLength(); i++) {
                String key = attrs.getName(i);
                String value = attrs.getValue(i);

                if (key.equals("name")) {
                    taskName = value;
                } else if (key.equals("classname")) {
                    className = value;
                } else if (key.equals("version")) {
                    version = value;
                } else if (key.equals("description")) {
                    description = value;
                } else {
                    throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator);
                }
            }
            
            if (taskName == null) {
                throw new SAXParseException("task element appears without a name attribute", locator);
            }
            if (className == null) {
                throw new SAXParseException("task element appears without a classname attribute", locator);
            }
            
            try { 
                Class cls = loader.loadClass(className);
                TaskDefinition taskDef = new TaskDefinition(taskName, cls, description,
                                                            TaskDefinition.TASK_EXTENSION);
                project.addTaskDefinition(taskName, taskDef);
            } catch (ClassNotFoundException ex) { 
                //throw new BuildException("Class " + className + " could not ne found");
            }
        }
    }
}
