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

//  imports for basic functions
import java.io.*;
import java.util.*;

//  imports for ANT 
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.*;

//  imports for XaLan package
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

//  imports for JDOM API
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
import org.jdom.transform.*;
/**
 * A Task to process via XSLT a set of XML documents. This is
 * useful for building views of XML based documentation.
 * arguments:
 * <ul>
 * <li>basedir
 * <li>destdir
 * <li>style
 * <li>includes
 * <li>excludes
 * </ul>
 * Of these arguments, the <b>basedir</b> and <b>destdir</b> are required.
 * Note: Use of the implicit FileSet is deprecated in favor of a nested fileset element.
 * <p>
 * This task will recursively scan the basedir and destdir
 * looking for XML documents to process via XSLT. Any other files,
 * such as images, or html files in the source directory will be
 * copied into the destination directory.
 *
 * @author <a href="mailto:kvisco@exoffice.com">Keith Visco</a>
 * @author <a href="mailto:rubys@us.ibm.com">Sam Ruby</a>
 * @author <a href="mailto:russgold@acm.org">Russell Gold</a>
 * @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a>
 * @author <a href="mailto:smliu@yahoo.com">Shengmeng Liu</a>
 * @version $Revision: 1.25.2.2 $ $Date: 2001/11/16 20:25:22 $
 */
public class XSLTProcessX extends MatchingTask {

	private File destDir = null;

	private File baseDir = null;

	private String xslFile = null;

	private File inFile = null;

	private File outFile = null;
	
	/* used to support nested fileset */
	private Vector filesets = new Vector(); 

	private boolean force = false;
	
	private String targetExtension = ".html";
	
	/* used to select the appropriate stylesheet from multiple PIs */
	private String media = null;
	
	private Vector params = new Vector();

	/**
	* Adds a set of files (nested fileset attribute).
	*/
	public void addFileset(FileSet set) {
		filesets.addElement(set);
	}//-- addFileset
	
	/**
	 * Set whether to check dependencies, or always generate.
	 **/
	public void setForce(boolean force) {
		this.force = force;
	} //-- setForce

	/**
	 * Set the base directory.
	 **/
	public void setBasedir(File dir) {
		baseDir = dir;
	} //-- setSourceDir

	/**
	 * Set the destination directory into which the XSL result
	 * files should be copied to
	 * @param dirName the name of the destination directory
	 **/
	public void setDestdir(File dir) {
		destDir = dir;
	} //-- setDestDir

	/**
	 * Set the desired file extension to be used for the target
	 * @param name the extension to use
	 **/
	public void setExtension(String name) {
		targetExtension = name;
	} //-- setExtension
	/**
	 * Set the desired media type for stylesheet(PI) to be used for transformation
	 * @param name the media type to use
	 **/
	public void setMedia(String media) {
		this.media = media;
	} //-- setMedia

	/**
	 * Sets the file to use for styling relative to the base directory
	 * of this task.
	 */
	public void setStyle(String xslFile) {
		this.xslFile = xslFile;
	}
	
	/**
	 * Sets an out file
	 */
	public void setOut(File outFile){
		this.outFile = outFile;
	}

	/**
	 * Sets an input xml file to be styled
	 */
	public void setIn(File inFile){
		this.inFile = inFile;
	}
	/**
	 * Sets transformation parameters for stylesheet
	 */
	public Param createParam() {
		Param p = new Param();
		params.addElement(p);
		return p;
	}

	public class Param {
		private String name=null;
		private String expression=null;

		public void setName(String name){
			this.name = name;
		}

		public void setExpression(String expression){
			this.expression = expression;
		}

		public String getName() throws BuildException{
			if(name==null)throw new BuildException("Name attribute is missing.");
			return name;
		}

		public String getExpression() throws BuildException{
			if(expression==null)throw new BuildException("Expression attribute is missing.");
			return expression;
		}
	}

	/**
	 * Executes the task.
	 */
	
