/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: JavaUtils.java,v 1.7 2004/02/17 04:23:24 minchau Exp $
 */

package org.apache.xml.utils.synthetic;

import java.io.IOException;

/** 
 * 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.
 * @xsl.usage internal
 */
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;
  }
        
}
