/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/* 
 * JavaCompiler.java
 *
 * Created on June 22, 2002, 8:56 PM
 * 
 * @author  bnevins
 * @version $Revision: 1.4 $
 * <BR> <I>$Source: /cvs/glassfish/appserv-core/src/java/com/sun/ejb/codegen/JavaCompiler.java,v $
 *
 */

package com.sun.ejb.codegen;

import java.io.*;
import java.util.*;
import java.util.logging.*;
import com.sun.enterprise.util.OS;
import com.sun.enterprise.util.StringUtils;
import com.sun.enterprise.util.i18n.StringManager;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.server.Constants;
import com.sun.tools.javac.Main;

class JavaCompiler extends Compiler
{
    private static final String JAVAC_EXT_DIRS_OPTION = "-extdirs";
    private static final String JAVAC_OUT_OF_PROCESS =
                                "com.sun.aas.deployment.javacoutofprocess";


	JavaCompiler(List theOptions, List theFiles) throws JavaCompilerException
	{
		super(theOptions, theFiles);
	}

	///////////////////////////////////////////////////////////////////////////

	protected void internal_compile() throws JavaCompilerException, ProcessExecutorException
	{
		// note: we are NOT catching Exceptions and then trying the next one.
		// An Exception means there was a compile error and it would be a waste of
		// time to run another compile.
		// if they return true -- it means the facility exists AND everything compiled OK
		// note: only allow JavaCompilerException out of here -- catch everything else
		// and wrap it!!
		
		if(userCompile()) {
			return;
                }

		if(fastjavacCompile()) {
			return;
                }

		javacCompile();
	}
	
	///////////////////////////////////////////////////////////////////////////

	private boolean userCompile() throws ProcessExecutorException
	{
		if(userExe == null)
			return false;
		
		ArrayList cmd = new ArrayList();
		cmd.add(userExe.getPath());
                cmd.add(JAVAC_EXT_DIRS_OPTION);
                cmd.add(System.getProperty(JAVA_EXT_DIRS_SYS_PROP));
		cmd.addAll(userOptions);
		cmd.addAll(options);
		cmd.addAll(files);
		
		String[] cmds = new String[cmd.size()];
		cmds = (String[])cmd.toArray(cmds);
		runProcess(cmds, getUserSpecifiedCompilerTimeout() * files.size());
		logCompilerName(userExe.getName());
		return true;
	}
	
	//////////////////////////////////////////////////////////////////////////

	private boolean fastjavacCompile() throws ProcessExecutorException
	{
		if(fastExe == null || jdkDir == null)
			return false;

		ArrayList cmd = new ArrayList();
		cmd.add(fastExe.getPath());
		cmd.add("-jdk");
		cmd.add(jdkDir.getPath());
		cmd.addAll(options);
		addJavaFiles(cmd);
		String[] cmds = new String[cmd.size()];
		cmds = (String[])cmd.toArray(cmds);
		runProcess(cmds, getFastjavacTimeout() * files.size());
		logCompilerName("fastjavac");
		return true;
	}

	///////////////////////////////////////////////////////////////////////////

	private boolean javacCompile() throws JavaCompilerException, 
                                              ProcessExecutorException
	{
		if(javacExe == null)
			return false;
		
                boolean outOfProcess = 
                    Boolean.getBoolean(JAVAC_OUT_OF_PROCESS);

		ArrayList cmd = new ArrayList();
                if (outOfProcess) {
                    cmd.add(javacExe.getPath());
                }
                cmd.add(JAVAC_EXT_DIRS_OPTION);
                cmd.add(System.getProperty(JAVA_EXT_DIRS_SYS_PROP));
    	        cmd.addAll(options);
	        addJavaFiles(cmd);
	        String[] cmds = new String[cmd.size()];
	        cmds = (String[])cmd.toArray(cmds);

                if (outOfProcess) {
                    runProcess(cmds, getJavacTimeout() * files.size());
                } else {
                    try {
                        ByteArrayOutputStream bos = 
                            new ByteArrayOutputStream();
                        PrintWriter pw = new PrintWriter(bos); 
                        Main compiler = new Main();
                        int ret = compiler.compile(cmds, pw);
                        if (ret != 0) {
                            byte[] errorBytes = bos.toByteArray();
                            String errorString = new String(errorBytes); 
                            throw new JavaCompilerException(
                                "java_compiler.error", "Native compiler returned an error: {0}\nError messages are: {1}", new Object[] { new Integer (ret), errorString } );
                        }
                    }
                    catch(JavaCompilerException jce) {
                        throw jce;
                    }
                    catch(Throwable t)
                    {
                        throw new JavaCompilerException(
                            "java_compiler.unknown_exception",
                            "JavaC compiler threw an Exception", t);
                    }
                }
		logCompilerName("javac");
		return true;
	}
	
