//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 IBM Corporation and others.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// Contributors:
// IBM Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.common.xml;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.eclipse.epf.common.CommonPlugin;


/**
 * A wrapper over the XSLT processor bundled with the JRE.
 * 
 * @author Kelvin Low
 * @since 1.0
 */
public class XSLTProcessor {

	// If true, cache the compiled the XSL transformer with the compiled XSL
	// templates.
	private static boolean cacheXSL;

	// Caches the XSL transformers.
	private static Map transformerCache;

	static {
		String cacheXSLProperty = CommonPlugin.getDefault().getString(
				"cacheXSL"); //$NON-NLS-1$
		if (cacheXSLProperty != null && !cacheXSLProperty.startsWith("[")) { //$NON-NLS-1$
			cacheXSL = Boolean.getBoolean(cacheXSLProperty);
		} else {
			cacheXSL = true;
		}
		if (cacheXSL) {
			transformerCache = new HashMap();
		}
	}

	/**
	 * Default private constructor to prevent this class from being
	 * instantiated.
	 */
	private XSLTProcessor() {
	}

	/**
	 * Executes the XSL transformation given the XSL source, XML source, target
	 * output and encoding.
	 * 
	 * @param xslSource
	 *            The XSL source.
	 * @param xmlSource
	 *            The XML source.
	 * @param output
	 *            The output target.
	 * @param params
	 *            The parameters for the XSL transformation.
	 * @param encoding
	 *            The target encoding.
	 */
	public static void transform(Source xslSource, Source xmlSource,
			Writer output, Properties params, String encoding) throws Exception {
		if (xslSource != null && xmlSource != null) {
			Transformer transformer = null;
			String xslFile = xslSource.getSystemId();
			if (cacheXSL && xslFile != null) {
				synchronized (transformerCache) {
					transformer = (Transformer) transformerCache.get(xslFile);
					if (transformer == null) {
						TransformerFactory factory = TransformerFactory
								.newInstance();
						transformer = factory.newTransformer(xslSource);
						transformerCache.put(xslFile, transformer);
					}
				}
			} else {
				TransformerFactory factory = TransformerFactory.newInstance();
				transformer = factory.newTransformer(xslSource);
			}
			if (params != null && params.size() > 0) {
				for (Iterator i = params.keySet().iterator(); i.hasNext();) {
					String paramName = (String) i.next();
					String paramValue = params.getProperty(paramName);
					transformer.setParameter(paramName, paramValue);
				}
			}
			if (encoding != null && encoding.length() > 0) {
				transformer.setOutputProperty("encoding", encoding); //$NON-NLS-1$
			} else {
				transformer.setOutputProperty("encoding", "utf-8"); //$NON-NLS-1$ //$NON-NLS-2$
			}
			transformer.transform(xmlSource, new StreamResult(output));
		}
	}

