scala.tools.nsc.doc.html.page.diagram.DotRunner.scala Maven / Gradle / Ivy
package scala.tools.nsc
package doc
package html
package page
package diagram
import java.io.InputStream
import java.io.OutputStream
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.io.BufferedWriter
import java.io.BufferedReader
import scala.sys.process._
import scala.concurrent.SyncVar
import model._
/** This class takes care of running the graphviz dot utility */
class DotRunner(settings: doc.Settings) {
private[this] var dotRestarts = 0
private[this] var dotProcess: DotProcess = null
def feedToDot(dotInput: String, template: DocTemplateEntity): String = {
if (dotProcess == null) {
if (dotRestarts < settings.docDiagramsDotRestart.value) {
if (dotRestarts != 0)
settings.printMsg("Graphviz will be restarted...\n")
dotRestarts += 1
dotProcess = new DotProcess(settings)
} else
return null
}
val tStart = System.currentTimeMillis
val result = dotProcess.feedToDot(dotInput, template.qualifiedName)
val tFinish = System.currentTimeMillis
DiagramStats.addDotRunningTime(tFinish - tStart)
if (result == null) {
dotProcess.cleanup()
dotProcess = null
if (dotRestarts == settings.docDiagramsDotRestart.value) {
settings.printMsg("\n")
settings.printMsg("**********************************************************************")
settings.printMsg("Diagrams will be disabled for this run because the graphviz dot tool")
settings.printMsg("has malfunctioned too many times. These scaladoc flags may help:")
settings.printMsg("")
val baseList = List(settings.docDiagramsDebug,
settings.docDiagramsDotPath,
settings.docDiagramsDotRestart,
settings.docDiagramsDotTimeout)
val width = (baseList map (_.helpSyntax.length)).max
def helpStr(s: doc.Settings#Setting) = ("%-" + width + "s") format (s.helpSyntax) + " " + s.helpDescription
baseList.foreach((sett: doc.Settings#Setting) => settings.printMsg(helpStr(sett)))
settings.printMsg("\nPlease note that graphviz package version 2.26 or above is required.")
settings.printMsg("**********************************************************************\n\n")
}
}
result
}
def cleanup() =
if (dotProcess != null)
dotProcess.cleanup()
}
class DotProcess(settings: doc.Settings) {
@volatile var error: Boolean = false // signal an error
val inputString = new SyncVar[String] // used for the dot process input
val outputString = new SyncVar[String] // used for the dot process output
val errorBuffer: StringBuffer = new StringBuffer() // buffer used for both dot process error console AND logging
// set in only one place, in the main thread
var process: Process = null
var templateName: String = ""
var templateInput: String = ""
def feedToDot(input: String, template: String): String = {
templateName = template
templateInput = input
try {
// process creation
if (process == null) {
val procIO = new ProcessIO(inputFn(_), outputFn(_), errorFn(_))
val processBuilder: ProcessBuilder = Seq(settings.docDiagramsDotPath.value, "-Tsvg")
process = processBuilder.run(procIO)
}
// pass the input and wait for the output
assert(!inputString.isSet)
assert(!outputString.isSet)
inputString.put(input)
var result = outputString.take(settings.docDiagramsDotTimeout.value * 1000L)
if (error) result = null
result
} catch {
case exc: Throwable =>
errorBuffer.append(" Main thread in " + templateName + ": " +
(if (exc.isInstanceOf[NoSuchElementException]) "Timeout" else "Exception: " + exc))
error = true
return null
}
}
def cleanup(): Unit = {
// we'll need to know if there was any error for reporting
val _error = error
if (process != null) {
// if there's no error, this should exit cleanly
if (!error) feedToDot("", "")
// just in case there's any thread hanging, this will take it out of the loop
error = true
process.destroy()
// we'll need to unblock the input again
if (!inputString.isSet) inputString.put("")
if (outputString.isSet) outputString.take()
}
if (_error) {
if (settings.docDiagramsDebug.value) {
settings.printMsg("\n**********************************************************************")
settings.printMsg("The graphviz dot diagram tool has malfunctioned and will be restarted.")
settings.printMsg("\nThe following is the log of the failure:")
settings.printMsg(errorBuffer.toString)
settings.printMsg(" Cleanup: Last template: " + templateName)
settings.printMsg(" Cleanup: Last dot input: \n " + templateInput.replaceAll("\n","\n ") + "\n")
settings.printMsg(" Cleanup: Dot path: " + settings.docDiagramsDotPath.value)
if (process != null)
settings.printMsg(" Cleanup: Dot exit code: " + process.exitValue)
settings.printMsg("**********************************************************************")
} else {
// we shouldn't just sit there for 50s not reporting anything, no?
settings.printMsg("Graphviz dot encountered an error when generating the diagram for:")
settings.printMsg(templateName)
settings.printMsg("These are usually spurious errors, but if you notice a persistant error on")
settings.printMsg("a diagram, please use the " + settings.docDiagramsDebug.name + " flag and report a bug with the output.")
}
}
}
/* The standard input passing function */
private[this] def inputFn(stdin: OutputStream): Unit = {
val writer = new BufferedWriter(new OutputStreamWriter(stdin))
try {
var input = inputString.take()
while (!error) {
if (input == "") {
// empty => signal to finish
stdin.close()
return
} else {
// send output to dot
writer.write(input + "\n\n")
writer.flush()
}
if (!error) input = inputString.take()
}
stdin.close()
} catch {
case exc: Throwable =>
error = true
stdin.close()
errorBuffer.append(" Input thread in " + templateName + ": Exception: " + exc + "\n")
}
}
private[this] def outputFn(stdOut: InputStream): Unit = {
val reader = new BufferedReader(new InputStreamReader(stdOut))
val buffer: StringBuilder = new StringBuilder()
try {
var line = reader.readLine
while (!error && line != null) {
buffer.append(line + "\n")
// signal the last element in the svg (only for output)
if (line == "") {
outputString.put(buffer.toString)
buffer.setLength(0)
}
if (error) { stdOut.close(); return }
line = reader.readLine
}
assert(!outputString.isSet)
outputString.put(buffer.toString)
stdOut.close()
} catch {
case exc: Throwable =>
error = true
stdOut.close()
errorBuffer.append(" Output thread in " + templateName + ": Exception: " + exc + "\n")
}
}
private[this] def errorFn(stdErr: InputStream): Unit = {
val reader = new BufferedReader(new InputStreamReader(stdErr))
try {
var line = reader.readLine
while (line != null) {
errorBuffer.append(" DOT : " + line + "\n")
error = true
line = reader.readLine
}
stdErr.close()
} catch {
case exc: Throwable =>
error = true
stdErr.close()
errorBuffer.append(" Error thread in " + templateName + ": Exception: " + exc + "\n")
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy