Browse files

Two small data object for in-memory representation of plugin and ecos…

…ystem descriptor files
  • Loading branch information...
1 parent 95465d3 commit 88f09e30ea460bda95c8157b520b76dac1a93bdc @dotta dotta committed Oct 5, 2012
View
24 .classpath
@@ -1,7 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry output="target/scala-2.9.2/classes" path="src/main/scala" kind="src"></classpathentry>
- <classpathentry output="target/scala-2.9.2/test-classes" path="src/test/scala" kind="src"></classpathentry>
- <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"></classpathentry>
- <classpathentry path="org.eclipse.jdt.launching.JRE_CONTAINER" kind="con"></classpathentry>
- <classpathentry path="bin" kind="output"></classpathentry>
+ <classpathentry kind="src" path="src/main/scala"/>
+ <classpathentry kind="src" path="src/main/resources"/>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="src" path="src/test/scala"/>
+ <classpathentry kind="src" path="src/test/java"/>
+ <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/net.databinder.dispatch/core_2.9.1/jars/core_2.9.1-0.9.1.jar" sourcepath="/Users/mirco/.ivy2/cache/net.databinder.dispatch/core_2.9.1/srcs/core_2.9.1-0.9.1-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/com.ning/async-http-client/jars/async-http-client-1.7.5.jar" sourcepath="/Users/mirco/.ivy2/cache/com.ning/async-http-client/srcs/async-http-client-1.7.5-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/io.netty/netty/bundles/netty-3.4.4.Final.jar" sourcepath="/Users/mirco/.ivy2/cache/io.netty/netty/srcs/netty-3.4.4.Final-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/org.slf4j/slf4j-api/jars/slf4j-api-1.6.2.jar" sourcepath="/Users/mirco/.ivy2/cache/org.slf4j/slf4j-api/srcs/slf4j-api-1.6.2-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/com.typesafe/config/bundles/config-0.5.2.jar" sourcepath="/Users/mirco/.ivy2/cache/com.typesafe/config/srcs/config-0.5.2-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/junit/junit/jars/junit-4.8.1.jar" sourcepath="/Users/mirco/.ivy2/cache/junit/junit/srcs/junit-4.8.1-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/com.novocode/junit-interface/jars/junit-interface-0.10-M1.jar" sourcepath="/Users/mirco/.ivy2/cache/com.novocode/junit-interface/srcs/junit-interface-0.10-M1-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/junit/junit-dep/jars/junit-dep-4.10.jar" sourcepath="/Users/mirco/.ivy2/cache/junit/junit-dep/srcs/junit-dep-4.10-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/org.hamcrest/hamcrest-core/jars/hamcrest-core-1.1.jar" sourcepath="/Users/mirco/.ivy2/cache/org.hamcrest/hamcrest-core/srcs/hamcrest-core-1.1-sources.jar"/>
+ <classpathentry kind="lib" path="/Users/mirco/.ivy2/cache/org.scala-tools.testing/test-interface/jars/test-interface-0.5.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="output" path="bin"/>
</classpath>
View
1 build.sbt
@@ -14,5 +14,6 @@ credentials += Credentials(Path.userHome / ".credentials")
libraryDependencies ++= Seq(
"net.databinder.dispatch" %% "core" % "0.9.1",
+ "com.typesafe" % "config" % "0.5.2",
"junit" % "junit" % "4.8.1" % "test",
"com.novocode" % "junit-interface" % "0.10-M1" % "test")
View
56 src/main/scala/org/scalaide/buildtools/EcosystemsDescriptor.scala
@@ -0,0 +1,56 @@
+package org.scalaide.buildtools
+
+import java.net.URL
+import com.typesafe.config.Config
+import scala.collection.JavaConverters.asScalaBufferConverter
+import com.typesafe.config.ConfigFactory
+import java.io.File
+
+/** In-memory representation of a ecosystems descriptor file.
+ * The descriptor file is parsed using the typesafe-config library.
+ *
+ * Instances of this class should be created via the factory methods `load` in the
+ * companion object.
+ *
+ * Example of a ecosystems descriptor file:
+ *
+ * ecosystems-descriptor {
+ * ids = ["stable-scala29", "stable-scala210", "dev-scala29", "dev-scala210"]
+ * stable-scala29 {
+ * site = "http://download.scala-ide.org/ecosystem/scala29/stable/site"
+ * }
+ * stable-scala210 {
+ * site = "http://download.scala-ide.org/ecosystem/scala210/stable/site"
+ * }
+ * dev-scala29 {
+ * site = "http://download.scala-ide.org/ecosystem/scala29/dev/site"
+ * }
+ * dev-scala210 {
+ * site = "http://download.scala-ide.org/ecosystem/scala210/dev/site"
+ * }
+ * }
+ */
+class EcosystemsDescriptor(config: Config) {
+ val ecosystems: List[Ecosystem] = {
+ val ids = config.getStringList(EcosystemsDescriptor.Keys.idsKey).asScala.toList
+ for(ecosystemId <- ids) yield {
+ val site = config.getString(EcosystemsDescriptor.Keys.site(ecosystemId))
+ val siteUrl = new URL(site)
+ Ecosystem(ecosystemId, siteUrl)
+ }
+ }
+}
+
+object EcosystemsDescriptor {
+ object Keys {
+ val root = "ecosystems-descriptor"
+ val idsKey = root + ".ids"
+ def site(ecosystemId: String): String = root + "." + ecosystemId + ".site"
+ }
+
+ def load(file: File): EcosystemsDescriptor = {
+ new EcosystemsDescriptor(ConfigFactory.parseFile(file))
+ }
+}
+
+final case class Ecosystem(id: String, site: URL)
View
150 src/main/scala/org/scalaide/buildtools/PluginDescriptor.scala
@@ -0,0 +1,150 @@
+package org.scalaide.buildtools
+
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigFactory
+import java.io.File
+import java.net.URL
+import scala.util.control.Exception._
+import scala.Array.canBuildFrom
+import scala.collection.JavaConverters.asScalaBufferConverter
+
+/** In-memory representation of a plugin descriptor file.
+ * The descriptor file is parsed using the typesafe-config library.
+ *
+ * A light validation of the descriptor is performed in the constructor.
+ *
+ * Instances of this class should be created via the factory methods `load` and `loadAll` in the
+ * companion object.
+ *
+ * Example of a plugin descriptor file:
+ *
+ * plugin-descriptor {
+ * source-repository = "https://github.com/scala-ide/scala-worksheet"
+ * documentation = "https://github.com/scala-ide/scala-worksheet/wiki/Getting-Started"
+ * issue-tracker = "https://github.com/scala-ide/scala-worksheet/issues/"
+ * update-sites = ["http://scala-ide.dreamhosters.com/nightly-update-worksheet-scalaide21-29/site/",
+ * "http://scala-ide.dreamhosters.com/nightly-update-worksheet-scalaide21-210/site/"]
+ * source-feature-id = org.scala-ide.worksheet.source.feature
+ * }
+ *
+ * @param name Is the plugin's feature id.
+ */
+final class PluginDescriptor(name: String, config: Config) {
+ require(name.nonEmpty)
+
+ import PluginDescriptor._
+ import PluginDescriptor.Keys._
+
+ val featureId: String = name
+ val website: Option[URL] = maybeURL(config, websiteKey)
+ val sourceRepo: URL = obtainURL(config, sourceRepoKey)
+ val documentation: URL = obtainURL(config, documentationKey)
+ val issueTracker: URL = obtainURL(config, issueTrackerKey)
+ val updateSites: List[URL] = {
+ val urls = obtainURLs(config, updateSitesKey)
+ if (urls.isEmpty) throw new FailedToRetrieveKeyOrValue(name, updateSitesKey, new IllegalStateException("Expected at leats one value for " + updateSitesKey))
+ urls
+ }
+ val category: Option[Category] = maybeString(config, categoryKey) flatMap (Category(_))
+ val sourceFeatureId: String = obtainString(config, sourceFeatureIdKey)
+
+ private def maybeURL(config: Config, path: String): Option[URL] = maybeString(config, path) map (new URL(_))
+
+ private def maybeString(config: Config, path: String): Option[String] = maybe(config, path) {
+ Option(config.getString(path))
+ }
+
+ private def maybe[T](config: Config, path: String)(f: => Option[T]): Option[T] = {
+ if (config.hasPath(path)) f
+ else None
+ }
+
+ private def obtainURLs(config: Config, path: String): List[URL] = {
+ import scala.collection.JavaConverters._
+ try {
+ for (raw <- config.getStringList(path).asScala.toList) yield new URL(raw)
+ } catch {
+ case e: Exception => throw new FailedToRetrieveKeyOrValue(name, path, e)
+ }
+ }
+
+ private def obtainURL(config: Config, path: String): URL = maybeURL(config, path) match {
+ case None => throw new FailedToRetrieveKeyOrValue(name, path, new NoSuchElementException(path))
+ case Some(url) => url
+ }
+
+ private def obtainString(config: Config, path: String): String = maybeString(config, path) match {
+ case None => throw new FailedToRetrieveKeyOrValue(name, path, new NoSuchElementException(path))
+ case Some(string) => string
+ }
+}
+
+object PluginDescriptor {
+ object Keys {
+ private val root = "plugin-descriptor"
+ protected[buildtools] val websiteKey = root + ".website"
+ protected[buildtools] val sourceRepoKey = root + ".source-repository"
+ protected[buildtools] val documentationKey = root + ".documentation"
+ protected[buildtools] val issueTrackerKey = root + ".issue-tracker"
+ protected[buildtools] val updateSitesKey = root + ".update-sites"
+ protected[buildtools] val categoryKey = root + ".category"
+ protected[buildtools] val sourceFeatureIdKey = root + ".source-feature-id"
+ }
+
+ sealed abstract class Category {
+ def name: String = toString()
+ }
+ private case object Category {
+ def apply(value: String): Option[Category] = {
+ if (value.equalsIgnoreCase(Stable.toString)) Some(Stable)
+ else if (value.equalsIgnoreCase(Incubation.toString)) Some(Incubation)
+ else None
+ }
+ }
+ private case object Incubation extends Category
+ private case object Stable extends Category
+
+ /** Load in memory all plugin descriptors files in the passed `folder`.*/
+ def loadAll(folder: File): Array[Either[PluginDescriptorException, PluginDescriptor]] = {
+ require(folder.isDirectory, folder.getAbsolutePath + " is not a folder")
+
+ val files = folder.listFiles()
+ stopIfDuplicates(files)
+
+ files map (load)
+ }
+
+ /** Load in memory the passed plugin descriptor `file`.*/
+ def load(file: File): Either[PluginDescriptorException, PluginDescriptor] = {
+ require(file.isFile, file.getAbsolutePath + " is not a file")
+ parse(file).right.map(new PluginDescriptor(file.getName, _))
+ }
+
+ /** Parse the configuration `file`.*/
+ private def parse(file: File): Either[FailedParsingDescriptor, Config] =
+ try {
+ Right(ConfigFactory.parseFile(file))
+ } catch {
+ case e: Exception => Left(new FailedParsingDescriptor(file.getName, e))
+ }
+
+ /** Throws an exception if two (or more) of the passed `files` have the same (case-insensitive) name.*/
+ private def stopIfDuplicates(files: Array[File]): Unit = {
+ val seen = new collection.mutable.HashMap[String, File]()
+ val duplicates = new collection.mutable.ListBuffer[(String, String)]
+ for {
+ file <- files
+ name = file.getName.toLowerCase
+ } seen get name match {
+ case None => seen update (name, file)
+ case Some(other) => duplicates += ((file.getName, other.getName))
+ }
+
+ if (duplicates.nonEmpty) throw new DuplicatePluginDescriptors(duplicates.toList)
+ }
+
+ sealed abstract class PluginDescriptorException(cause: String, t: Exception) extends Exception(cause, t)
+ final class DuplicatePluginDescriptors(duplicates: List[(String, String)]) extends PluginDescriptorException("Duplicated plugin descriptor files: " + duplicates.mkString("{", ",", "}"), null)
+ final class FailedParsingDescriptor(descriptorFileName: String, t: Exception) extends PluginDescriptorException(descriptorFileName, t)
+ final class FailedToRetrieveKeyOrValue(descriptorFileName: String, key: String, t: Exception) extends PluginDescriptorException("In %s, key: %s is missing or its value is incorrect.".format(descriptorFileName, key), t)
+}
View
27 src/test/scala/org/scalaide/buildtools/EcosystemsDescriptorTests.scala
@@ -0,0 +1,27 @@
+package org.scalaide.buildtools
+
+import scala.collection.JavaConversions._
+import scala.collection.Map
+import org.junit.Before
+import org.junit.Test
+import com.typesafe.config.ConfigFactory
+import java.net.URL
+import org.junit.Assert
+
+class EcosystemsDescriptorTests {
+ import EcosystemsDescriptor.Keys._
+
+ @Test
+ def ecosystemsDescriptorIsCorrectlyLoaded() {
+ val expected = List(Ecosystem("stable-scala29", new URL("http://download.scala-ide.org/ecosystem/scala29/stable/site")),
+ Ecosystem("stable-scala210", new URL("http://download.scala-ide.org/ecosystem/scala210/stable/site")),
+ Ecosystem("dev-scala29", new URL("http://download.scala-ide.org/ecosystem/scala29/dev/site")),
+ Ecosystem("dev-scala210", new URL("http://download.scala-ide.org/ecosystem/scala210/dev/site")))
+ val resource = EcosystemsDescriptor.getClass().getResource("ecosystems.conf")
+ val file = new java.io.File(resource.toURI())
+
+ val descriptor = EcosystemsDescriptor.load(file)
+
+ Assert.assertEquals(expected, descriptor.ecosystems)
+ }
+}
View
124 src/test/scala/org/scalaide/buildtools/PluginDescriptorTest.scala
@@ -0,0 +1,124 @@
+package org.scalaide.buildtools
+
+import java.net.MalformedURLException
+import java.net.URL
+
+import scala.collection.JavaConversions._
+import scala.collection.JavaConverters.seqAsJavaListConverter
+import scala.collection.Map
+
+import org.junit.Assert
+import org.junit.Before
+
+import org.junit.Test
+
+import com.typesafe.config.ConfigFactory
+
+import PluginDescriptor.Keys.categoryKey
+import PluginDescriptor.Keys.documentationKey
+import PluginDescriptor.Keys.issueTrackerKey
+import PluginDescriptor.Keys.sourceFeatureIdKey
+import PluginDescriptor.Keys.sourceRepoKey
+import PluginDescriptor.Keys.updateSitesKey
+import PluginDescriptor.Keys.websiteKey
+
+class PluginDescriptorTest {
+
+ import PluginDescriptor._
+ import PluginDescriptor.Keys._
+
+ private var defaultConfigValues: Map[String, Any] = _
+
+ @Before
+ def beforeTest() {
+ defaultConfigValues = Map(
+ sourceRepoKey -> "http://github.com/plugin",
+ documentationKey -> "http://github.com/plugin/documentation",
+ issueTrackerKey -> "http://github.com/plugin/issues",
+ updateSitesKey -> List("http://github.com/plugin/download").asJava,
+ sourceFeatureIdKey -> "org.scala-ide.sdt.feature")
+ }
+
+ @Test
+ def websiteURLIsOptional() {
+ val config = ConfigFactory.parseMap(defaultConfigValues)
+ val descriptor = new PluginDescriptor("test", config)
+ val websiteURL = descriptor.website
+ Assert.assertTrue(websiteURL.isEmpty)
+ }
+
+ @Test(expected = classOf[MalformedURLException])
+ def invalidWebsiteURL() {
+ val config = ConfigFactory.parseMap(defaultConfigValues + (websiteKey -> "invalid url"))
+ val descriptor = new PluginDescriptor("test", config)
+ }
+
+ @Test
+ def websiteHttpURLIsOK() {
+ val config = ConfigFactory.parseMap(defaultConfigValues + (websiteKey -> "http://scala-ide.org/"))
+ val descriptor = new PluginDescriptor("test", config)
+ val websiteURL = descriptor.website
+ Assert.assertTrue(websiteURL.isDefined)
+ }
+
+ @Test
+ def websiteHttpsURLIsOK() {
+ val config = ConfigFactory.parseMap(defaultConfigValues + (websiteKey -> "https://scala-ide.org/"))
+ val descriptor = new PluginDescriptor("test", config)
+ val websiteURL = descriptor.website
+ Assert.assertTrue(websiteURL.isDefined)
+ }
+
+ @Test
+ def categoryIncubationIsOK() {
+ val config = ConfigFactory.parseMap(defaultConfigValues + (categoryKey -> "incubation"))
+ val descriptor = new PluginDescriptor("test", config)
+ val category = descriptor.category
+ Assert.assertTrue(category.get.name == "Incubation")
+ }
+
+ @Test
+ def categoryStableIsOK() {
+ val config = ConfigFactory.parseMap(defaultConfigValues + (categoryKey -> "stable"))
+ val descriptor = new PluginDescriptor("test", config)
+ val category = descriptor.category
+ Assert.assertTrue(category.get.name == "Stable")
+ }
+
+ @Test
+ def invalidCategory() {
+ val config = ConfigFactory.parseMap(defaultConfigValues + (categoryKey -> "wrong"))
+ val descriptor = new PluginDescriptor("test", config)
+ val category = descriptor.category
+ Assert.assertTrue(category.isEmpty)
+ }
+
+ @Test
+ def loadPluginDescriptor() {
+ val resource = PluginDescriptor.getClass().getResource("org.scala-ide.worksheet.feature")
+ val file = new java.io.File(resource.toURI())
+ PluginDescriptor.load(file) match {
+ case Left(ex) => Assert.fail(ex.getMessage())
+ case Right(descriptor) =>
+ Assert.assertEquals(descriptor.sourceRepo, new URL("https://github.com/scala-ide/scala-worksheet"))
+ Assert.assertEquals(descriptor.documentation, new URL("https://github.com/scala-ide/scala-worksheet/wiki/Getting-Started"))
+ Assert.assertEquals(descriptor.issueTracker, new URL("https://github.com/scala-ide/scala-worksheet/issues/"))
+ Assert.assertEquals(descriptor.updateSites, List(new URL("http://scala-ide.dreamhosters.com/nightly-update-worksheet-scalaide21-29/site/"), new URL("http://scala-ide.dreamhosters.com/nightly-update-worksheet-scalaide21-210/site/")))
+ Assert.assertEquals(descriptor.category.get.name, "Incubation")
+ Assert.assertEquals(descriptor.sourceFeatureId, "org.scala-ide.worksheet.source.feature")
+ }
+ }
+
+ @Test(expected = classOf[FailedToRetrieveKeyOrValue])
+ def loadFailsIfMandatoryKeyIsMissing() {
+ val resource = PluginDescriptor.getClass().getResource("org.scala-ide.worksheet.feature-incorrect")
+ val file = new java.io.File(resource.toURI())
+ PluginDescriptor.load(file)
+ }
+
+ @Test(expected = classOf[FailedToRetrieveKeyOrValue])
+ def loadFailsIfMandatoryValueIsMissing() {
+ val config = ConfigFactory.parseMap(defaultConfigValues + (updateSitesKey -> Nil.asJava))
+ val descriptor = new PluginDescriptor("test", config)
+ }
+}
View
16 src/test/scala/org/scalaide/buildtools/ecosystems.conf
@@ -0,0 +1,16 @@
+ecosystems-descriptor {
+ ids = ["stable-scala29", "stable-scala210", "dev-scala29", "dev-scala210"]
+
+ stable-scala29 {
+ site = "http://download.scala-ide.org/ecosystem/scala29/stable/site"
+ }
+ stable-scala210 {
+ site = "http://download.scala-ide.org/ecosystem/scala210/stable/site"
+ }
+ dev-scala29 {
+ site = "http://download.scala-ide.org/ecosystem/scala29/dev/site"
+ }
+ dev-scala210 {
+ site = "http://download.scala-ide.org/ecosystem/scala210/dev/site"
+ }
+}
View
9 src/test/scala/org/scalaide/buildtools/org.scala-ide.worksheet.feature
@@ -0,0 +1,9 @@
+plugin-descriptor {
+ source-repository = "https://github.com/scala-ide/scala-worksheet"
+ documentation = "https://github.com/scala-ide/scala-worksheet/wiki/Getting-Started"
+ issue-tracker = "https://github.com/scala-ide/scala-worksheet/issues/"
+ update-sites = ["http://scala-ide.dreamhosters.com/nightly-update-worksheet-scalaide21-29/site/",
+ "http://scala-ide.dreamhosters.com/nightly-update-worksheet-scalaide21-210/site/"]
+ category = Incubation
+ source-feature-id = org.scala-ide.worksheet.source.feature
+}
View
8 src/test/scala/org/scalaide/buildtools/org.scala-ide.worksheet.feature-incorrect
@@ -0,0 +1,8 @@
+plugin-descriptor {
+ documentation = "https://github.com/scala-ide/scala-worksheet/wiki/Getting-Started"
+ issue-tracker = "https://github.com/scala-ide/scala-worksheet/issues/"
+ update-sites = ["http://scala-ide.dreamhosters.com/nightly-update-worksheet-scalaide21-29/site/",
+ "http://scala-ide.dreamhosters.com/nightly-update-worksheet-scalaide21-210/site/"]
+ category = Incubation
+ source-feature-id = org.scala-ide.worksheet.source.feature
+}

0 comments on commit 88f09e3

Please sign in to comment.