package org.jboss.verifier;

import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.util.StringTokenizer;

// Vector is used instead of Collection to maintain source
// compatibility with other Ant tasks.
import java.util.Vector;
import java.util.Enumeration;

// Ant imports
import org.apache.tools.ant.Task;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.DirectoryScanner;

// Project imports
import org.jboss.verifier.event.VerificationListener;
import org.jboss.verifier.event.VerificationEvent;

import org.jboss.metadata.XmlFileLoader;


/*
 *
 * $Id$
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Comments and remarks to:
 * Maurice le Rutte, info@lerutte.net or mplr@oce.nl
 */

/*
 * Deployment note:
 *
 * In Ant's classpath (e.g. lib directory) you'll need to store the
 * additional libraries jpl-util-0_5b.jar, metadata.jar and verifier.jar
 */

 /**
  * Implementation of Ant task that verifies an ejb jar file.
  * <p>The task takes the following arguments:
  * <ul>
  * <li>file File to verify.
  * <li>A nested fileset
  * <li>A classpath
  * </ul>
  *
  * Either a file or a fileset must be specified (not both).
  * The classpath is optional and is used to load additional
  * classes referenced by the beans.
  *
  * <p>
  * Example:<pre>
  * &lt;taskdef name='verify' classname='org.jboss.verifier.AntVerifier'/&gt;
  *
  * &lt;verify&gt;
  * 	&lt;fileset dir='${build.lib}'&gt;
  * 		&lt;include name='*.ear'/&gt;
  * 	&lt;/fileset&gt;
  *
  * 	&lt;classpath&gt;
  * 		&lt;fileset dir='${lib.dir}'/&gt;
  * 	&lt;/classpath&gt;
  * &lt;/verify&gt;</pre>
  */
public class AntVerifier extends Task {
	/** Code returned by verifier if file was ok. */
    private final static int OK      = 0;
    /** Code returned by verifier if file contained warnings. */
    private final static int WARNING = 1;

	/** Value returned by verifier. Set by listener. */
    int returnCode;

	/** List of all filesets to verify. */
	private Vector filesets = new Vector();

	/** File to verify. */
	private File  	file;

	/** Classpath to pass to verifier. */
	private Path   classpath;

	/**
	 * Set file to verify. Either this property has to be
	 * set or a fileset has to be set.
	 *
	 * @param file File to verify.
	 */
	public void setFile(File file) {
		this.file = file;
	}


	/**
	 * Add a fileset to verify. Either this property has to be
	 * set or a file has to be set.
	 *
	 * @param set Property file to verify.
	 */
    public void addFileset(FileSet set) {
        filesets.addElement(set);
    }

	/**
	 * Add a nested classpath.
	 */
    public Path createClasspath() {
        if (classpath == null) {
            classpath = new Path(project);
        }
        return classpath.createPath();
    }


	/**
	 * Execute the task.
	 */
	public void execute() throws BuildException {
		Vector files;

		files = new Vector();

		if (filesets.size() > 0) {
			Enumeration enum;

			// Can't have both ways.
			if (file != null) {
				throw new BuildException("Specify either a fileset or a file, not both.");
			}

			enum = filesets.elements();


			while (enum.hasMoreElements()) {
				FileSet fs = (FileSet)enum.nextElement();
	            File base = fs.getDir(project);
	            DirectoryScanner ds = fs.getDirectoryScanner(project);
	            String[] fileNames;

	            fileNames = ds.getIncludedFiles();

	            for (int i = 0; i < fileNames.length; i++) {
	            	files.add(new File(base,fileNames[i]));
	            }
			}
		} else {
			// Must have one way.
			if (file == null) {
				throw new BuildException("Specify at least a fileset or a file.");
			}

			files.add(file);
		}

		verify(files);
	}

	/**
	 * Change the classpath into a list of URLs to be passed
	 * to the verifier's classloader.
	 *
	 * @return <code>self.classpath.elements->collect(f : File | f.toURL)</code>
	 */
	private URL[] getClassPathURLs() throws BuildException {
		URL[] result;

		try {
			if (classpath != null) {
				int count;
				String[] elements;

				log("Using classpath: " + classpath,Project.MSG_VERBOSE);

				elements = classpath.list();
				result = new URL[elements.length];

				for (int i = 0; i < elements.length; i++) {
					result[i] = new File(elements[i]).toURL();
				}
			} else {
				result = new URL[0];
			}
		} catch(MalformedURLException e) {
			throw new BuildException("Error constructing classpath",e);
		}

		return result;
	}

	/**
	 * Verify a set of files.
	 *
	 * @param files Files to verify.
	 */
	private void verify(Vector files) throws BuildException {
		Enumeration enum;
		returnCode = OK;
		URL[] urls;

		urls = getClassPathURLs();

		enum = files.elements();

		while (enum.hasMoreElements()) {
			verify((File)enum.nextElement(),urls);
		}

		if (returnCode != OK) {
			throw new BuildException("Verification failed");
		}
	}

	/**
	 * Verify a file. The file can have an additional classpath.
	 *
	 * @param file EJB file to check.
	 * @param classURLs list of additional classpaths.
	 */
	private void verify(File file,URL[] classURLs) throws BuildException {
		URL[] urls;

		if (file.exists() == false) {
			throw new BuildException("Could not verify '" + file.getAbsolutePath() +
				"': File not found.");
		}

		if (file.isFile() == false) {
			throw new BuildException("Could not verify '" + file.getAbsolutePath() +
				"': Not a file.");
		}

		log("Verifying " + file.getAbsolutePath(),Project.MSG_VERBOSE);

		try {
		    ClassLoader cl;
		    Class clazz;

			urls = new URL[1 + classURLs.length];
			urls[0] = file.toURL();
			System.arraycopy(classURLs,0,urls,1,classURLs.length);
		    cl = new URLClassLoader(urls,
		    			Thread.currentThread().getContextClassLoader());

		    XmlFileLoader xfl     = new XmlFileLoader();

		    BeanVerifier verifier = new BeanVerifier();

		    xfl.setClassLoader(cl);
		    verifier.addVerificationListener(new Listener(file));

		    verifier.verify(urls[0], xfl.load(),cl);
		} catch(NoClassDefFoundError e) {
			throw new BuildException("Class '" + e.getMessage() +
			 "' not found in classpath.");
		} catch(Exception e) {
		    throw new BuildException("Problem starting the application",e);
		}
	}

	/**
	 * Verification listener.
	 */
	class Listener implements VerificationListener {
		/** Current file. */
		private File file;

		Listener(File file) {
			this.file = file;
		}

		/**
		 * Verification failed.
		 *
		 * @param event Event carrying description of error.
		 */
    	public void specViolation(VerificationEvent event) {
    		StringTokenizer tokenizer;

			log("Verification of " + file + " failed.",Project.MSG_ERR);
    		tokenizer = new StringTokenizer(event.getVerbose(),
				  System.getProperty("line.separator"));

           	// Chop the error into separate lines
           	// and log each one separately.
			while (tokenizer.hasMoreTokens()) {
        		log(tokenizer.nextToken(),Project.MSG_ERR);
        	}

	        returnCode = WARNING;
    	}

		/**
		 * Verification succeeded.
		 */
	    public void beanChecked(VerificationEvent event) {
        	log(event.getMessage(),Project.MSG_INFO);
	    }
	}
}