From cb4702c4064b1edfb8ac3d53d421df34de9b140a Mon Sep 17 00:00:00 2001 From: Sergey Mashkov Date: Thu, 17 Mar 2016 15:30:10 +0300 Subject: [PATCH] KT-11261 Implement MavenArchetypesProvider extension point --- idea/src/META-INF/maven.xml | 1 + .../KotlinMavenArchetypesProvider.kt | 101 +++++++++++++++ .../maven-central-response.json | 122 ++++++++++++++++++ .../KotlinMavenArchetypesProviderTest.kt | 66 ++++++++++ 4 files changed, 290 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMavenArchetypesProvider.kt create mode 100644 idea/testData/configuration/extractVersions/maven-central-response.json create mode 100644 idea/tests/org/jetbrains/kotlin/idea/configuration/KotlinMavenArchetypesProviderTest.kt diff --git a/idea/src/META-INF/maven.xml b/idea/src/META-INF/maven.xml index c43b790fb22..a3da2212a9c 100644 --- a/idea/src/META-INF/maven.xml +++ b/idea/src/META-INF/maven.xml @@ -5,6 +5,7 @@ + diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMavenArchetypesProvider.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMavenArchetypesProvider.kt new file mode 100644 index 00000000000..97c4eb7709e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMavenArchetypesProvider.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.idea.configuration + +import com.google.gson.JsonElement +import com.google.gson.JsonParser +import com.intellij.util.net.HttpConfigurable +import org.jetbrains.idea.maven.dom.MavenVersionComparable +import org.jetbrains.idea.maven.indices.MavenArchetypesProvider +import org.jetbrains.idea.maven.model.MavenArchetype +import org.jetbrains.kotlin.idea.KotlinPluginUtil +import org.jetbrains.kotlin.utils.ifEmpty +import java.net.HttpURLConnection +import java.net.URLEncoder +import java.util.concurrent.TimeUnit + +class KotlinMavenArchetypesProvider(val kotlinPluginVersion: String) : MavenArchetypesProvider { + constructor() : this(KotlinPluginUtil.getPluginVersion()) + + val VERSIONS_LIST_URL = mavenSearchUrl("org.jetbrains.kotlin", packaging = "maven-archetype", rowsLimit = 1000) + private val versionPrefix by lazy { """^\d+\.\d+\.""".toRegex().find(kotlinPluginVersion)?.value ?: "1.0." } + + private val archetypesBlocking by lazy { + try { + loadVersions().ifEmpty { defaultArchetypes() } + } catch (t: Throwable) { + defaultArchetypes() + } + } + + override fun getArchetypes() = archetypesBlocking.toMutableList() + + private fun defaultArchetypes() = listOf(MavenArchetype("org.jetbrains.kotlin", "kotlin-archetype-jvm", "1.0.0", null, null)) + + private fun loadVersions(): List { + return connectAndApply(VERSIONS_LIST_URL) { urlConnection -> + urlConnection.inputStream.bufferedReader().use { reader -> + extractVersions(JsonParser().parse(reader)) + } + } + } + + internal fun extractVersions(root: JsonElement) = + root.asJsonObject.get("response") + .asJsonObject.get("docs") + .asJsonArray + .map { it.asJsonObject } + .map { MavenArchetype(it.get("g").asString, it.get("a").asString, it.get("v").asString, null, null) } + .filter { it.version?.startsWith(versionPrefix) ?: false } + .maxBy { MavenVersionComparable(it.version) } + .let { listOfNotNull(it) } + + private fun mavenSearchUrl(group: String, artifactId: String? = null, version: String? = null, packaging: String? = null, rowsLimit: Int = 20): String { + val q = listOf( + "g" to group, + "a" to artifactId, + "v" to version, + "p" to packaging + ) + .filter { it.second != null } + .map { "${it.first}:\"${it.second}\"" } + .joinToString(separator = " AND ") + + return "http://search.maven.org/solrsearch/select?q=${q.encodeURL()}&core=gav&rows=$rowsLimit&wt=json" + } + + private fun connectAndApply(url: String, timeoutSeconds: Int = 15, block: (HttpURLConnection) -> R): R { + return HttpConfigurable.getInstance().openHttpConnection(url).use { urlConnection -> + val timeout = TimeUnit.SECONDS.toMillis(timeoutSeconds.toLong()).toInt() + urlConnection.connectTimeout = timeout + urlConnection.readTimeout = timeout + + urlConnection.connect() + block(urlConnection) + } + } + + private fun HttpURLConnection.use(block: (HttpURLConnection) -> R): R = + try { + block(this) + } + finally { + disconnect() + } + + private fun String.encodeURL() = URLEncoder.encode(this, "UTF-8") +} diff --git a/idea/testData/configuration/extractVersions/maven-central-response.json b/idea/testData/configuration/extractVersions/maven-central-response.json new file mode 100644 index 00000000000..ba555720a93 --- /dev/null +++ b/idea/testData/configuration/extractVersions/maven-central-response.json @@ -0,0 +1,122 @@ +{ + "responseHeader": { + "status": 0, + "QTime": 2, + "params": { + "fl": "id,g,a,v,p,ec,timestamp,tags", + "sort": "score desc,timestamp desc,g asc,a asc,v desc", + "indent": "off", + "q": "g:\"org.jetbrains.kotlin\" AND p:\"maven-archetype\"", + "core": "gav", + "wt": "json", + "rows": "200", + "version": "2.2" + } + }, + "response": { + "numFound": 3, + "start": 0, + "docs": [ + { + "id": "org.jetbrains.kotlin:kotlin-archetype-jvm:1.0.0", + "g": "org.jetbrains.kotlin", + "a": "kotlin-archetype-jvm", + "v": "1.0.0", + "p": "maven-archetype", + "timestamp": 1458127798000, + "tags": [ + "using", + "restful", + "creates", + "example", + "webservice" + ], + "ec": [ + "-sources.jar", + ".jar", + ".pom" + ] + }, + { + "id": "org.jetbrains.kotlin:kotlin-archetype-js:1.0.0", + "g": "org.jetbrains.kotlin", + "a": "kotlin-archetype-js", + "v": "1.0.0", + "p": "maven-archetype", + "timestamp": 1458127798000, + "tags": [ + "using", + "restful", + "creates", + "example", + "webservice" + ], + "ec": [ + "-sources.jar", + ".jar", + ".pom" + ] + }, + { + "id": "org.jetbrains.kotlin:kotlin-archetype-jvm:1.0.1", + "g": "org.jetbrains.kotlin", + "a": "kotlin-archetype-jvm", + "v": "1.0.1", + "p": "maven-archetype", + "timestamp": 1458127798000, + "tags": [ + "using", + "restful", + "creates", + "example", + "webservice" + ], + "ec": [ + "-sources.jar", + ".jar", + ".pom" + ] + }, + { + "id": "org.jetbrains.kotlin:kotlin-archetype-jvm:1.0.1", + "g": "org.jetbrains.kotlin", + "a": "kotlin-archetype-jvm", + "v": "1.0.1-2", + "p": "maven-archetype", + "timestamp": 1458127798000, + "tags": [ + "using", + "restful", + "creates", + "example", + "webservice" + ], + "ec": [ + "-sources.jar", + ".jar", + ".pom" + ] + }, + { + "id": "org.jetbrains.kotlin:kotlin-archetype-jvm:1.1.2", + "g": "org.jetbrains.kotlin", + "a": "kotlin-archetype-jvm", + "v": "1.1.2", + "p": "maven-archetype", + "timestamp": 1458127798000, + "tags": [ + "using", + "restful", + "creates", + "example", + "webservice" + ], + "ec": [ + "-sources.jar", + ".jar", + ".pom" + ] + } + ] + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/configuration/KotlinMavenArchetypesProviderTest.kt b/idea/tests/org/jetbrains/kotlin/idea/configuration/KotlinMavenArchetypesProviderTest.kt new file mode 100644 index 00000000000..48af5af2e77 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/configuration/KotlinMavenArchetypesProviderTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.idea.configuration + +import com.google.gson.JsonParser +import org.jetbrains.idea.maven.model.MavenArchetype +import org.junit.Test + +import org.junit.Assert.* +import java.io.File + +class KotlinMavenArchetypesProviderTest { + private val BASE_PATH = "idea/testData/configuration/" + + @Test + fun extractVersions() { + val file = File(BASE_PATH, "extractVersions/maven-central-response.json") + assertTrue("Test data is missing", file.exists()) + + val json = file.bufferedReader().use { + JsonParser().parse(it) + } + + val versions = KotlinMavenArchetypesProvider("1.0.0-Release-Something-1886").extractVersions(json) + + assertEquals( + listOf( + MavenArchetype("org.jetbrains.kotlin", "kotlin-archetype-jvm", "1.0.1-2", null, null) + ).sortedBy { it.artifactId + "." + it.version }, + versions.sortedBy { it.artifactId + "." + it.version } + ) + } + + @Test + fun extractVersionsNewPlugin() { + val file = File(BASE_PATH, "extractVersions/maven-central-response.json") + assertTrue("Test data is missing", file.exists()) + + val json = file.bufferedReader().use { + JsonParser().parse(it) + } + + val versions = KotlinMavenArchetypesProvider("1.1.0-Next-Release-Something-9999").extractVersions(json) + + assertEquals( + listOf( + MavenArchetype("org.jetbrains.kotlin", "kotlin-archetype-jvm", "1.1.2", null, null) + ).sortedBy { it.artifactId + "." + it.version }, + versions.sortedBy { it.artifactId + "." + it.version } + ) + } +} \ No newline at end of file