	///////////////////////////////////////////////////////////////////////////

	protected void internal_init()
	{
		fastExe		= null;
		javacExe	= null;
		userExe		= null;
		userOptions	= new ArrayList();		
		
		initUserCompiler();
		initFastjavac();
		initJavac();
		logger.log(Level.FINE, "fastExe: " + ((fastExe == null)		? "null" : fastExe.getPath()) );
		logger.log(Level.FINE, "javacExe: " + ((javacExe == null)	? "null" : javacExe.getPath()) );
		logger.log(Level.FINE, "jdkDir: " + ((jdkDir == null)		? "null" : jdkDir.getPath()) );
	}
	
	///////////////////////////////////////////////////////////////////////////

	private void initUserCompiler()
	{
		String userSpecified = getSystemPropertyIgnoreCase(Constants.USER_SPECIFIED_COMPILER);
		
		if(!StringUtils.ok(userSpecified))
			return;
		
		userExe = new File(userSpecified);

		if(!userExe.exists())
		{
			String msg = localStrings.getStringWithDefault(
				"java_compiler.bad_user_compiler", 
				"Can't locate user-specified Java Compiler for deployment.  " 
					+"Environmental Variable= {0}, Value = {1}",
				new Object[] { Constants.USER_SPECIFIED_COMPILER, userSpecified } );

			logger.warning(msg);
			userExe = null;
			return;
		}
		
		// note: it is difficult to handle spaces inside options.
		// at least without requiring the user to specify the args as:
		// xxx1, xxx2, xxx3, etc.  That's too painful for them.  Or I could
		// parse out quote-delimited Strings.  Maybe later.  What
		// are the chances that they will use spaces in filenames anyways?

		userExe = FileUtils.safeGetCanonicalFile(userExe);
		String opts = getSystemPropertyIgnoreCase(Constants.USER_SPECIFIED_COMPILER_OPTIONS);
		
		if(!StringUtils.ok(opts))
			return;
		
		StringTokenizer tok = new StringTokenizer(opts);
		
		while(tok.hasMoreTokens())
		{
			userOptions.add(tok.nextToken());
		}
	}
	
	
	///////////////////////////////////////////////////////////////////////////

	private void initFastjavac()
	{
		if(installRoot == null || jdkDir == null)
			return;
		
		String fastName;
		/*
		// WBN -- Allow config of fastjavac in the environment
		String fastName = System.getProperty(Constants.FASTJAVAC_COMPILER);
		
		if(StringUtils.ok(fastName))
		{
			fastExe = new File(fastName); 

			if(fastExe.exists())
			{
				fastExe = FileUtils.safeGetCanonicalFile(fastExe);
				return;
			}
			fastExe = null;
		}
		*/
		
		if(OS.isWindows())
			fastName	= "fastjavac.exe";
		else if(OS.isSun())
			fastName	= "fastjavac.sun";
		else if(OS.isLinux())
			fastName	= "fastjavac.linux";
		else
			fastName	= null;
		
		if(fastName == null)
			return;

		// if fastjavac app exists -- set it
		fastExe = new File(installRoot + "/studio4/bin/fastjavac/" + fastName); //now named studio4

		if(fastExe.exists())
			fastExe = FileUtils.safeGetCanonicalFile(fastExe);
		else
			fastExe = null;
	}
	
	///////////////////////////////////////////////////////////////////////////

	private void initJavac()
	{
		if(jdkDir == null)
			return;
		
		String javacName;
		
		if(OS.isWindows())
		{
			javacName	= "javac.exe";
		}
		else
		{
			javacName	= "javac";
		}

		javacExe = new File(jdkDir, "/bin/" + javacName);

		if(javacExe.exists())
			javacExe = FileUtils.safeGetCanonicalFile(javacExe);
		else
			javacExe = null;
	}

