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

protocbridge.frontend.PluginFrontend.scala Maven / Gradle / Ivy

The newest version!
package protocbridge.frontend

import java.io.{ByteArrayOutputStream, InputStream, PrintWriter, StringWriter}
import java.nio.file.{Files, Path}

import protocbridge.{ProtocCodeGenerator, ExtraEnv}

/** A PluginFrontend instance provides a platform-dependent way for protoc to
  * communicate with a JVM based ProtocCodeGenerator.
  *
  * protoc is able to launch plugins. Plugins are executables that take a
  * serialized CodeGenerationRequest via stdin and serialize a
  * CodeGenerationRequest to stdout. The idea in PluginFrontend is to create a
  * minimal plugin that wires its stdin/stdout to this JVM.
  *
  * The two-way communication always goes as follows:
  *
  *   1. protoc writes a request to the stdin of a plugin 2. plugin writes the
  *      data to the channel 3. this JVM reads it, interprets it as
  *      CodeGenerationRequest and process it. 4. this JVM writes a
  *      CodeGenerationResponse to the channel 5. this JVM closes the channel.
  *      6. the plugin reads the data and writes it to standard out. 7. protoc
  *      handles the CodeGenerationResponse (creates Scala sources)
  */
trait PluginFrontend {
  type InternalState

  // Notifies the frontend to set up a protoc plugin that runs the given generator. It returns
  // the system path of the executable and an arbitary internal state object that is passed
  // later. Useful for cleanup.
  def prepare(plugin: ProtocCodeGenerator, env: ExtraEnv): (Path, InternalState)

  def cleanup(state: InternalState): Unit
}

object PluginFrontend {
  private def getStackTrace(e: Throwable): String = {
    val stringWriter = new StringWriter
    val printWriter = new PrintWriter(stringWriter)
    e.printStackTrace(printWriter)
    stringWriter.toString
  }

  def runWithBytes(
      gen: ProtocCodeGenerator,
      request: Array[Byte]
  ): Array[Byte] = {
    gen.run(request)
  }

  def createCodeGeneratorResponseWithError(error: String): Array[Byte] = {
    val b = Array.newBuilder[Byte]

    def addRawVarint32(value0: Int): Unit = {
      var value = value0
      while (true) {
        if ((value & ~0x7f) == 0) {
          b += value.toByte
          return
        } else {
          b += ((value & 0x7f) | 0x80).toByte
          value >>>= 7
        }
      }
    }

    b += 10
    val errorBytes = error.getBytes(java.nio.charset.Charset.forName("UTF-8"))
    var length = errorBytes.length
    addRawVarint32(length)
    b ++= errorBytes
    b.result()
  }

  @deprecated("This method is going to be removed.", "0.9.0")
  def readInputStreamToByteArray(fsin: InputStream): Array[Byte] = {
    val b = new ByteArrayOutputStream()
    val buffer = new Array[Byte](4096)
    var count = 0
    while (count != -1) {
      count = fsin.read(buffer)
      if (count > 0) {
        b.write(buffer, 0, count)
      }
    }
    b.toByteArray
  }

  private[protocbridge] def readInputStreamToByteArrayWithEnv(
      fsin: InputStream,
      env: ExtraEnv
  ): Array[Byte] = {
    val b = new ByteArrayOutputStream()
    val buffer = new Array[Byte](4096)
    var count = 0
    while (count != -1) {
      count = fsin.read(buffer)
      if (count > 0) {
        b.write(buffer, 0, count)
      }
    }
    val envBytes = env.toByteArrayAsField
    b.write(envBytes, 0, envBytes.length)
    b.toByteArray
  }

  def runWithInputStream(
      gen: ProtocCodeGenerator,
      fsin: InputStream,
      env: ExtraEnv
  ): Array[Byte] = try {
    val bytes = readInputStreamToByteArrayWithEnv(fsin, env)
    runWithBytes(gen, bytes)
  } catch {
    // This covers all Throwable including OutOfMemoryError, StackOverflowError, etc.
    // We need to make a best effort to return a response to protoc,
    // otherwise protoc can hang indefinitely.
    case throwable: Throwable =>
      createCodeGeneratorResponseWithError(
        throwable.toString + "\n" + getStackTrace(throwable)
      )
  }

  def createTempFile(extension: String, content: String): Path = {
    val fileName = Files.createTempFile("protocbridge", extension)
    val os = Files.newOutputStream(fileName)
    os.write(content.getBytes("UTF-8"))
    os.close()
    fileName
  }

  def isWindows: Boolean = sys.props("os.name").startsWith("Windows")

  def isMac: Boolean = sys.props("os.name").startsWith("Mac") || sys
    .props("os.name")
    .startsWith("Darwin")

  def newInstance: PluginFrontend = {
    if (isWindows) WindowsPluginFrontend
    else if (isMac) MacPluginFrontend
    else PosixPluginFrontend
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy