import java.io.Reader;
import java.io.Writer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;

/**
 * <code>XsltEngine</code> is used to transform any <code>xml</code> input to
 * any plain text output using an <code>xsl</code> stylesheet. If the
 * transformer used by this product has to be changed for any reason, the change
 * is isolated completely to this class. Currently, the transformer of choice
 * is <i>Xalan-J 2.1.0</i> by <a href="http://xml.apache.org/" target="_blank">
 * The Apache XML Project</a>.
 * @since       0.1
 * @version     0.1
 * @author      Bjoern Martin, STZ-IDA
 */
public class XsltEngine implements javax.xml.transform.ErrorListener {

  /**
   * Reference to the reader of the stylesheet used for this transformation
   * process. It is only <code>null</code> if not set yet.
   */
  private Reader  styleReader;
  /**
   * Status information about the stylesheet reader. If this value is
   * <code>0</code>, the reader has not been set yet, otherwise it was set and
   * is expected to be readable.
   * @see #styleReader
   */
  private byte    styleState;
  /**
   * Reference to the reader of the xml source data transformed during this
   * transformation process. It is only <code>null</code> if not set yet.
   */
  private Reader  inputReader;
  /**
   * Status information about the xml source data reader. If this value is
   * <code>0</code>, the reader has not been set yet, otherwise it was set and
   * is expected to be readable.
   * @see #inputReader
   */
  private byte    inputState;
  /**
   * Reference to the writer of the plain text result data generated during this
   * transformation process. It is only <code>null</code> if not set yet.
   */
  private Writer  outputWriter;
  /**
   * Status information about the plain text result data writer. If this value
   * is <code>0</code>, the writer has not been set yet, otherwise it was set
   * and is expected to be writeable.
   * @see #outputWriter
   */
  private byte    outputState;

  /**
   * Base path used during transformation for external referenced files by the
   * stylesheet. This path is mandatory and only set with the stylesheet.
   * @see #setStyle(Reader, String)
   */
  private String  basepath;
  /**
   * Status field to monitor whether an error/fatal error occurs during
   * transformation. This field has to be global, as the handling methods used
   * by this xslt engine might report an error, but still continue with
   * transformation. In that case, this class will report the transformation as
   * not successfully completed, as data might have been lost during the
   * process.
   */
  private boolean noErrorOccured;

  /**
   * Constructor. Takes the status handler as parameter. This constructor makes
   * use of {@link #init(StatusHandler)} for setting up an instance of this
   * class. It will set the status handler and all default values.
   * @param handler The status handler to which this class will report status,
   *        warning, and error messages during setup and transformation.
   * @throws java.lang.IllegalArgumentException, if one or more of the given
   *         arguments are <code>null</code>.
   */
  public XsltEngine() {
    reset();
  }

  /**
   * Sets the stylesheet to be used for transformation. If the given parameter
   * is <code>null</code>, a warning is issued to the status handler and the
   * call is ignored. So if a stylesheet is set already, and another stylesheet
   * is to be set which is <code>null</code> by the time this method is called,
   * the stylesheet set before will apply on the next run.
   * @param style The reader instance containing the stylesheet to be used.
   * @param path The base path where the transformer will look for documents 
   *        and/or stylesheets referenced by the stylesheet set for this
   *        transformation process.
   */
  public void setStyle(java.io.Reader style, String path) {
    if((style != null) && (path != null)) {
      this.styleReader = style;
      this.styleState = 1;
      this.basepath = path;
      status(4, "Set stylesheet and base path [" + this.basepath + "]");
    } else {
      warning("Not possible to set stylesheet and/or base path to 'null'");
    }
  }

  /**
   * Returns the style to be used to transform the document. This method is
   * rather dangerous, as it might render the reader useless for this class if
   * its content is read before the transformation process starts.
   * @return The stylesheet reader to be used for transformation.
   */
  public java.io.Reader getStyle() {
    status(5, "Returned stylesheet");
    return this.styleReader;
  }

  /**
   * Returns the base path to be used to locate relatively linked documents by
   * the stylesheet.
   * @return The base path for the set stylesheet, or <code>null</code>, if not
   *         set yet.
   */
  public String getBasepath() {
    status(5, "Returned base path");
    return this.basepath;
  }