	/** 
	 * Returns the timeout, in milliseconds, for each java file.
	 * The compiler calling code will multiply this value by the
	 * number of java files.  If the compiler takes longer than
	 * this amount of time the process will be killed.
	 * This is to avoid hangs.
	 *
	 * <p>For flexibility, a environmental variable is checked first.  Failing that,
	 * it will use the hard-coded default value.
	 *
 	 * <p>This method caches the value of timeout in "fastJavacTimeout" variable to prevent
         * memory leak seen in the System.getProperty method in Compiler.java
	 *
	 * @return The timeout, in milliseconds, for each java file
	 */	
	private static int getFastjavacTimeout()
	{
		if (fastJavacTimeout < 0 ) 
		{
			fastJavacTimeout = getTimeout(Constants.FASTJAVAC_TIMEOUT_MS, Constants.DEFAULT_FASTJAVAC_TIMEOUT_MS, 1000, 300000);
		}
		return fastJavacTimeout;
	}

	/** Returns the timeout, in milliseconds, for each java file.
	 * The compiler calling code will multiply this value by the
	 * number of java files.  If the compiler takes longer than
	 * this amount of time the process will be killed.
	 * This is to avoid hangs.
	 *
	 * <p>For flexibility, a environmental variable is checked first.  Failing that,
	 * it will use the hard-coded default value.
	 *
 	 * <p>This method caches the value of timeout in "javacTimeout" variable to prevent
         * memory leak seen in the System.getProperty method in Compiler.java
	 *
	 * @return The timeout, in milliseconds, for each java file
	 */	
	private static int getJavacTimeout()
	{
		if (javacTimeout < 0 ) 
		{
			javacTimeout = getTimeout(Constants.JAVAC_TIMEOUT_MS, Constants.DEFAULT_JAVAC_TIMEOUT_MS, 1000, 900000);
		}
		return javacTimeout;
	}

	/** Returns the timeout, in milliseconds, for each java file.
	 * The compiler calling code will multiply this value by the
	 * number of java files.  If the compiler takes longer than
	 * this amount of time the process will be killed.
	 * This is to avoid hangs.
	 *
	 * <p>For flexibility, a environmental variable is checked first.  Failing that,
	 * it will use the hard-coded default value.
	 *
 	 * <p>This method caches the value of timeout in "userTimeout" variable to prevent
         * memory leak seen in the System.getProperty method in Compiler.java
	 *
	 * @return The timeout, in milliseconds, for each java file
	 */	
	private static int getUserSpecifiedCompilerTimeout()
	{
		if (userTimeout < 0 ) 
		{
			userTimeout = getTimeout(Constants.USER_SPECIFIED_COMPILER_TIMEOUT_MS, Constants.DEFAULT_USER_SPECIFIED_COMPILER_TIMEOUT_MS, 1000, 900000);
		}
		return userTimeout;
	}
	
	///////////////////////////////////////////////////////////////////////////

	private					File			userExe;
	private					File			fastExe;
	private					File			javacExe;
	private					List			userOptions;

	// used for caching timeouts to prevent memory leaks. must be initialized to -1.
	private	static				int			fastJavacTimeout = -1;
	private	static				int			javacTimeout = -1;
	private	static				int			userTimeout = -1;

	///////////////////////////////////////////////////////////////////////////
/* testing code...
	public static void main(String[] args)
	{
		System.out.println("Test 1: install-root == C:/ias7 and JAVA_HOME not set");
		System.setProperty(Constants.INSTALL_ROOT, "C:/ias7");
		test();
		System.out.println("Test 2: install-root == C:/ias7 and JAVA_HOME == c:/jdk1.4");
		System.setProperty("JAVA_HOME", "C:/jdk1.4");
		test();
	}		
	
	
	///////////////////////////////////////////////////////////////////////////

	public static void test()
	{
		List opt = new ArrayList();
		List f   = new ArrayList();
	
		opt.add("-d");
		opt.add("c:/tmp/crap");
		opt.add("-g");
		f.add("c:/src/java/junk/Crap.java");
	
		try
		{
			JavaCompiler jc = new JavaCompiler(opt, f);
			logger.log(Level.SEVERE, "fastExe: " + ((jc.fastExe == null)		? "null" : jc.fastExe.getPath()) );
			logger.log(Level.SEVERE, "javacExe: " + ((jc.javacExe == null)	? "null" : jc.javacExe.getPath()) );
			logger.log(Level.SEVERE, "jdkDir: " + ((jc.jdkDir == null)		? "null" : jc.jdkDir.getPath()) );
			jc.compile();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
 **/
}