	/**
	 * Executes the XSL transformation given the XSL source, XML source, target
	 * output and encoding.
	 * 
	 * @param xslSource
	 *            The XSL source.
	 * @param xmlSource
	 *            The XML source.
	 * @param output
	 *            The output target.
	 * @param encoding
	 *            The target encoding.
	 */
	public static void transform(Source xslSource, Source xmlSource,
			Writer output, String encoding) throws Exception {
		transform(xslSource, xmlSource, output, null, encoding);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML source,
	 * target output and encoding.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML source.
	 * @param output
	 *            The output target.
	 * @param params
	 *            The parameters for the XSL transformation.
	 * @param encoding
	 *            The target encoding.
	 */
	public static void transform(String xslUri, Source xmlSource,
			Writer output, Properties params, String encoding) throws Exception {
		InputStream xslInput = getXslInputStream(xslUri);
		if (xslInput != null) {
			StreamSource xslSource = new StreamSource(xslInput);
			xslSource.setSystemId(new File(xslUri));
			transform(xslSource, xmlSource, output, params, encoding);
			try {
				xslInput.close();
			} catch (Exception e) {
			}
		}
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML source,
	 * target output and encoding.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML source.
	 * @param output
	 *            The output target.
	 * @param encoding
	 *            The target encoding.
	 */
	public static void transform(String xslUri, Source xmlSource,
			Writer output, String encoding) throws Exception {
		transform(xslUri, xmlSource, output, null, encoding);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML string,
	 * target output and encoding.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML string.
	 * @param output
	 *            The output target.
	 * @param params
	 *            The parameters for the XSL transformation.
	 * @param encoding
	 *            The target encoding.
	 */
	public static void transform(String xslUri, String xmlStr, Writer output,
			Properties params, String encoding) throws Exception {
		InputStream xslInput = getXslInputStream(xslUri);
		if (xslInput != null) {
			StreamSource xslSource = new StreamSource(xslInput);
			xslSource.setSystemId(new File(xslUri));

			byte[] xml = xmlStr.getBytes("utf-8"); //$NON-NLS-1$
			ByteArrayInputStream xmlInput = new ByteArrayInputStream(xml);
			StreamSource xmlSource = new StreamSource(xmlInput);

			transform(xslSource, xmlSource, output, params, encoding);

			try {
				xslInput.close();
			} catch (Exception e) {
			}
		}
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML string,
	 * target output and encoding.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML string.
	 * @param output
	 *            The output target.
	 * @param encoding
	 *            The target encoding.
	 */
	public static void transform(String xslUri, String xmlStr, Writer output,
			String encoding) throws Exception {
		transform(xslUri, xmlStr, output, null, encoding);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML string,
	 * target output and encoding.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML string.
	 * @param file
	 *            The output file.
	 * @param params
	 *            The parameters for the XSL transformation.
	 * @param encoding
	 *            The target encoding.
	 */
	public static void transform(String xslUri, String xmlStr, File file,
			Properties params, String encoding) throws Exception {
		FileWriter output = new FileWriter(file);
		if (output != null) {
			transform(xslUri, xmlStr, output, params, encoding);
			try {
				output.close();
			} catch (Exception e) {
			}
		}
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML string,
	 * target output and encoding.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML string.
	 * @param file
	 *            The output file.
	 * @param encoding
	 *            The target encoding.
	 */
	public static void transform(String xslUri, String xmlStr, File file,
			String encoding) throws Exception {
		transform(xslUri, xmlStr, file, null, encoding);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML source
	 * and target output.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML source.
	 * @param params
	 *            The parameters for the XSL transformation.
	 * @param output
	 *            The output target.
	 */
	public static void transform(String xslUri, Source xmlSource,
			Properties params, Writer output) throws Exception {
		transform(xslUri, xmlSource, output, params, null);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML source
	 * and target output.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML source.
	 * @param output
	 *            The output target.
	 */
	public static void transform(String xslUri, Source xmlSource, Writer output)
			throws Exception {
		transform(xslUri, xmlSource, output, null, null);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML string
	 * and target output.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML string.
	 * @param params
	 *            The parameters for the XSL transformation.
	 * @param output
	 *            The output target.
	 */
	public static void transform(String xslUri, String xmlStr,
			Properties params, Writer output) throws Exception {
		transform(xslUri, xmlStr, output, params, null);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML string
	 * and target output.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML string.
	 * @param output
	 *            The output target.
	 */
	public static void transform(String xslUri, String xmlStr, Writer output)
			throws Exception {
		transform(xslUri, xmlStr, output, null, null);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML string
	 * and target output file.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML string.
	 * @param params
	 *            The parameters for the XSL transformation.
	 * @param output
	 *            The output target.
	 */
	public static void transform(String xslUri, String xmlStr,
			Properties params, File file) throws Exception {
		transform(xslUri, xmlStr, file, params, null);
	}

	/**
	 * Executes the XSL transformation given the XSL stylesheet URI, XML string
	 * and target output file.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 * @param xmlStr
	 *            The XML string.
	 * @param output
	 *            The output target.
	 */
	public static void transform(String xslUri, String xmlStr, File file)
			throws Exception {
		transform(xslUri, xmlStr, file, null, null);
	}

	/**
	 * Returns the XSL input stream given the XSL stylesheet URI.
	 * 
	 * @param xslURI
	 *            The XSL stylesheet URI.
	 */
	private static InputStream getXslInputStream(String xslUri) {
		InputStream xslInput = null;
		try {
			xslInput = new FileInputStream(xslUri);
		} catch (Exception e) {
			if (xslInput == null) {
				xslInput = XSLTProcessor.class.getClassLoader()
						.getResourceAsStream(xslUri);
			}
		}
		return xslInput;
	}

}