	public void execute() throws BuildException {
		DirectoryScanner scanner;
		String[]         list;
		String[]         dirs;
		File       stylesheet = null;

		if (xslFile != null) { 
        	   stylesheet = project.resolveFile(xslFile);
        	   if (!stylesheet.exists()) {
        	   	log("Can't find the specified stylesheet "+stylesheet.getAbsolutePath());
        		throw new BuildException("Can't find the Stylesheet, which should be relative to build.xml basedir!");
        	   }
        	
		}
		/* Disable the implicit baseDir to avoid conflict with fileset's dir
		if (baseDir == null) {
			baseDir = project.resolveFile(".");
		}
		*/
		
		// if we have an in file and out then process them
		if (inFile != null && outFile != null) {
			
			try {
				performXSLT(inFile, outFile, stylesheet);
			}
			catch (Exception ex) {
				throw new BuildException(ex);
			}
			return;
		}

		/*
		 * if we get here, in and out have not been specified, we are
		 * in batch processing mode.
		 */

		//-- make sure Destination directory exists...
		if (destDir == null ) {
			String msg = "destdir attributes must be set!";
			throw new BuildException(msg);
		}
		
		// Make sure either Fileset's dir or baseDir is set, not both
		if (filesets.size()==0 && baseDir == null ||
		    filesets.size()!=0 && baseDir != null    )
			throw new BuildException("Either Fileset's dir or baseDir must be set, not both! Fileset's dir is prefered. ");
			
			
		// Process the files in the filesets
		for (int k=0; k<filesets.size(); k++) {
			FileSet fs = (FileSet) filesets.elementAt(k);
			
			scanner = fs.getDirectoryScanner(project);
			
			// Process all the files marked for styling
			list = scanner.getIncludedFiles();
			for (int i = 0;i < list.length; ++i) 
				process( fs.getDir(project), list[i], destDir, stylesheet );
				
		}


		// DEPRECATED - Process the files in the implicit fileset 
		if (baseDir != null) {
			scanner = getDirectoryScanner(baseDir);
			
			// Process all the files marked for styling
			list = scanner.getIncludedFiles();
			for (int i = 0;i < list.length; ++i) {
				
				process( baseDir, list[i], destDir, stylesheet );
				
				log("DEPRECATED - Use of the implicit FileSet is deprecated.  Use a nested fileset element instead.");
				
			}
		}
		
		
	} //-- execute

	
	/**
	 * Processes the given input XML file and stores the result
	 * in the given resultFile.
	 **/
	private void process(File baseDir, String xmlFile, File destDir,
						 File stylesheet)
		throws BuildException {

		String fileExt=targetExtension;
		File   inFile =null;
		File   outFile=null;

		try {
			// Prepare the input XML File
			inFile = new File(baseDir,xmlFile);
				
			// Prepare the output HTML File
			int dotPos = xmlFile.lastIndexOf('.');
			if(dotPos>0){
				// in case, xmlFile has .xml extension, then get rid of .xml and add .html
				outFile = new File(destDir,xmlFile.substring(0, dotPos)+fileExt);
			}else{
				// in case, xmlFile has no extension, then add .html directly
				outFile = new File(destDir,xmlFile+fileExt);
			}
			// Create all necessary parent directories for the output File
			ensureDirectoryFor(outFile);
				
			performXSLT(inFile, outFile, stylesheet);
						
		}
		catch (Exception ex) {
			// If failed to process document, must delete target document,
			// or it will not attempt to process it the second time
			log("Failed to process " + inFile, Project.MSG_INFO);
			if (outFile != null) {
				outFile.delete();
			}

			throw new BuildException(ex);
		}

	} //-- processXML


	/* Ensure the target File exists(writable), by creating all necessary parent directories */
	private void ensureDirectoryFor( File targetFile ) throws BuildException {
		File directory = new File( targetFile.getParent() );
		if (!directory.exists()) {
			if (!directory.mkdirs()) {
				throw new BuildException("Unable to create directory: "
										 + directory.getAbsolutePath() );
			}
		}
	}//-- ensureDirectoryFor
	
	/** Perform XSLT transformation for inFile(xml) to outFile(html) by applying xslFile(xsl).
	 *  This method can auto detect wether to use the xslFile passed in(if specified) or obtain it from the xmlFile's PI
	 **/
	protected void performXSLT(File inFile, File outFile, File xslFile) throws Exception {
	
		try {	
	   					
			// Create SAXBuilder with SAX and Xerces, no validation
	            	SAXBuilder builder = new SAXBuilder();
	            	// Build the JDOM document from xml File
	            	Document doc = builder.build(inFile);
	            	
	            	// Get the Document level PI, if xslFile is not specified externally
	            	if (xslFile == null)	{
	            		// Extract the xsl File from XML inFile
	   			String xslPath = null;	
				List content = doc.getContent();
				Iterator i = content.iterator();
				    	    
			  	while (i.hasNext()) {
			    	    Object o = i.next();
			    	    if (o instanceof ProcessingInstruction) { 
			    	    	// process the PI elements
			    	        ProcessingInstruction pi = (ProcessingInstruction) o;
			    	        
			      		if (media == null || media.equals(pi.getValue("media")))  {
			      			// if media attribute is not specified or matched
			    	    	     	xslPath = pi.getValue("href");
				      		break;  // the first match is used
			      		}
			      	    }
			      		
			        }
			        if (xslPath == null) {
        	   			log("No matched PI found for stylesheet in "+inFile.getAbsolutePath());
        				throw new Exception("No matched PI for stylesheet!");
        	   		}
        	   		
			        xslFile = new File( inFile.getParent(), xslPath);
			        
			        if (!xslFile.exists()) {
       	   				log("specified stylesheet does not exist! path="+xslFile.getAbsolutePath());
       					throw new Exception("specified stylesheet does not exist, which should be relative to the XML file!");
       	   			}
			}
		       
		        
		        // obtain the last modified dates for xml, xsl and html to decide if transform is necessary
		        
		        long xmlLastModified = inFile.lastModified();
			long xslLastModified = xslFile.lastModified();
			long outLastModified = outFile.lastModified();
			
			
			if (force ||
				xmlLastModified  > outLastModified ||
				xslLastModified  > outLastModified )  {
					
			        // Create a transformer with this xsl File, no caching
		    		Transformer transformer = TransformerFactory.newInstance()
	        						.newTransformer(new StreamSource(xslFile));
	        						
	        		Enumeration e = params.elements();
	        		while( e.hasMoreElements()) {
	        			Param p = (Param)e.nextElement();
                       			transformer.setParameter( p.name, p.expression);
                       		}
	        		
	        		transformer.transform(new JDOMSource(doc), new StreamResult(outFile));
	        		
	        		log("xml File = "    + inFile.getAbsolutePath());
		        	log("xsl File ="     + xslFile.getAbsolutePath());
				log("result File = " + outFile.getAbsolutePath());
		        
        		}else 
        			log("Skip transformation for "+inFile.getAbsolutePath());
        		
        		
	            	
            	} catch (Exception e) {
			throw e;
		}
		
	}  //-- performXSLT

  // end of the task class 
}

