From 99520c9f54bc892bdbcaca13a740d24fbba11248 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Wed, 20 Mar 2013 14:25:22 +0400 Subject: [PATCH] Update several libraries in project all at once --- .../CachingLibraryPresentationProvider.java | 26 ++- .../JavaRuntimePresentationProvider.java | 20 ++- .../versions/KotlinRuntimeLibraryUtil.java | 46 ++--- .../OutdatedKotlinRuntimeNotification.java | 164 ++++++++++++++++-- 4 files changed, 213 insertions(+), 43 deletions(-) diff --git a/idea/src/org/jetbrains/jet/plugin/framework/CachingLibraryPresentationProvider.java b/idea/src/org/jetbrains/jet/plugin/framework/CachingLibraryPresentationProvider.java index 7b657729645..0a6024c1646 100644 --- a/idea/src/org/jetbrains/jet/plugin/framework/CachingLibraryPresentationProvider.java +++ b/idea/src/org/jetbrains/jet/plugin/framework/CachingLibraryPresentationProvider.java @@ -18,8 +18,10 @@ package org.jetbrains.jet.plugin.framework; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.libraries.*; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.List; @@ -34,17 +36,33 @@ public abstract class CachingLibraryPresentationProvider classesRoots) { - return cachedDetect(classesRoots, getKind()); + return cachedDetect(classesRoots, getKind()) != null; } - private static boolean cachedDetect(@NotNull List classesRoots, @NotNull final LibraryKind libraryKind) { - return !LibraryDetectionManager.getInstance().processProperties( + @Nullable + public T getLibraryProperties(@NotNull Library library) { + return cachedDetect(Arrays.asList(library.getFiles(OrderRootType.CLASSES)), getKind()); + } + + @Nullable + private T cachedDetect(@NotNull List classesRoots, @NotNull final LibraryKind libraryKind) { + final Ref propertiesRef = new Ref(); + + LibraryDetectionManager.getInstance().processProperties( classesRoots, new LibraryDetectionManager.LibraryPropertiesProcessor() { @Override public

boolean processProperties(@NotNull LibraryKind processedKind, @NotNull P properties) { - return !libraryKind.equals(processedKind); + if (libraryKind.equals(processedKind)) { + //noinspection unchecked + propertiesRef.set((T)properties); + return false; + } + + return true; } }); + + return propertiesRef.get(); } } diff --git a/idea/src/org/jetbrains/jet/plugin/framework/JavaRuntimePresentationProvider.java b/idea/src/org/jetbrains/jet/plugin/framework/JavaRuntimePresentationProvider.java index 6286b82a0e8..0089d2a4797 100644 --- a/idea/src/org/jetbrains/jet/plugin/framework/JavaRuntimePresentationProvider.java +++ b/idea/src/org/jetbrains/jet/plugin/framework/JavaRuntimePresentationProvider.java @@ -17,6 +17,8 @@ package org.jetbrains.jet.plugin.framework; import com.intellij.framework.library.LibraryVersionProperties; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryPresentationProvider; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; @@ -26,6 +28,7 @@ import org.jetbrains.jet.plugin.versions.KotlinRuntimeLibraryUtil; import org.jetbrains.jet.utils.PathUtil; import javax.swing.*; +import java.util.Arrays; import java.util.List; public class JavaRuntimePresentationProvider extends CachingLibraryPresentationProvider { @@ -46,12 +49,27 @@ public class JavaRuntimePresentationProvider extends CachingLibraryPresentationP @Nullable @Override public LibraryVersionProperties detect(@NotNull List classesRoots) { + VirtualFile stdJar = getRuntimeJar(classesRoots); + if (stdJar != null) { + return new LibraryVersionProperties(KotlinRuntimeLibraryUtil.getLibraryVersion(stdJar)); + } + + return null; + } + + @Nullable + public static VirtualFile getRuntimeJar(@NotNull List classesRoots) { for (VirtualFile root : classesRoots) { if (root.getName().equals(PathUtil.KOTLIN_JAVA_RUNTIME_JAR)) { - return new LibraryVersionProperties(KotlinRuntimeLibraryUtil.getLibraryVersion(root)); + return root; } } return null; } + + @Nullable + public static VirtualFile getRuntimeJar(@NotNull Library library) { + return getRuntimeJar(Arrays.asList(library.getFiles(OrderRootType.CLASSES))); + } } diff --git a/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java b/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java index c977c8da75e..720187e9a84 100644 --- a/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java +++ b/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java @@ -150,10 +150,7 @@ public class KotlinRuntimeLibraryUtil { return null; } - public static void updateRuntime( - @NotNull final Project project, - @NotNull final Runnable jarNotFoundHandler - ) { + public static void updateRuntime(@NotNull final Project project, @NotNull final Runnable jarNotFoundHandler) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { @@ -163,28 +160,14 @@ public class KotlinRuntimeLibraryUtil { return; } - VirtualFile runtimeJar = getLocalKotlinRuntimeJar(project); - assert runtimeJar != null; + VirtualFile localJar = getLocalKotlinRuntimeJar(project); + assert localJar != null; - try { - File libraryJarPath = new File(runtimeJar.getPath()); - assert !FileUtil.filesEqual(runtimePath, libraryJarPath) : "Shouldn't be called for updating same file"; - - FileUtil.copy(runtimePath, libraryJarPath); - } - catch (IOException e) { - throw new AssertionError(e); - } - runtimeJar.refresh(true, true); + replaceFile(runtimePath, localJar); } }); } - @Nullable - public static String getRuntimeVersion(@NotNull Project project) { - return getLibraryVersion(getKotlinRuntimeJar(project)); - } - @Nullable public static String getLibraryVersion(@Nullable VirtualFile kotlinStdJar) { if (kotlinStdJar == null) return null; @@ -207,7 +190,6 @@ public class KotlinRuntimeLibraryUtil { VirtualFile virtualFile = markerClass.getContainingFile().getVirtualFile(); if (virtualFile == null) return null; - ProjectFileIndex projectFileIndex = ProjectFileIndex.SERVICE.getInstance(project); return projectFileIndex.getClassRootForFile(virtualFile); } @@ -223,4 +205,24 @@ public class KotlinRuntimeLibraryUtil { } return kotlinRuntimeJar; } + + static void replaceFile(File updatedFile, VirtualFile replacedFile) { + try { + String localPath = com.intellij.util.PathUtil.getLocalPath(replacedFile); + assert localPath != null; + + File libraryJarPath = new File(localPath); + + if (FileUtil.filesEqual(updatedFile, libraryJarPath)) { + throw new IllegalArgumentException("Shouldn't be called for updating same file: " + updatedFile); + } + + FileUtil.copy(updatedFile, libraryJarPath); + } + catch (IOException e) { + throw new AssertionError(e); + } + + replacedFile.refresh(true, true); + } } diff --git a/idea/src/org/jetbrains/jet/plugin/versions/OutdatedKotlinRuntimeNotification.java b/idea/src/org/jetbrains/jet/plugin/versions/OutdatedKotlinRuntimeNotification.java index 276dc661c74..bbab6a855fe 100644 --- a/idea/src/org/jetbrains/jet/plugin/versions/OutdatedKotlinRuntimeNotification.java +++ b/idea/src/org/jetbrains/jet/plugin/versions/OutdatedKotlinRuntimeNotification.java @@ -16,6 +16,9 @@ package org.jetbrains.jet.plugin.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; @@ -23,14 +26,30 @@ 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.module.Module; +import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.OrderEntry; +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 com.sun.istack.internal.Nullable; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.plugin.JetPluginUtil; +import org.jetbrains.jet.plugin.framework.JavaRuntimePresentationProvider; +import org.jetbrains.jet.utils.PathUtil; import javax.swing.event.HyperlinkEvent; +import java.io.File; +import java.util.Collection; +import java.util.List; public class OutdatedKotlinRuntimeNotification extends AbstractProjectComponent { private static final String SUPPRESSED_PROPERTY_NAME = "oudtdated.runtime.suppressed.plugin.version"; @@ -39,41 +58,82 @@ public class OutdatedKotlinRuntimeNotification extends AbstractProjectComponent super(project); } + private static class VersionedLibrary extends Pair { + 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() { if (ApplicationManager.getApplication().isInternal()) return; + StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() { @Override public void run() { - String runtimeVersion = KotlinRuntimeLibraryUtil.getRuntimeVersion(myProject); final String pluginVersion = JetPluginUtil.getPluginVersion(); - if (runtimeVersion == null) return; // runtime is not present in project 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; - boolean isRuntimeOutdated = "snapshot".equals(runtimeVersion) - || KotlinRuntimeLibraryUtil.UNKNOWN_VERSION.equals(runtimeVersion) - || runtimeVersion.startsWith("internal-") != pluginVersion.startsWith("internal-") - || VersionComparatorUtil.compare(pluginVersion, runtimeVersion) > 0; + Collection versionedOutdatedLibraries = findOutdatedKotlinLibraries(myProject, pluginVersion); + if (versionedOutdatedLibraries.isEmpty()) { + return; + } - if (!isRuntimeOutdated) return; + final Collection outdatedLibraries = extractLibraries(versionedOutdatedLibraries); - String message = String.format("

Your version of Kotlin runtime library is %s, while plugin version is %s." + - " Runtime library should be updated to avoid compatibility problems.

" + - "

Update Runtime Ignore

", - KotlinRuntimeLibraryUtil.UNKNOWN_VERSION.equals(runtimeVersion) - ? "older than 0.1.2296" - : runtimeVersion, pluginVersion); - Notifications.Bus.notify(new Notification("Outdated Kotlin Runtime", "Outdated Kotlin Runtime", - message, + 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( + "

Your version of Kotlin runtime in '%s' library is %s, while plugin version is %s.

" + + "

Runtime library should be updated to avoid compatibility problems.

" + + "

Update Runtime Ignore

", + libraryName, + readableVersion, + pluginVersion); + } + else { + String libraryNames = StringUtil.join(outdatedLibraries, new Function() { + @Override + public String fun(Library library) { + return library.getName(); + } + }, ", "); + + message = String.format( + "

Version of Kotlin runtime is outdated in several libraries (%s). Plugin version is %s.

" + + "

Runtime libraries should be updated to avoid compatibility problems.

" + + "

Update All Ignore

", + libraryNames, + pluginVersion); + } + + + Notifications.Bus.notify(new Notification("Outdated Kotlin Runtime", "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())) { - KotlinRuntimeLibraryUtil.updateRuntime(myProject, showRuntimeJarNotFoundDialog(myProject)); + updateKotlinLibraries(outdatedLibraries, showRuntimeJarNotFoundDialog(myProject)); } else if ("ignore".equals(event.getDescription())) { PropertiesComponent.getInstance(myProject).setValue(SUPPRESSED_PROPERTY_NAME, pluginVersion); @@ -89,6 +149,78 @@ public class OutdatedKotlinRuntimeNotification extends AbstractProjectComponent }); } + private static Collection extractLibraries(Collection libraries) { + return Collections2.transform(libraries, new com.google.common.base.Function() { + @Override + public Library apply(@Nullable VersionedLibrary versionedLibrary) { + assert versionedLibrary != null; + return versionedLibrary.getLibrary(); + } + }); + } + + private static void updateKotlinLibraries(Collection libraries, @NotNull Runnable jarNotFoundHandler) { + File runtimePath = PathUtil.getKotlinPathsForIdeaPlugin().getRuntimePath(); + if (!runtimePath.exists()) { + jarNotFoundHandler.run(); + return; + } + + for (Library library : libraries) { + VirtualFile libraryJar = JavaRuntimePresentationProvider.getRuntimeJar(library); + assert JavaRuntimePresentationProvider.getInstance().isDetected(library) && libraryJar != null : "Only java runtime libraries are expected"; + + KotlinRuntimeLibraryUtil.replaceFile(runtimePath, libraryJar); + } + } + + private static Collection findOutdatedKotlinLibraries(Project project, String pluginVersion) { + List outdatedLibraries = Lists.newArrayList(); + + for (VersionedLibrary versionedLibrary : findKotlinLibraries(project)) { + String libraryVersion = versionedLibrary.getVersion(); + + boolean isOutdated = "snapshot".equals(libraryVersion) + || libraryVersion == null + || libraryVersion.startsWith("internal-") != pluginVersion.startsWith("internal-") + || VersionComparatorUtil.compare(pluginVersion, libraryVersion) > 0; + + if (isOutdated) { + outdatedLibraries.add(versionedLibrary); + } + } + + return outdatedLibraries; + } + + private static Collection findKotlinLibraries(Project project) { + List libraries = Lists.newArrayList(); + + for (Module module : ModuleManager.getInstance(project).getModules()) { + ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); + + for (OrderEntry entry : moduleRootManager.getOrderEntries()) { + if (entry instanceof LibraryOrderEntry) { + LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry) entry; + Library library = libraryOrderEntry.getLibrary(); + + if (library == null) { + continue; + } + + LibraryVersionProperties javaRuntimeProperties = JavaRuntimePresentationProvider.getInstance().getLibraryProperties(library); + if (javaRuntimeProperties != null) { + libraries.add(new VersionedLibrary(library, javaRuntimeProperties.getVersionString())); + } + + // TODO: search js libraries as well + } + } + } + + return libraries; + } + @NotNull public static Runnable showRuntimeJarNotFoundDialog(@NotNull final Project project) { return new Runnable() {