All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.eharmony.aloha.models.h2o.compiler.compiler.scala Maven / Gradle / Ivy

package com.eharmony.aloha.models.h2o.compiler

import java.io.File
import java.net.URLClassLoader
import java.nio.charset.Charset
import java.util.Locale
import javax.tools.JavaCompiler.CompilationTask
import javax.tools.{DiagnosticCollector, JavaFileObject, ToolProvider}

import com.eharmony.aloha.io.{AlohaReadable, ReadableByString}
import com.eharmony.aloha.reflect.{RefInfo, RefInfoOps}
import com.eharmony.aloha.util.Logging
import org.apache.commons.io.FileUtils

import scala.collection.JavaConversions.{asJavaIterable, collectionAsScalaIterable}
import scala.util.{Failure, Success, Try}


/**
 * A compiler that compiles and instantiates the code.
 * @param classDir If supplied, an attempt to create the directory but no attempt to delete the
 *                 directory will be made.
 * @param locale A Locale
 * @param charSet A CharSet
 * @param riB reflection info about the output type B.
 * @tparam B the output type.  This should be an interface or base class that the compiled class extends.
 */
private[h2o] class Compiler[B](classDir: Option[File] = None,
                               locale: Locale   = Locale.getDefault,
                               charSet: Charset = Charset.defaultCharset)
                              (implicit riB: RefInfo[B])
extends AlohaReadable[Try[B]]
   with ReadableByString[Try[B]] {

  /** Read from a String.
    * @param code a String to read.
    * @return the result
    */
  override def fromString(code: String): Try[B] = {
    val diagnosticCollector = new DiagnosticCollector[JavaFileObject]

    for {
      dirAndDelete         <- getCompileDir(classDir)
      compileDir           = dirAndDelete._1  // Avoids: `withFilter' method does not yet exist on
      delete               = dirAndDelete._2  //         Try[(File, Boolean)], using `filter' method instead
      _                    <- mkDir(compileDir)
      compilationUnit      <- getCompilationUnit(code)
      compileTask          <- getCompileTask(compilationUnit, compileDir, diagnosticCollector)
      diagnostics          <- compile(compileTask, diagnosticCollector)
      untypedInstance      <- instantiate(compilationUnit, compileDir)
      instance             <- cast(untypedInstance)
      _                    <- rmDir(compileDir, delete)
    } yield instance
  }

  def getCompileDir(dir: Option[File]) = dir.map(d => Try{(d, false)}) getOrElse Compiler.tmpDir.map(d => (d, true))

  def getCompileTask(compilationUnit: InMemoryJavaSource[B],
                     compileDir: File,
                     diagnosticCollector: DiagnosticCollector[JavaFileObject]) = Try {
    val locations = asJavaIterable(Seq("-d", compileDir.getCanonicalPath))
    val compiler = ToolProvider.getSystemJavaCompiler
    val fileManager = compiler.getStandardFileManager(null, locale, charSet)
    compiler.getTask(null, null, diagnosticCollector, locations, null, Iterable(compilationUnit))
  }

  def compile(compileTask: CompilationTask,
              diagnosticCollector: DiagnosticCollector[JavaFileObject]) =
    Try {
      compileTask.call()
    } transform (
      b => {
        val diagnostics = collectionAsScalaIterable(diagnosticCollector.getDiagnostics)
        if (b) Success(diagnostics)
        else   Failure(CompilationError(diagnostics))
      },
      // Failure(new Exception(s"Compilation failed for ${compilationUnit.className}."))
      f => Failure(f)
    )

  def instantiate(compilationUnit: InMemoryJavaSource[B], compileDir: File) = Try[Any] {
    val classLoader = new URLClassLoader(Array(compileDir.toURI.toURL))
    val clazz = classLoader.loadClass(compilationUnit.className)
    clazz.newInstance()
  }

  def cast(instance: Any): Try[B] = instance match {
    case b: B => Try(b)
    case d    => Failure(new IllegalArgumentException(s"Expected ${RefInfoOps.toString[B]}.  Found: ${d.getClass.getCanonicalName}"))
  }

  def getCompilationUnit(code: String): Try[InMemoryJavaSource[B]] =
    InMemoryJavaSource.fromString[B](code).map(Success(_)) getOrElse {
      Failure(new IllegalArgumentException("Couldn't create InMemoryJavaSource."))
    }

  def mkDir(f: File) = Try[Unit] {
    if(!f.exists())
      f.mkdir()
  }

  def rmDir(f: File, delete: Boolean) = Try[Unit] {
    if(delete && f.exists() && f.isDirectory)
      FileUtils.deleteDirectory(f)
  }
}

private[h2o] object Compiler extends Logging {
  def tmpDir = Try[File] {
    val f = File.createTempFile("javacompiler", "classdir")
    debug(s"creating temp class directory: ${f.getCanonicalPath}")
    f.delete()
    f.mkdir
    f
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy