diff --git a/build.sbt b/build.sbt index 4f6c079c1..ffd4ae2f7 100644 --- a/build.sbt +++ b/build.sbt @@ -176,11 +176,13 @@ lazy val actionsProj = (project in file("main-actions")). // General command support and core commands not specific to a build system lazy val commandProj = (project in file("main-command")). + enablePlugins(DatatypePlugin, JsonCodecPlugin). settings( testedBaseSettings, name := "Command", libraryDependencies ++= Seq(launcherInterface, compilerInterface, - sbtIO, utilLogging, utilCompletion, compilerClasspath, json4s, json4sNative) // to transitively get json4s) + sbtIO, utilLogging, utilCompletion, compilerClasspath, sjsonNewScalaJson), + sourceManaged in (Compile, generateDatatypes) := baseDirectory.value / "src" / "main" / "datatype-scala" ) // Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions diff --git a/main-command/src/main/datatype-scala/sbt/internal/server/CommandMessage.scala b/main-command/src/main/datatype-scala/sbt/internal/server/CommandMessage.scala new file mode 100644 index 000000000..889f8fe7a --- /dev/null +++ b/main-command/src/main/datatype-scala/sbt/internal/server/CommandMessage.scala @@ -0,0 +1,39 @@ +/** + * This code is generated using sbt-datatype. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.server +final class CommandMessage( + val `type`: String, + val commandLine: Option[String]) extends Serializable { + + def this(`type`: String) = this(`type`, None) + + override def equals(o: Any): Boolean = o match { + case x: CommandMessage => (this.`type` == x.`type`) && (this.commandLine == x.commandLine) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (17 + `type`.##) + commandLine.##) + } + override def toString: String = { + "CommandMessage(" + `type` + ", " + commandLine + ")" + } + def copy(`type`: String): CommandMessage = { + new CommandMessage(`type`, commandLine) + } + def copy(`type`: String = `type`, commandLine: Option[String] = commandLine): CommandMessage = { + new CommandMessage(`type`, commandLine) + } + def withType(`type`: String): CommandMessage = { + copy(`type` = `type`) + } + def withCommandLine(commandLine: Option[String]): CommandMessage = { + copy(commandLine = commandLine) + } +} +object CommandMessage { + def apply(`type`: String): CommandMessage = new CommandMessage(`type`, None) + def apply(`type`: String, commandLine: Option[String]): CommandMessage = new CommandMessage(`type`, commandLine) +} diff --git a/main-command/src/main/datatype-scala/sbt/internal/server/EventMessage.scala b/main-command/src/main/datatype-scala/sbt/internal/server/EventMessage.scala new file mode 100644 index 000000000..55946b0e7 --- /dev/null +++ b/main-command/src/main/datatype-scala/sbt/internal/server/EventMessage.scala @@ -0,0 +1,59 @@ +/** + * This code is generated using sbt-datatype. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.server +final class EventMessage( + val `type`: String, + val status: Option[String], + val commandQueue: Vector[String], + val level: Option[String], + val message: Option[String], + val success: Option[Boolean], + val commandLine: Option[String]) extends Serializable { + + def this(`type`: String) = this(`type`, None, Vector(), None, None, None, None) + + override def equals(o: Any): Boolean = o match { + case x: EventMessage => (this.`type` == x.`type`) && (this.status == x.status) && (this.commandQueue == x.commandQueue) && (this.level == x.level) && (this.message == x.message) && (this.success == x.success) && (this.commandLine == x.commandLine) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + `type`.##) + status.##) + commandQueue.##) + level.##) + message.##) + success.##) + commandLine.##) + } + override def toString: String = { + "EventMessage(" + `type` + ", " + status + ", " + commandQueue + ", " + level + ", " + message + ", " + success + ", " + commandLine + ")" + } + def copy(`type`: String): EventMessage = { + new EventMessage(`type`, status, commandQueue, level, message, success, commandLine) + } + def copy(`type`: String = `type`, status: Option[String] = status, commandQueue: Vector[String] = commandQueue, level: Option[String] = level, message: Option[String] = message, success: Option[Boolean] = success, commandLine: Option[String] = commandLine): EventMessage = { + new EventMessage(`type`, status, commandQueue, level, message, success, commandLine) + } + def withType(`type`: String): EventMessage = { + copy(`type` = `type`) + } + def withStatus(status: Option[String]): EventMessage = { + copy(status = status) + } + def withCommandQueue(commandQueue: Vector[String]): EventMessage = { + copy(commandQueue = commandQueue) + } + def withLevel(level: Option[String]): EventMessage = { + copy(level = level) + } + def withMessage(message: Option[String]): EventMessage = { + copy(message = message) + } + def withSuccess(success: Option[Boolean]): EventMessage = { + copy(success = success) + } + def withCommandLine(commandLine: Option[String]): EventMessage = { + copy(commandLine = commandLine) + } +} +object EventMessage { + def apply(`type`: String): EventMessage = new EventMessage(`type`, None, Vector(), None, None, None, None) + def apply(`type`: String, status: Option[String], commandQueue: Vector[String], level: Option[String], message: Option[String], success: Option[Boolean], commandLine: Option[String]): EventMessage = new EventMessage(`type`, status, commandQueue, level, message, success, commandLine) +} diff --git a/main-command/src/main/datatype-scala/sbt/internal/server/codec/CommandMessageFormats.scala b/main-command/src/main/datatype-scala/sbt/internal/server/codec/CommandMessageFormats.scala new file mode 100644 index 000000000..17130c90e --- /dev/null +++ b/main-command/src/main/datatype-scala/sbt/internal/server/codec/CommandMessageFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using sbt-datatype. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.server.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val CommandMessageFormat: JsonFormat[sbt.internal.server.CommandMessage] = new JsonFormat[sbt.internal.server.CommandMessage] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.server.CommandMessage = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val `type` = unbuilder.readField[String]("type") + val commandLine = unbuilder.readField[Option[String]]("commandLine") + unbuilder.endObject() + new sbt.internal.server.CommandMessage(`type`, commandLine) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.server.CommandMessage, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("type", obj.`type`) + builder.addField("commandLine", obj.commandLine) + builder.endObject() + } +} +} diff --git a/main-command/src/main/datatype-scala/sbt/internal/server/codec/EventMessageFormats.scala b/main-command/src/main/datatype-scala/sbt/internal/server/codec/EventMessageFormats.scala new file mode 100644 index 000000000..4ce95d9ef --- /dev/null +++ b/main-command/src/main/datatype-scala/sbt/internal/server/codec/EventMessageFormats.scala @@ -0,0 +1,39 @@ +/** + * This code is generated using sbt-datatype. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.server.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait EventMessageFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val EventMessageFormat: JsonFormat[sbt.internal.server.EventMessage] = new JsonFormat[sbt.internal.server.EventMessage] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.server.EventMessage = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val `type` = unbuilder.readField[String]("type") + val status = unbuilder.readField[Option[String]]("status") + val commandQueue = unbuilder.readField[Vector[String]]("commandQueue") + val level = unbuilder.readField[Option[String]]("level") + val message = unbuilder.readField[Option[String]]("message") + val success = unbuilder.readField[Option[Boolean]]("success") + val commandLine = unbuilder.readField[Option[String]]("commandLine") + unbuilder.endObject() + new sbt.internal.server.EventMessage(`type`, status, commandQueue, level, message, success, commandLine) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.server.EventMessage, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("type", obj.`type`) + builder.addField("status", obj.status) + builder.addField("commandQueue", obj.commandQueue) + builder.addField("level", obj.level) + builder.addField("message", obj.message) + builder.addField("success", obj.success) + builder.addField("commandLine", obj.commandLine) + builder.endObject() + } +} +} diff --git a/main-command/src/main/datatype/server.json b/main-command/src/main/datatype/server.json new file mode 100644 index 000000000..3dcc4af17 --- /dev/null +++ b/main-command/src/main/datatype/server.json @@ -0,0 +1,73 @@ +{ + "codecNamespace": "sbt.internal.server.codec", + "types": [ + { + "name": "CommandMessage", + "namespace": "sbt.internal.server", + "type": "record", + "target": "Scala", + "fields": [ + { + "name": "type", + "type": "String", + "since": "0.0.0" + }, + { + "name": "commandLine", + "type": "String?", + "default": "None", + "since": "0.1.0" + } + ] + }, + { + "name": "EventMessage", + "namespace": "sbt.internal.server", + "type": "record", + "target": "Scala", + "fields": [ + { + "name": "type", + "type": "String", + "since": "0.0.0" + }, + { + "name": "status", + "type": "String?", + "default": "None", + "since": "0.1.0" + }, + { + "name": "commandQueue", + "type": "String*", + "default": "Vector()", + "since": "0.1.0" + }, + { + "name": "level", + "type": "String?", + "default": "None", + "since": "0.1.0" + }, + { + "name": "message", + "type": "String?", + "default": "None", + "since": "0.1.0" + }, + { + "name": "success", + "type": "boolean?", + "default": "None", + "since": "0.1.0" + }, + { + "name": "commandLine", + "type": "String?", + "default": "None", + "since": "0.1.0" + } + ] + } + ] +} diff --git a/main-command/src/main/scala/sbt/internal/server/Serialization.scala b/main-command/src/main/scala/sbt/internal/server/Serialization.scala index f7da4b0f9..99142383f 100644 --- a/main-command/src/main/scala/sbt/internal/server/Serialization.scala +++ b/main-command/src/main/scala/sbt/internal/server/Serialization.scala @@ -5,66 +5,68 @@ package sbt package internal package server -import org.json4s.JsonAST.{ JArray, JString } -import org.json4s._ -import org.json4s.JsonDSL._ -import org.json4s.native.JsonMethods._ -import org.json4s.ParserUtil.ParseException +import sjsonnew.{ JsonFormat, BasicJsonProtocol } +import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter } +import scala.json.ast.unsafe.JValue +import sjsonnew.support.scalajson.unsafe.Parser +import java.nio.ByteBuffer +import scala.util.{ Success, Failure } object Serialization { - def serialize(event: Event): Array[Byte] = { - compact(render(toJson(event))).getBytes("UTF-8") - } - - def toJson(event: Event): JObject = event match { - case LogEvent(level, message) => - JObject( - "type" -> JString("log_event"), - "level" -> JString(level), - "message" -> JString(message) - ) - - case StatusEvent(Ready) => - JObject( - "type" -> JString("status_event"), - "status" -> JString("ready"), - "command_queue" -> JArray(List.empty) - ) - - case StatusEvent(Processing(command, commandQueue)) => - JObject( - "type" -> JString("status_event"), - "status" -> JString("processing"), - "command_queue" -> JArray(commandQueue.map(JString).toList) - ) - - case ExecutionEvent(command, status) => - JObject( - "type" -> JString("execution_event"), - "command" -> JString(command), - "success" -> JBool(status) - ) - } + def serialize(event: Event): Array[Byte] = + { + import ServerCodec._ + val msg = toMessage(event) + val json: JValue = Converter.toJson[EventMessage](msg).get + CompactPrinter(json).getBytes("UTF-8") + } + def toMessage(event: Event): EventMessage = + event match { + case LogEvent(level, message) => + EventMessage(`type` = "logEvent", + status = None, commandQueue = Vector(), + level = Some(level), message = Some(message), success = None, commandLine = None) + case StatusEvent(Ready) => + EventMessage(`type` = "statusEvent", + status = Some("ready"), commandQueue = Vector(), + level = None, message = None, success = None, commandLine = None) + case StatusEvent(Processing(command, commandQueue)) => + EventMessage(`type` = "statusEvent", + status = Some("processing"), commandQueue = commandQueue.toVector, + level = None, message = None, success = None, commandLine = None) + case ExecutionEvent(command, status) => + EventMessage(`type` = "executionEvent", + status = None, commandQueue = Vector(), + level = None, message = None, success = Some(status), commandLine = Some(command)) + } /** * @return A command or an invalid input description */ def deserialize(bytes: Seq[Byte]): Either[String, Command] = - try { - val json = parse(new String(bytes.toArray, "UTF-8")) - implicit val formats = DefaultFormats - - (json \ "type").toOption match { - case Some(JString("exec")) => - (json \ "command_line").toOption match { - case Some(JString(cmd)) => Right(Execution(cmd)) - case _ => Left("Missing or invalid command_line field") + { + val buffer = ByteBuffer.wrap(bytes.toArray) + Parser.parseFromByteBuffer(buffer) match { + case Success(json) => + import ServerCodec._ + Converter.fromJson[CommandMessage](json) match { + case Success(command) => + command.`type` match { + case "exec" => + command.commandLine match { + case Some(cmd) => Right(Execution(cmd)) + case None => Left("Missing or invalid command_line field") + } + case cmd => Left(s"Unknown command type $cmd") + } + case Failure(e) => Left(e.getMessage) } - case Some(cmd) => Left(s"Unknown command type $cmd") - case None => Left("Invalid command, missing type field") + case Failure(e) => + Left(s"Parse error: ${e.getMessage}") } - } catch { - case e: ParseException => Left(s"Parse error: ${e.getMessage}") } } + +object ServerCodec extends ServerCodec +trait ServerCodec extends codec.EventMessageFormats with codec.CommandMessageFormats with BasicJsonProtocol diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 16ec839e9..5c0e9e9ef 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -37,9 +37,7 @@ object Dependencies { lazy val compilerClasspath = "org.scala-sbt" %% "zinc-classpath" % zincVersion lazy val compilerApiInfo = "org.scala-sbt" %% "zinc-apiinfo" % zincVersion lazy val compilerIvyIntegration = "org.scala-sbt" %% "zinc-ivy-integration" % zincVersion - - lazy val json4s = "org.json4s" %% "json4s" % "3.2.10" - lazy val json4sNative = "org.json4s" %% "json4s-native" % "3.2.10" + lazy val sjsonNewScalaJson = "com.eed3si9n" %% "sjson-new-scalajson" % "0.4.2" lazy val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.11.4" lazy val specs2 = "org.specs2" %% "specs2" % "2.3.11" diff --git a/project/datatype.sbt b/project/datatype.sbt new file mode 100644 index 000000000..3fd610568 --- /dev/null +++ b/project/datatype.sbt @@ -0,0 +1 @@ +addSbtPlugin("org.scala-sbt" % "sbt-datatype" % "0.2.6")