OutdatedKotlinRuntimeNotification: J2K and cleanup

This commit is contained in:
Dmitry Jemerov
2015-11-21 11:38:38 +01:00
parent ca9979f9fb
commit 85ea295e92
2 changed files with 150 additions and 231 deletions
@@ -25,6 +25,7 @@ import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.JarFileSystem
@@ -45,7 +46,6 @@ import org.jetbrains.kotlin.idea.framework.JSLibraryStdPresentationProvider
import org.jetbrains.kotlin.idea.framework.JavaRuntimePresentationProvider
import org.jetbrains.kotlin.idea.framework.LibraryPresentationProviderUtil
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
import org.jetbrains.kotlin.idea.versions.OutdatedKotlinRuntimeNotification.showRuntimeJarNotFoundDialog
import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion
import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
import org.jetbrains.kotlin.utils.PathUtil
@@ -122,7 +122,7 @@ private fun updateJar(
}
if (!jarPath.exists()) {
showRuntimeJarNotFoundDialog(project, libraryJarDescriptor.jarName).run()
showRuntimeJarNotFoundDialog(project, libraryJarDescriptor.jarName)
return
}
@@ -258,3 +258,9 @@ private fun <T> collectAllKeys(id: ID<T, Void>, modules: List<Module>): Collecti
return allKeys
}
fun showRuntimeJarNotFoundDialog(project: Project, jarName: String) {
Messages.showErrorDialog(project,
jarName + " is not found. Make sure plugin is properly installed.",
"No Runtime Found")
}
@@ -14,263 +14,176 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.versions;
package org.jetbrains.kotlin.idea.versions
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.framework.library.LibraryVersionProperties;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.text.VersionComparatorUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.KotlinPluginUtil;
import org.jetbrains.kotlin.idea.framework.JSLibraryStdPresentationProvider;
import org.jetbrains.kotlin.idea.framework.JavaRuntimePresentationProvider;
import org.jetbrains.kotlin.idea.framework.LibraryPresentationProviderUtil;
import com.google.common.collect.Lists
import com.intellij.ide.util.PropertiesComponent
import com.intellij.notification.Notification
import com.intellij.notification.NotificationListener
import com.intellij.notification.NotificationType
import com.intellij.notification.Notifications
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.AbstractProjectComponent
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.startup.StartupManager
import com.intellij.util.PathUtil.getLocalFile
import com.intellij.util.text.VersionComparatorUtil
import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.idea.framework.JSLibraryStdPresentationProvider
import org.jetbrains.kotlin.idea.framework.JavaRuntimePresentationProvider
import org.jetbrains.kotlin.idea.framework.LibraryPresentationProviderUtil
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import java.io.IOException
import javax.swing.event.HyperlinkEvent
import javax.swing.event.HyperlinkEvent;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
class OutdatedKotlinRuntimeNotification(project: Project) : AbstractProjectComponent(project) {
import static com.intellij.util.PathUtil.getLocalFile;
private data class VersionedLibrary(val library: Library, val version: String?)
public class OutdatedKotlinRuntimeNotification extends AbstractProjectComponent {
private static final String SUPPRESSED_PROPERTY_NAME = "oudtdated.runtime.suppressed.plugin.version";
private static final String OUTDATED_RUNTIME_GROUP_DISPLAY_ID = "Outdated Kotlin Runtime";
override fun projectOpened() {
StartupManager.getInstance(myProject).registerPostStartupActivity(Runnable {
val pluginVersion = KotlinPluginUtil.getPluginVersion()
if ("@snapshot@" == pluginVersion) return@Runnable // plugin is run from sources, can't compare versions
public OutdatedKotlinRuntimeNotification(Project project) {
super(project);
}
// user already clicked suppress
if (pluginVersion == PropertiesComponent.getInstance(myProject).getValue(SUPPRESSED_PROPERTY_NAME)) return@Runnable
private static class VersionedLibrary extends Pair<Library, String> {
public VersionedLibrary(@NotNull Library library, @Nullable String version) {
super(library, version);
}
@NotNull
public Library getLibrary() {
return first;
}
@Nullable
public String getVersion() {
return second;
}
}
@Override
public void projectOpened() {
StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() {
@Override
public void run() {
final String pluginVersion = KotlinPluginUtil.getPluginVersion();
if ("@snapshot@".equals(pluginVersion)) return; // plugin is run from sources, can't compare versions
// user already clicked suppress
if (pluginVersion.equals(PropertiesComponent.getInstance(myProject).getValue(SUPPRESSED_PROPERTY_NAME))) return;
Collection<VersionedLibrary> versionedOutdatedLibraries = findOutdatedKotlinLibraries(myProject);
if (versionedOutdatedLibraries.isEmpty()) {
return;
}
Collection<Library> outdatedLibraries = extractLibraries(versionedOutdatedLibraries);
String message;
if (versionedOutdatedLibraries.size() == 1) {
VersionedLibrary versionedLibrary = versionedOutdatedLibraries.iterator().next();
String version = versionedLibrary.getVersion();
String readableVersion = version == null ? "unknown" : version;
String libraryName = versionedLibrary.getLibrary().getName();
message = String.format(
"<p>Your version of Kotlin runtime in '%s' library is %s, while plugin version is %s.</p>" +
"<p>Runtime library should be updated to avoid compatibility problems.</p>" +
"<p><a href=\"update\">Update Runtime</a> <a href=\"ignore\">Ignore</a></p>",
libraryName,
readableVersion,
pluginVersion);
}
else {
String libraryNames = StringUtil.join(outdatedLibraries, new Function<Library, String>() {
@Override
public String fun(Library library) {
return library.getName();
}
}, ", ");
message = String.format(
"<p>Version of Kotlin runtime is outdated in several libraries (%s). Plugin version is %s.</p>" +
"<p>Runtime libraries should be updated to avoid compatibility problems.</p>" +
"<p><a href=\"update\">Update All</a> <a href=\"ignore\">Ignore</a></p>",
libraryNames,
pluginVersion);
}
Notifications.Bus.notify(new Notification(OUTDATED_RUNTIME_GROUP_DISPLAY_ID, "Outdated Kotlin Runtime", message,
NotificationType.WARNING, new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if ("update".equals(event.getDescription())) {
Collection<VersionedLibrary> versionedOutdatedLibraries = findOutdatedKotlinLibraries(myProject);
Collection<Library> outdatedLibraries = extractLibraries(versionedOutdatedLibraries);
KotlinRuntimeLibraryUtilKt.updateLibraries(myProject, outdatedLibraries);
suggestDeleteKotlinJsIfNeeded(outdatedLibraries);
}
else if ("ignore".equals(event.getDescription())) {
PropertiesComponent.getInstance(myProject).setValue(SUPPRESSED_PROPERTY_NAME, pluginVersion);
}
else {
throw new AssertionError();
}
notification.expire();
}
}
}), myProject);
val versionedOutdatedLibraries = findOutdatedKotlinLibraries(myProject)
if (versionedOutdatedLibraries.isEmpty()) {
return@Runnable
}
});
}
private void deleteKotlinJs() {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
VirtualFile kotlinJsFile = myProject.getBaseDir().findFileByRelativePath("script/kotlin.js");
if (kotlinJsFile == null) return;
val message: String = if (versionedOutdatedLibraries.size == 1) {
val versionedLibrary = versionedOutdatedLibraries.first()
VirtualFile fileToDelete = getLocalFile(kotlinJsFile);
try {
VirtualFile parent = fileToDelete.getParent();
fileToDelete.delete(this);
parent.refresh(false, true);
}
catch (IOException ex) {
Notifications.Bus.notify(
new Notification(OUTDATED_RUNTIME_GROUP_DISPLAY_ID, "Error", "Could not delete 'script/kotlin.js': " + ex.getMessage(), NotificationType.ERROR));
}
}
});
val version = versionedLibrary.version
val readableVersion = version ?: "unknown"
val libraryName = versionedLibrary.library.name
"<p>Your version of Kotlin runtime in '$libraryName' library is $readableVersion, while plugin version is $pluginVersion.</p>" +
"<p>Runtime library should be updated to avoid compatibility problems.</p>" +
"<p><a href=\"update\">Update Runtime</a> <a href=\"ignore\">Ignore</a></p>"
}
});
}
else {
val libraryNames = versionedOutdatedLibraries.joinToString { it.library.name!! }
private void suggestDeleteKotlinJsIfNeeded(Collection<Library> outdatedLibraries) {
VirtualFile kotlinJsFile = myProject.getBaseDir().findFileByRelativePath("script/kotlin.js");
if (kotlinJsFile == null) return;
boolean addNotification = false;
for(Library library : outdatedLibraries) {
if (LibraryPresentationProviderUtil.isDetected(JSLibraryStdPresentationProvider.getInstance(), library)) {
VirtualFile jsStdlibJar = JSLibraryStdPresentationProvider.getJsStdLibJar(library);
assert jsStdlibJar != null : "jslibFile should not be null";
if (jsStdlibJar.findFileByRelativePath("kotlin.js") == null) {
addNotification = true;
break;
}
"<p>Version of Kotlin runtime is outdated in several libraries ($libraryNames). Plugin version is $pluginVersion.</p>" +
"<p>Runtime libraries should be updated to avoid compatibility problems.</p>" +
"<p><a href=\"update\">Update All</a> <a href=\"ignore\">Ignore</a></p>"
}
}
if (!addNotification) return;
String message = String.format(
"<p>File 'script/kotlin.js' was probably created by an older version of the Kotlin plugin.</p>" +
"<p>The new Kotlin plugin copies an up-to-date version of this file to the output directory automatically, so the old version of it can be deleted.</p>" +
"<p><a href=\"delete\">Delete this file</a> <a href=\"ignore\">Ignore</a></p>");
Notifications.Bus.notify(new Notification(OUTDATED_RUNTIME_GROUP_DISPLAY_ID, "Outdated Kotlin Runtime", message,
NotificationType.WARNING, new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if ("delete".equals(event.getDescription())) {
deleteKotlinJs();
Notifications.Bus.notify(Notification(OUTDATED_RUNTIME_GROUP_DISPLAY_ID, "Outdated Kotlin Runtime", message,
NotificationType.WARNING, NotificationListener { notification, event ->
if (event.eventType == HyperlinkEvent.EventType.ACTIVATED) {
if ("update" == event.description) {
val outdatedLibraries = findOutdatedKotlinLibraries(myProject).map { it.library }
updateLibraries(myProject, outdatedLibraries)
suggestDeleteKotlinJsIfNeeded(outdatedLibraries)
}
else if ("ignore".equals(event.getDescription())) {
// pass
else if ("ignore" == event.description) {
PropertiesComponent.getInstance(myProject).setValue(SUPPRESSED_PROPERTY_NAME, pluginVersion)
}
else {
throw new AssertionError();
throw AssertionError()
}
notification.expire();
notification.expire()
}
}), myProject)
})
}
private fun deleteKotlinJs() {
ApplicationManager.getApplication().invokeLater {
runWriteAction {
val kotlinJsFile = myProject.baseDir.findFileByRelativePath("script/kotlin.js") ?: return@runWriteAction
val fileToDelete = getLocalFile(kotlinJsFile)
try {
val parent = fileToDelete.parent
fileToDelete.delete(this)
parent.refresh(false, true)
}
catch (ex: IOException) {
Notifications.Bus.notify(
Notification(OUTDATED_RUNTIME_GROUP_DISPLAY_ID, "Error", "Could not delete 'script/kotlin.js': " + ex.message, NotificationType.ERROR))
}
}
}), myProject);
}
}
private static Collection<Library> extractLibraries(Collection<VersionedLibrary> libraries) {
return Collections2.transform(libraries, new com.google.common.base.Function<VersionedLibrary, Library>() {
@Override
public Library apply(@Nullable VersionedLibrary versionedLibrary) {
assert versionedLibrary != null;
return versionedLibrary.getLibrary();
}
});
}
private fun suggestDeleteKotlinJsIfNeeded(outdatedLibraries: Collection<Library>) {
myProject.baseDir.findFileByRelativePath("script/kotlin.js") ?: return
@NotNull
private static Collection<VersionedLibrary> findOutdatedKotlinLibraries(@NotNull Project project) {
List<VersionedLibrary> outdatedLibraries = Lists.newArrayList();
var addNotification = false
for (library in outdatedLibraries) {
if (LibraryPresentationProviderUtil.isDetected(JSLibraryStdPresentationProvider.getInstance(), library)) {
val jsStdlibJar = JSLibraryStdPresentationProvider.getJsStdLibJar(library)
assert(jsStdlibJar != null) { "jslibFile should not be null" }
for (Library library : KotlinRuntimeLibraryUtilKt.findKotlinLibraries(project)) {
LibraryVersionProperties libraryVersionProperties =
LibraryPresentationProviderUtil.getLibraryProperties(JavaRuntimePresentationProvider.getInstance(), library);
if (libraryVersionProperties == null) {
libraryVersionProperties =
LibraryPresentationProviderUtil.getLibraryProperties(JSLibraryStdPresentationProvider.getInstance(), library);
}
if (libraryVersionProperties == null) {
continue;
}
String libraryVersion = libraryVersionProperties.getVersionString();
String runtimeVersion = KotlinRuntimeLibraryUtilKt.bundledRuntimeVersion();
boolean isOutdated = isRuntimeOutdated(libraryVersion, runtimeVersion);
if (isOutdated) {
outdatedLibraries.add(new VersionedLibrary(library, libraryVersion));
if (jsStdlibJar!!.findFileByRelativePath("kotlin.js") == null) {
addNotification = true
break
}
}
}
if (!addNotification) return
return outdatedLibraries;
}
val message = "<p>File 'script/kotlin.js' was probably created by an older version of the Kotlin plugin.</p>" +
"<p>The new Kotlin plugin copies an up-to-date version of this file to the output directory automatically, so the old version of it can be deleted.</p>" +
"<p><a href=\"delete\">Delete this file</a> <a href=\"ignore\">Ignore</a></p>"
public static boolean isRuntimeOutdated(String libraryVersion, String runtimeVersion) {
return libraryVersion == null
|| libraryVersion.startsWith("internal-") != runtimeVersion.startsWith("internal-")
|| VersionComparatorUtil.compare(runtimeVersion, libraryVersion) > 0;
}
@NotNull
public static Runnable showRuntimeJarNotFoundDialog(@NotNull final Project project, final @NotNull String jarName) {
return new Runnable() {
@Override
public void run() {
Messages.showErrorDialog(project,
jarName + " is not found. Make sure plugin is properly installed.",
"No Runtime Found");
Notifications.Bus.notify(Notification(OUTDATED_RUNTIME_GROUP_DISPLAY_ID, "Outdated Kotlin Runtime", message,
NotificationType.WARNING, NotificationListener { notification, event ->
if (event.eventType == HyperlinkEvent.EventType.ACTIVATED) {
if ("delete" == event.description) {
deleteKotlinJs()
}
else if ("ignore" == event.description) {
// pass
}
else {
throw AssertionError()
}
notification.expire()
}
};
}), myProject)
}
companion object {
private val SUPPRESSED_PROPERTY_NAME = "oudtdated.runtime.suppressed.plugin.version"
private val OUTDATED_RUNTIME_GROUP_DISPLAY_ID = "Outdated Kotlin Runtime"
private fun findOutdatedKotlinLibraries(project: Project): Collection<VersionedLibrary> {
val outdatedLibraries = Lists.newArrayList<VersionedLibrary>()
for (library in findKotlinLibraries(project)) {
var libraryVersionProperties = LibraryPresentationProviderUtil.getLibraryProperties(JavaRuntimePresentationProvider.getInstance(), library)
if (libraryVersionProperties == null) {
libraryVersionProperties = LibraryPresentationProviderUtil.getLibraryProperties(JSLibraryStdPresentationProvider.getInstance(), library)
}
if (libraryVersionProperties == null) {
continue
}
val libraryVersion = libraryVersionProperties.versionString
val runtimeVersion = bundledRuntimeVersion()
val isOutdated = isRuntimeOutdated(libraryVersion, runtimeVersion)
if (isOutdated) {
outdatedLibraries.add(VersionedLibrary(library, libraryVersion))
}
}
return outdatedLibraries
}
fun isRuntimeOutdated(libraryVersion: String?, runtimeVersion: String): Boolean {
return libraryVersion == null || libraryVersion.startsWith("internal-") != runtimeVersion.startsWith("internal-") ||
VersionComparatorUtil.compare(runtimeVersion, libraryVersion) > 0
}
}
}