  /**
   * Sets the xml document to be transformed. If the given parameter is <code>
   * null</code>, a warning is issued to the status handler and the call is
   * ignored. So if a document is set already, and another document is to be set
   * which is <code>null</code> by the time this method is called, the document
   * set before will be used instead.
   * @param in A reader instance containing the document that is to be
   *        transformed.
   */
  public void setInput(java.io.Reader in) {
    if(in != null) {
      this.inputReader = in;
      this.inputState = 1;
      status(4, "Set input reader");
    } else {
      warning("Not possible to set input reader to 'null'");
    }
  }

  /**
   * Returns the document to be transformed. This method is rather dangerous, as
   * it might render the reader useless for this class if its content is read
   * before the transformation process starts.
   * @return The document reader set for this class to be used.
   */
  public java.io.Reader getInput() {
    status(5, "Returned input reader");
    return this.inputReader;
  }

  /**
   * Sets the target document the transformation results should go to. If the
   * given parameter is <code>null</code>, a warning is issued to the status
   * handler and the call is ignored. So if a target document is set already,
   * and <code>null</code> is to be set, the document set before will be used
   * instead.
   * @param out A writer instance containing the target document the result
   *        data should be written to.
   */
  public void setOutput(java.io.Writer out) {
    if(out != null) {
      this.outputWriter = out;
      this.outputState = 1;
      status(4, "Set output writer");
    } else {
      warning("Not possible to set output writer to 'null'");
    }
  }

  /**
   * Returns the target document that contains/will contain the result data. If
   * this reader is used to write other data <i>after</i> it is assigned to this
   * class and before or during the transformation process, the data will most
   * likely be corrupted.
   * @return The result target writer to be used for this transformation.
   */
  public java.io.Writer getOutput() {
    status(5, "Returned output writer");
    return this.outputWriter;
  }

  /**
   * Transforms the given input using the given style to the given output. It
   * first instantiates the xslt engine used by this class and then executes the
   * transformation. Any warnings, errors, and fatal errors occuring during the
   * process will be reported to the status handler instead of rethrowing them
   * to the calling class, as the only information necessary for the calling
   * class should be whether the transformation process was successful or not.
   * @return If the transformation was completed without any error, this method
   *         will return <code>true</code>. If any parameter was not set
   *         properly or the xslt engine reported an error - no matter if it
   *         decided to continue after the error or not -, <code>false</code>
   *         will be returned to ensure the transformation result represents the
   *         expected result.
   * @see #setInput(Reader)
   * @see #setOutput(Writer)
   * @see #setStyle(Reader, String)
   */
  public boolean process() {
    status(4, "Start process()");
    try {
      status(6, "process(): check properties");
      if(this.styleState == 0) {
        // no stylesheet set, abort
        error("Mandatory property 'stylesheet' is not set, aborting");
        throw new TransformException("Mandatory property 'stylesheet' is not set, aborting");
      }
      if(this.inputState == 0) {
        // no stylesheet set, abort
        error("Mandatory property 'input document' is not set, aborting");
        throw new TransformException("Mandatory property 'input document' is not set, aborting");
      }
      if(this.outputState == 0) {
        // no stylesheet set, abort
        error("Mandatory property 'output document' is not set, aborting");
        throw new TransformException("Mandatory property 'output document' is not set, aborting");
      }
      status(5, "process(): transform");
    	TransformerFactory tFactory = TransformerFactory.newInstance();
      tFactory.setErrorListener(this);
      StreamSource _xml = new StreamSource(this.inputReader);
      StreamSource _xsl = new StreamSource(
        this.styleReader,
        this.basepath + System.getProperty("file.separator"));
    	Transformer transformer = tFactory.newTransformer(_xsl);
      if(noErrorOccured) {
        // if we get here, the stylesheet was processed without an error/fatal error
      	transformer.transform(_xml, new StreamResult(this.outputWriter));
        if(noErrorOccured) {
          // if we get here, we're fine
          status(4, "Stop process(OK)");
        } else {
          // if we get here, transformation caused an error/fatal error
          status(4, "Stop process(Failed on transformation)");
        }
      } else {
        // if we get here, stylesheet processing caused an error/fatal error
        status(4, "Stop process(Failed on stylesheet)");
      }
      return noErrorOccured;
    } catch(TransformException te) {
      // if we get here, something went wrong
      error("Stop process(TransE:" + te.getMessage() + ")");
      return false;
    } catch(TransformerException tere) {
      // if we get here, something went wrong
      error("Stop process(XsltE:" + tere.getMessageAndLocation() + ")");
      return false;
    }
  }

