/*
 * 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 acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xalan" 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 name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * 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 and was
 * originally based on software copyright (c) 1999, Lotus
 * Development Corporation., http://www.lotus.com.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>. 
 *
 * $Id: JavaUtils.java,v 1.2 2001/01/02 03:41:13 sboag Exp $ 
 */

package org.apache.xml.utils.synthetic;

import java.io.IOException;

/** 
 * <meta name="usage" content="internal"/>
 * This class supports invoking Java compilation from within
 * a Java program. Recent versions of the Java environment have
 * provided such an API (in tools.jar). But that isn't available
 * on all platforms, and a fallback to the command line may be needed
 * (though this too may not always be available, eg. for security
 * reasons).
 * <p>
 * There's an additional complication in some environments --
 * such as Microsoft's VJ++ -- where the classpath as seen in
 * the System Properties may not be the one the user expects.
 * The code here is parameterized to try to deal with that.
 */
public class JavaUtils
{
        // One-time flag for whether we could dynamically load compiler API
        private static boolean cantLoadCompiler=false; 

        // Debug flag - generates debug stuff if true.
        private static boolean debug = false;
  
        /** Control whether compilation occurs with the -g option
         * (debugging information included in generated classfile).
         * This is an attribute, rather than a parameter on the compile
         * method, largely because it tends to be an all-or-nothing decision; 
         * generally you're either doing program development and want it,
         * or running in production mode and don't. But that may not match
         * the needs of future users...
         * <p>
         * TODO: Consider whether debug should be a parameter.
         * 
         * @param boolean newDebug True to request debugging data,
         * false to request optimized output. (It's uncommon to
         * want both or neither!)
         */ 
        public static void setDebug(boolean newDebug)
        {
            debug=newDebug;
        }

        /** Try to compile a .java file on disk. This will first attempt to
         * use the sun.java.tools.javac() method, then (if that is unavailable)
         * fall back upon shelling out to a command line and running javac
         * there.
         * <p>
         * NOTE: This must be _compiled_ with sun.java.tools.* (tools.jar)
         * available. We could change that to use reflection instead, if we
         * accept some overhead... minor compared to the cost of running the
         * compiler!
         * <p>
         * This has complications on some platforms. For example, under 
         * Microsoft Visual Java ++ (at least, as installed on my test system),
         * I found that I had to specify paths to both javac and xerces.jar
         * rather than counting on the shell's path and classpath having
         * been set to reach these. For that reason I've parameterized this
         * method with a few system properties, so you can adapt it to your
         * own system's needs without modifying the code:
         * <dl>
         * <dt>org.apache.xml.utils.synthetic.javac
         * <dd>Command line issued to invoke the compiler. Defaults to "javac",
         * which should work in most systems. In VJ++, try setting it to
         * "cmd /c %JAVA_HOME%\\bin\javac.exe"
         * <dt>org.apache.xml.utils.synthetic.moreclasspath
         * <dd>Additional classpath, to be prepended to the one retrieved from
         * java.class.path. Defaults to "" (empty). In VJ++, try setting it to
         * point to your copy of xerces.jar, which may not be found otherwise.
         * TODO: Reconsider prepend versus append!
         * </dl>
         * 
         * @param String fileName Which .java file to compile. Note that this may
         * be relative to the "current directory".
         * @param String classPath Additional places to look for classes that
         * this .java file depends upon. Becomes the javac command's
         * -classpath parameter value.
         * @return boolean True iff compilation succeeded.
         */
        public static boolean JDKcompile(String fileName, String classPath)
        {
                String moreClassPath=
                        System.getProperty("org.apache.xml.utils.synthetic.moreclasspath","")
                        .trim();
                if(moreClassPath.length()>0)
                        classPath=moreClassPath+';'+classPath;
                                                                                                  
                if (debug)
                {
                        System.err.println ("JavaEngine: Compiling " + fileName);
                        System.err.println ("JavaEngine: Classpath is " + classPath);
                }
    
                String code_option = debug ? "-g" : "-O";

                // Start by trying Sun's compiler API
            if(!cantLoadCompiler)
                {
                        String args[] = {
                                code_option,
                            "-classpath", classPath,
                                fileName
                        };
                                
                        try
                    {
                                return new sun.tools.javac.Main(System.err, "javac").compile(args);
                        }
                        catch (Throwable th)
                        {
                                System.err.println("INFORMATIONAL: Unable to load Java compiler API (eg tools.jar).");
                                System.err.println("\tSwitching to command-line invocation.");
                                cantLoadCompiler=true;
                        }
                }
    
                // FALLTHRU:
                // Can't load javac() method; try shelling out to the command
                // line and invoking it via exec(). 
                String javac_command=
                        System.getProperty("org.apache.xml.utils.synthetic.javac","javac");
            String args[] = {
                        javac_command,
                        code_option,
                        "-classpath", classPath,
                        fileName
                        };
                try
                {
                        Process p=java.lang.Runtime.getRuntime().exec(args);
                        int compileOK=waitHardFor(p); // pause for debugging...
                        return compileOK==0; //0 == no error reported
                }
                catch(IOException e)
                {
                        System.err.println("ERROR: IO exception during exec(javac).");
                }
                catch(SecurityException e)
                {
                        System.err.println("ERROR: Unable to create subprocess to exec(javac).");
                }
                
                // FALLTHRU: All attempts failed.
                return false;
        }

  /** Subroutine: Like p.waitFor, but discards the InterruptedException
   * and goes right back into a wait. I don't want to report compiler
   * success or failure until it really has succeeded or failed... I think.
   * @param Process p to be waited for
   * @return the exitValue() of the process.
   */
  static int waitHardFor(java.lang.Process p)
  {
    boolean done=false;
    while(!done)
        try
        {
            p.waitFor();
            done=true;
        }
        catch(InterruptedException e)
        {
            System.err.println("(Compiler process wait interrupted and resumed)");
        }
     int ev=p.exitValue();  // Pause for debugging...
     return ev;
  }
        
}
