Refactoring: allows text or json files in zip.

In preparation for some commands which need to leave files with non Json
content, we must be prepared to have both formats available from the
commands. Each command can now produce a Seq of (filename,content) where
content can be Json or Textual.
Some additional magic has been implemented via implicits to allow
commands to produce the needed content in a convenient way.
Also tests for bundle production have been separated.

Change-Id: Id34b12f42abd17f29063d324a3a2ab4ac641311f
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/support/GerritSupportCommands.scala b/src/main/scala/com/googlesource/gerrit/plugins/support/GerritSupportCommands.scala
index 881fa3e..0371a23 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/support/GerritSupportCommands.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/support/GerritSupportCommands.scala
@@ -18,30 +18,64 @@
 
 import com.google.gson.{Gson, JsonElement, JsonObject}
 import com.google.inject._
+import com.googlesource.gerrit.plugins.support.GerritSupportCommand.{CommandResult, JsonResult, ResultName}
 import org.slf4j.LoggerFactory
 
 import scala.util.Try
 
-case class CommandResult(entryName: String, content: JsonElement)
+object GerritSupportCommand {
+
+  class ResultName(val value: String) extends AnyVal
+
+  implicit def wrapToResultName(name: String): ResultName = new ResultName(name)
+
+  implicit def unwrapResultName(resultName: ResultName): String = resultName.value
+
+  // allows returning a pure Json result or a textual file content
+  case class CommandResult(entryName: ResultName, content: AnyResult)
+
+  trait AnyResult
+
+  case class JsonResult(result: JsonElement)(implicit val gson: Gson) extends AnyResult {
+    override def toString: String = gson.toJson(result)
+  }
+
+  case class TextResult(result: String) extends AnyResult {
+    override def toString: String = result
+  }
+
+  implicit def convertAny2CommandResult(x: Any)(implicit resultName: ResultName, gson: Gson) =
+    x match {
+      case res: AnyResult => CommandResult(resultName, res)
+      case anyRes => CommandResult(resultName, JsonResult(gson.toJsonTree(anyRes)))
+    }
+
+  implicit def convertString2TextResult(x:String) = TextResult(x)
+}
 
 abstract class GerritSupportCommand {
+  import GerritSupportCommand._
+
   val log = LoggerFactory.getLogger(classOf[GerritSupportCommand])
+
   implicit val gson = new Gson
-  val name = camelToUnderscores(this.getClass.getSimpleName.stripSuffix("Command"))
+  implicit val name: ResultName = camelToUnderscores(this.getClass.getSimpleName.stripSuffix("Command"))
     .stripPrefix("_")
 
-  def getResult: Any
+  val nameJson = s"$name.json"
 
-  def execute = {
-    CommandResult(s"${name}.json",
-      gson.toJsonTree(
-        Try {
-          getResult
-        } getOrElse {
-          val error = s"${name} not available on ${System.getProperty("os.name")}"
-          log.error(error);
-          ErrorInfo("error" -> error)
-        }))
+  def getResults: Seq[CommandResult] = Seq(getResult)
+
+  def getResult: CommandResult
+
+  def execute: Seq[CommandResult] = {
+    Try {
+      getResults
+    } getOrElse {
+      val error = s"${name} not available on ${System.getProperty("os.name")}"
+      log.error(error)
+      Seq(ErrorInfo("error" -> error))
+    }
   }
 
   private def camelToUnderscores(name: String) = "[A-Z\\d]".r.replaceAllIn(name, { m =>
@@ -60,11 +94,13 @@
 }
 
 object ErrorInfo {
-  def apply[T](attributes: (String, T)*)(implicit gson: Gson): JsonObject =
-    attributes.foldLeft(new JsonObject) {
-      (json, pair) => {
-        json.add(pair._1, gson.toJsonTree(pair._2))
-        json
-      }
-    }
+  def apply[T](attributes: (String, T)*)(implicit gson: Gson, resultName: ResultName): CommandResult =
+    CommandResult(resultName,
+      JsonResult(
+        attributes.foldLeft(new JsonObject) {
+          (json, pair) => {
+            json.add(pair._1, gson.toJsonTree(pair._2))
+            json
+          }
+        }))
 }
\ No newline at end of file
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/support/RequestProcessor.scala b/src/main/scala/com/googlesource/gerrit/plugins/support/RequestProcessor.scala
index e022cb0..c9c6da3 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/support/RequestProcessor.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/support/RequestProcessor.scala
@@ -36,7 +36,7 @@
           .filter(_.getValue.getAsBoolean)
           .map(_.getKey)
           .map(commandFactory.apply)
-          .map(_.execute)
+          .flatMap(_.execute)
       }
     }
   }
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/support/SupportBundle.scala b/src/main/scala/com/googlesource/gerrit/plugins/support/SupportBundle.scala
index 2232f09..d72c0d6 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/support/SupportBundle.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/support/SupportBundle.scala
@@ -24,6 +24,7 @@
 
 import com.google.gson.Gson
 import com.google.inject.Inject
