/*
** Module   : InMemoryCompiler.java
** Abstract : in-memory compilation from source code to a loaded class
**
** Copyright (c) 2009, Golden Code Development Corporation.
**
** -#- -I- --Date-- -----------------------Description------------------------
** 001 GES 20090303 Provides in-memory compilation from source code to a loaded
**                  class.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.goldencode.compile;

import javax.tools.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import com.goldencode.util.*;
                       
/**
 * Provides in-memory dynamic compilation of Java source code while bypassing
 * the file-system.
 */
public class InMemoryCompiler
{
   /**
    * Compile the given Java source code into a class specified by the given
    * name.
    *
    * @param    className
    *           Fully qualified class name (includes package and any inner
    *           class extensions) in proper Java Language Specification format
    *           WITHOUT any ".java" or ".class" suffixes.
    * @param    code
    *           The complete, valid Java source code for the class.
    * @param    sb
    *           The buffer in which to write error/diagnostic data on any
    *           failure.
    *
    * @return   The resulting class or <code>null</code> on any failure.
    */
   public static Class<?> compile(String        className,
                                  String        code,
                                  StringBuilder sb)
   {
      Class<?> clazz              = null;
      InMemoryFileManager fileMgr = null;
      
      try
      {
         InMemorySourceCode src = new InMemorySourceCode(className, code);
         
         ArrayList<JavaFileObject> list = new ArrayList<JavaFileObject>();
         list.add(src);
         
         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
         
         DiagnosticCollector<JavaFileObject> diagnostics =
            new DiagnosticCollector<JavaFileObject>();
         
         StandardJavaFileManager std =
            compiler.getStandardFileManager(diagnostics, null, null);
                           
         fileMgr = new InMemoryFileManager(std);
         
         Callable<Boolean> compile = compiler.getTask(null,
                                                      fileMgr,
                                                      diagnostics,
                                                      null,
                                                      null,
                                                      list);
         Boolean result = compile.call();
         
         if (result)
         {
            InMemoryClass file = fileMgr.getClassFile(className);
            
            if (file == null)
            {
               sb.append(String.format("Invalid class name '%s' " +
                                       "(it may not match source code).",
                                        className));
               return null;
            }
            
            InMemoryClassLoader loader = new InMemoryClassLoader();
            clazz = loader.register(className, file.getBytes());
         }
         else
         {
            for (Diagnostic<? extends JavaFileObject> diag :
                 diagnostics.getDiagnostics())
            {
               // overall message
               sb.append(diag.getMessage(null)).append("\n");
               
               InMemorySourceCode insc = (InMemorySourceCode) diag.getSource();
               
               int    lineNum = (int) diag.getLineNumber();
               int    colNum  = (int) diag.getColumnNumber();
               String line    = insc.getLine(lineNum);
               
               // display source code
               if (line != null)
               {
                  sb.append(line).append("\n");
                  
                  int len = colNum - 1;
                  
                  for (int j = 0; j < len; j++)
                  {
                     sb.append(' ');
                  }
                  
                  sb.append('^').append("\n");
               }
               
               // our description
               sb.append(String.format("%s on line %d, column %d in %s\n",
                                       diag.getKind(),
                                       lineNum,
                                       colNum,
                                       insc.toUri()));
               
            }
         }
      }
      
      catch (Throwable thr)
      {
         sb.append(LogHelper.dumpThrowable(thr));
      }
      
      finally
      {
         if (fileMgr != null)
         {
            try
            {
               fileMgr.close();
            }
            
            catch (IOException ioe)
            {
               // ignore
            }
         }
      }
      
      return clazz;
   }
}