  /**
   * Resets the instance of this class. After the reset every information is
   * set back to its values directly after the instantiation finished. This
   * implies that the status handler will not be removed by this method, but
   * remain. If a reset of the status handler is to be performed, it has to be
   * replaced with another status handler.
   */
  public void reset() {
    this.styleReader    = null;
    this.basepath       = null;
    this.styleState     = 0;
    this.inputReader    = null;
    this.inputState     = 0;
    this.outputWriter   = null;
    this.outputState    = 0;
    this.noErrorOccured = true;
    status(3, "Reset");
  }

  /**
   * Used to report a status message to the status handler. It was implemented
   * to make the code more readable and changeable during development, but
   * actually only reports the given status message with the given verbosity
   * level to the status handler.
   * @param level The verbosity level of this status message. This value is used
   *              by the status handler to determine if the event is important
   *              enough to be logged.
   * @param message The status message reported to the status handler
   * @see StatusHandler
   */
  private void status(int level, String message) {
    System.out.println("" + level + ": " + message);
  }

  /**
   * Used to report a warning to the status handler. It was implemented to make
   * the code more readable and changeable during development, but actually only
   * reports the given warning to the status handler.
   * @param message The warning message reported to the status handler
   * @see StatusHandler
   */
  private void warning(String message) {
    System.out.println("2: " + message);
  }

  /**
   * Used to report an error to the status handler. It was implemented to make
   * the code more readable and changeable during development, but actually only
   * reports the given error to the status handler.
   * @param message The error message reported to the status handler
   * @see StatusHandler
   */
  private void error(String message) {
    System.out.println("1: " + message);
  }

  /**
   * <p>
   * Used by the xslt engine to issue a warning to this class. The warning will
   * be reported to the status handler, but the processing will not be
   * interrupted, nor will it be reported as failed by {@link #process()}.</p>
   * <p>
   * <b><i>Note:</i></b> As this method is part of the implementation of the
   * interface {@link javax.xml.transform.ErrorListener}, it is not to be called
   * by any class manually, but will be called by the xslt engine.</p>
   * @param warning The exception reporting the warning to this method.
   */
  public void warning(TransformerException warning) {
    warning("process(): warning: " + warning.getMessage());
  }

  /**
   * <p>
   * Used by the xslt engine to issue an error to this class. The error will be
   * reported to the status handler and be reported as failed by {@link
   * #process()}, even if the xslt engine decided to continue after the error.
   * </p>
   * <p>
   * <b><i>Note:</i></b> As this method is part of the implementation of the
   * interface {@link javax.xml.transform.ErrorListener}, it is not to be called
   * by any class manually, but will be called by the xslt engine.</p>
   * @param error The exception reporting the error to this method.
   */
  public void error(TransformerException error) {
    error("process(): error: " + error.getMessage());
    this.noErrorOccured = false;
  }

  /**
   * <p>
   * Used by the xslt engine to issue a fatal error to this class. The error
   * will be reported to the status handler and be reported as failed by {@link
   * #process()}, even if the xslt engine decided to continue after the fatal 
   * error.</p>
   * <p>
   * <b><i>Note:</i></b> As this method is part of the implementation of the
   * interface {@link javax.xml.transform.ErrorListener}, it is not to be called
   * by any class manually, but will be called by the xslt engine.</p>
   * @param fatalError The exception reporting the fatal error to this method.
   */
  public void fatalError(TransformerException fatalError) {
    error("process(): fatal error: " + fatalError.getMessage());
    this.noErrorOccured = false;
  }

  /**
   * <p>
   * Returns this class'es name combined with information about the thread this
   * instance is executed in. The format is:</p>
   * <p>
   * <code>ThreadName:ClassName</code></p>
   * @return A string as described above.
   */
  public String toString() {
    return Thread.currentThread().getName() + ":XsltEngine";
  }
}