+import com.googlesource.gerrit.plugins.support.GerritSupportCommand.CommandResult
 import com.googlesource.gerrit.plugins.support.annotations.PluginDataPath
 import org.joda.time.DateTime
 import org.joda.time.format.DateTimeFormat
@@ -64,7 +65,7 @@
     val zipBundle =  new ZipOutputStream(new FileOutputStream(file))
     results.foreach { result =>
       zipBundle.putNextEntry(new ZipEntry(result.entryName))
-      zipBundle.write(gson.toJson(result.content).getBytes(UTF8))
+      zipBundle.write(result.content.toString.getBytes(UTF8))
     }
     zipBundle.close()
     file
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/support/commands/DiskInfoCommand.scala b/src/main/scala/com/googlesource/gerrit/plugins/support/commands/DiskInfoCommand.scala
index 78f3b68..506ace7 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/support/commands/DiskInfoCommand.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/support/commands/DiskInfoCommand.scala
@@ -25,7 +25,7 @@
 
   case class DiskInfo(path: String, diskFree: Long, diskUsable: Long, diskTotal: Long)
 
-  def getResult = {
+  override def getResult = {
     val dataPath = sitePathsFolder.getAsPath("data_dir")
     val store = Files.getFileStore(dataPath)
     DiskInfo(dataPath.toString, store.getUnallocatedSpace,
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/support/commands/GerritVersionCommand.scala b/src/main/scala/com/googlesource/gerrit/plugins/support/commands/GerritVersionCommand.scala
index fe1782d..ec7cf88 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/support/commands/GerritVersionCommand.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/support/commands/GerritVersionCommand.scala
@@ -18,7 +18,8 @@
 
 import com.google.gerrit.common.Version
 import com.googlesource.gerrit.plugins.support.GerritSupportCommand
+import com.googlesource.gerrit.plugins.support.GerritSupportCommand.TextResult
 
 class GerritVersionCommand extends GerritSupportCommand {
-  override def getResult = Version.getVersion
+  override def getResult = TextResult(Version.getVersion)
 }
\ No newline at end of file
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/support/GerritBundleTest.scala b/src/test/scala/com/googlesource/gerrit/plugins/support/GerritBundleTest.scala
new file mode 100644
index 0000000..93d12c2
--- /dev/null
+++ b/src/test/scala/com/googlesource/gerrit/plugins/support/GerritBundleTest.scala
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlesource.gerrit.plugins.support
+
+import java.util.zip.ZipFile
+
+import com.google.gson.{Gson, JsonPrimitive}
+import com.googlesource.gerrit.plugins.support.FileMatchers._
+import com.googlesource.gerrit.plugins.support.GerritSupportCommand.{CommandResult, JsonResult}
+import org.scalatest.{FlatSpec, Matchers}
+
+import scala.collection.JavaConverters._
+
+class GerritBundleTest extends FlatSpec with Matchers
+  with JsonMatcher with TmpPath {
+  implicit val gson = new Gson
+
+  "Bundle builder" should "create an output zip file" in {
+    val zipFile = new SupportBundleBuilder(tmpPath.toPath, gson).build(Seq())
+
+    zipFile should (be a 'file
+      and endWithExtension("zip"))
+  }
+
+  it should "create a one entry in the output zip file" in {
+    val file = new SupportBundleBuilder(tmpPath.toPath, gson)
+      .build(Seq(CommandResult("foo", JsonResult(new JsonPrimitive("bar")))))
+
+    val zipEntries = new ZipFile(file).entries.asScala
+    zipEntries should have size (1)
+  }
+
+  it should "add the Json primitive into the zip entry" in {
+    val file = new SupportBundleBuilder(tmpPath.toPath, gson)
+      .build(Seq(CommandResult("entry-name", JsonResult(new JsonPrimitive("foo")))))
+
+    val zipEntries = new ZipFile(file).entries.asScala.toSeq
+    zipEntries.map(_.getName) should contain("entry-name")
+  }
+
+}
+
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/support/GerritSupportTest.scala b/src/test/scala/com/googlesource/gerrit/plugins/support/GerritSupportTest.scala
index 9331c7b..3ef19f0 100644
--- a/src/test/scala/com/googlesource/gerrit/plugins/support/GerritSupportTest.scala
+++ b/src/test/scala/com/googlesource/gerrit/plugins/support/GerritSupportTest.scala
@@ -16,44 +16,36 @@
 
 package com.googlesource.gerrit.plugins.support
 
-import java.io.File
 import java.nio.file.Paths
-import java.util.zip.ZipFile
 
-import com.google.gson.{Gson, JsonPrimitive}
-import com.googlesource.gerrit.plugins.support.FileMatchers._
+import com.googlesource.gerrit.plugins.support.GerritSupportCommand.{CommandResult, JsonResult, ResultName, TextResult}
 import com.googlesource.gerrit.plugins.support.commands._
 import org.mockito.Matchers.any
 import org.mockito.Mockito
 import org.scalatest.{FlatSpec, Matchers}
 
-import scala.collection.JavaConverters._
-
-class GerritSupportTest extends FlatSpec with Matchers with JsonMatcher {
-  val tmpPath = File.createTempFile(classOf[GerritSupportTest].getName, "").getParentFile
-
-  def tmpFile = {
-    val file = File.createTempFile("file.txt", System.currentTimeMillis.toString, tmpPath)
-    file.deleteOnExit
-    file
-  }
+class GerritSupportTest extends FlatSpec with Matchers
+  with JsonMatcher with TmpPath {
 
   "version command" should "return a non-empty version string" in {
-    val version = new GerritVersionCommand().execute.content
+    val Seq(CommandResult(name, TextResult(version))) = new GerritVersionCommand().execute
 
-    version.getAsString should not be null
+    name.value should be ("gerrit_version")
+    version should not be empty
   }
 
   "cpu-info command" should "return a json object with some fields" in {
-    val cpuInfo = new CpuInfoCommand().execute.content
+    val Seq(CommandResult(name, JsonResult(cpuInfo))) = new CpuInfoCommand().execute
 
+    name.value should be ("cpu_info")
     cpuInfo should not be null
     cpuInfo.getAsJsonObject should haveValidFields
   }
 
   "mem-info command" should "return a json object with some fields" in {
-    val memInfo = new MemInfoCommand().execute.content
+    val Seq(CommandResult(name, JsonResult(memInfo))) = new MemInfoCommand().execute
 
+    name.value should be ("mem_info")
     memInfo should not be null
     memInfo.getAsJsonObject should haveValidFields
   }
@@ -62,34 +54,12 @@
     val wrapper = Mockito.mock(classOf[SitePathsWrapper])
     Mockito.when(wrapper.getAsPath(any[String])).thenReturn(Paths.get("/tmp"))
 
-    val diskInfo = new DiskInfoCommand(wrapper).execute.content
+    val Seq(CommandResult(name, JsonResult(diskInfo))) = new DiskInfoCommand(wrapper).execute
 
+    name.value should be ("disk_info")
     diskInfo should not be null
     diskInfo.getAsJsonObject should haveValidFields
   }
 
-  "Bundle builder" should "create an output zip file" in {
-    val zipFile = new SupportBundleBuilder(tmpPath.toPath, new Gson).build(Seq())
-
-    zipFile should (be a 'file
-      and endWithExtension("zip"))
-  }
-
-  it should "create a one entry in the output zip file" in {
-    val file = new SupportBundleBuilder(tmpPath.toPath, new Gson)
-      .build(Seq(CommandResult("foo", new JsonPrimitive("bar"))))
-
-    val zipEntries = new ZipFile(file).entries.asScala
-    zipEntries should have size (1)
-  }
-
-  it should "add the Json primitive into the zip entry" in {
-    val file = new SupportBundleBuilder(tmpPath.toPath, new Gson)
-      .build(Seq(CommandResult("entry-name", new JsonPrimitive("foo"))))
-
-    val zipEntries = new ZipFile(file).entries.asScala.toSeq
-    zipEntries.map(_.getName) should contain("entry-name")
-  }
-
 }
 
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/support/TmpPath.scala b/src/test/scala/com/googlesource/gerrit/plugins/support/TmpPath.scala
new file mode 100644
index 0000000..825f5f2
--- /dev/null
+++ b/src/test/scala/com/googlesource/gerrit/plugins/support/TmpPath.scala
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlesource.gerrit.plugins.support
+
+import java.io.{File, PrintWriter}
+
+trait TmpPath {
+  val tmpPath = File.createTempFile(classOf[GerritSupportTest].getName, "").getParentFile
+
+  def millis = System.currentTimeMillis.toString
+
+  def tmpFile = {
+    val file = File.createTempFile("file.txt", millis, tmpPath)
+    file.deleteOnExit
+    file
+  }
+
+  def tmpConfigFile = {
+    val file = File.createTempFile(s"file.$millis", ".config", tmpPath)
+    file.deleteOnExit()
+    new PrintWriter(file) {
+      try {
+        write("something")
+      } finally {
+        close
+      }
+    }
+    file
+  }
+}