diff --git a/python/src/com/jetbrains/python/packaging/CondaPackageCache.java b/python/src/com/jetbrains/python/packaging/CondaPackageCache.java deleted file mode 100644 index 6691c7ca850a..000000000000 --- a/python/src/com/jetbrains/python/packaging/CondaPackageCache.java +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.jetbrains.python.packaging; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.diagnostic.Logger; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; - -/** - * @author Mikhail Golubev - */ -public class CondaPackageCache { - private static final Logger LOG = Logger.getInstance(PyPIPackageCache.class); - private static final Gson ourGson = new GsonBuilder().create(); - private static CondaPackageCache ourInstance = null; - - @NotNull - public static synchronized CondaPackageCache getInstance() { - if (ourInstance == null) { - ourInstance = new CondaPackageCache(); - try (Reader reader = Files.newBufferedReader(getCachePath(), StandardCharsets.UTF_8)) { - ourInstance = ourGson.fromJson(reader, CondaPackageCache.class); - LOG.info("Loaded " + ourInstance.myPackages.size() + " packages from " + getCachePath()); - } - catch (IOException exception) { - LOG.warn("Cannot load Conda package cache from the filesystem", exception); - } - } - return ourInstance; - } - - @NotNull - public static synchronized CondaPackageCache reload(@NotNull Map> packageNames) { - ourInstance = new CondaPackageCache(packageNames); - try { - Files.createDirectories(getCachePath().getParent()); - try (Writer writer = Files.newBufferedWriter(getCachePath(), StandardCharsets.UTF_8)) { - ourGson.toJson(ourInstance, writer); - } - } - catch (IOException exception) { - LOG.warn("Cannot save Conda package cache to the filesystem", exception); - } - return ourInstance; - } - - @NotNull - private static Path getCachePath() { - return Paths.get(PathManager.getSystemPath(), "python_packages", "conda-cache.json"); - } - - public CondaPackageCache() { - - } - - private CondaPackageCache(@NotNull Map> nameToVersion) { - for (Map.Entry> entry : nameToVersion.entrySet()) { - myPackages.put(entry.getKey(), new PackageInfo(entry.getValue())); - } - } - - public boolean containsPackage(@NotNull String packageName) { - return myPackages.containsKey(packageName); - } - - @NotNull - public Set getPackageNames() { - return Collections.unmodifiableSet(myPackages.keySet()); - } - - /** - * Returns available package versions sorted in the reversed order using {@link com.intellij.webcore.packaging.PackageVersionComparator}. - * @param packageName case-insensitive name of a package - */ - @NotNull - public List getVersions(@NotNull String packageName) { - return Collections.unmodifiableList(myPackages.get(packageName).getVersions()); - } - - @SerializedName("packages") - private final TreeMap myPackages = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - - private static class PackageInfo { - - @SerializedName("v") - private List myVersions; - - public PackageInfo(@NotNull List versions) { - myVersions = versions; - } - - @SuppressWarnings("unused") - public PackageInfo() { - } - - @NotNull - public List getVersions() { - return Collections.unmodifiableList(myVersions); - } - } -} diff --git a/python/src/com/jetbrains/python/packaging/PyAbstractPackageCache.java b/python/src/com/jetbrains/python/packaging/PyAbstractPackageCache.java new file mode 100644 index 000000000000..09a52d0ddbf5 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/PyAbstractPackageCache.java @@ -0,0 +1,125 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.packaging; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeMap; + +/** + * @author Mikhail Golubev + */ +public abstract class PyAbstractPackageCache { + private static final Logger LOG = Logger.getInstance(PyPIPackageCache.class); + private static final Gson ourGson = new GsonBuilder().create(); + + @SerializedName("packages") + protected TreeMap myPackages = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + protected PyAbstractPackageCache() { + } + + + @NotNull + protected static T load(@NotNull Class classToken, + @NotNull T fallbackValue, + @NotNull String cacheFileName) { + T cache = fallbackValue; + try (Reader reader = Files.newBufferedReader(getCachePath(cacheFileName), StandardCharsets.UTF_8)) { + cache = ourGson.fromJson(reader, classToken); + LOG.info("Loaded " + cache.getPackageNames().size() + " packages from " + getCachePath(cacheFileName)); + } + catch (IOException exception) { + LOG.warn("Cannot load " + cacheFileName + " package cache from the filesystem", exception); + } + return cache; + } + + protected static void store(@NotNull PyAbstractPackageCache newValue, @NotNull String cacheFileName) { + try { + final Path cacheFilePath = getCachePath(cacheFileName); + Files.createDirectories(cacheFilePath.getParent()); + try (Writer writer = Files.newBufferedWriter(cacheFilePath, StandardCharsets.UTF_8)) { + ourGson.toJson(newValue, writer); + } + } + catch (IOException exception) { + LOG.warn("Cannot save " + cacheFileName + " package cache to the filesystem", exception); + } + } + + @NotNull + private static Path getCachePath(@NotNull String cacheFileName) { + return Paths.get(PathManager.getSystemPath(), "python_packages", cacheFileName); + } + + /** + * Returns a case-insensitive set of packages names available in the cache. + */ + @NotNull + public Set getPackageNames() { + return Collections.unmodifiableSet(myPackages.keySet()); + } + + /** + * Checks that the given name is among those available in the repository case-insensitively. + *

+ * Note that if the cache hasn't been initialized yet or there was an error during its loading, + * {@link #load(Class, PyAbstractPackageCache, String)} returns an empty sentinel value, and, therefore, this method will return {@code false}. + * It's worth writing code analysis so that this value doesn't lead to false positives in the editor + * when the cache is merely not ready. + * + * @param name case-insensitive name of a package + */ + public boolean containsPackage(@NotNull String name) { + return myPackages.containsKey(name); + } + + /** + * Returns available package versions sorted in the reversed order using + * {@link com.intellij.webcore.packaging.PackageVersionComparator} so that the latest version is the first on the list + * or {@code null} if the given package is not contained in the cache or this feature is not available. + * + * @param packageName case-insensitive name of a package + */ + @Nullable + public List getVersions(@NotNull String packageName) { + final PackageInfo packageInfo = myPackages.get(packageName); + return packageInfo != null ? packageInfo.getVersions() : null; + } + + protected static class PackageInfo { + public static final PackageInfo EMPTY = new PackageInfo(); + + @SerializedName("v") + private List myVersions; + + public PackageInfo(@NotNull List versions) { + myVersions = versions; + } + + @SuppressWarnings("unused") + public PackageInfo() { + } + + @Nullable + public List getVersions() { + return myVersions != null ? Collections.unmodifiableList(myVersions) : null; + } + } +} diff --git a/python/src/com/jetbrains/python/packaging/PyCondaPackageCache.java b/python/src/com/jetbrains/python/packaging/PyCondaPackageCache.java new file mode 100644 index 000000000000..25fcfd4b55a9 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/PyCondaPackageCache.java @@ -0,0 +1,40 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.packaging; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +/** + * @author Mikhail Golubev + */ +public class PyCondaPackageCache extends PyAbstractPackageCache { + private static final String CACHE_FILE_NAME = "conda-cache.json"; + + private static PyCondaPackageCache ourInstance; + + @NotNull + public static synchronized PyCondaPackageCache getInstance() { + if (ourInstance == null) { + ourInstance = load(PyCondaPackageCache.class, new PyCondaPackageCache(), CACHE_FILE_NAME); + } + return ourInstance; + } + + @NotNull + public static synchronized PyCondaPackageCache reload(@NotNull Map> packageNames) { + ourInstance = new PyCondaPackageCache(packageNames); + store(ourInstance, CACHE_FILE_NAME); + return ourInstance; + } + + private PyCondaPackageCache() { + } + + private PyCondaPackageCache(@NotNull Map> nameToVersion) { + for (Map.Entry> entry : nameToVersion.entrySet()) { + myPackages.put(entry.getKey(), new PyAbstractPackageCache.PackageInfo(entry.getValue())); + } + } +} diff --git a/python/src/com/jetbrains/python/packaging/PyCondaPackageService.java b/python/src/com/jetbrains/python/packaging/PyCondaPackageService.java index dd7adb4035c3..9a434fdad271 100644 --- a/python/src/com/jetbrains/python/packaging/PyCondaPackageService.java +++ b/python/src/com/jetbrains/python/packaging/PyCondaPackageService.java @@ -58,7 +58,7 @@ public class PyCondaPackageService implements PersistentStateComponent getPackageVersions(@NotNull final String packageName) { - return CondaPackageCache.getInstance().getVersions(packageName); + return ContainerUtil.notNullize(PyCondaPackageCache.getInstance().getVersions(packageName)); } public void updateChannels() { diff --git a/python/src/com/jetbrains/python/packaging/PyPIPackageCache.java b/python/src/com/jetbrains/python/packaging/PyPIPackageCache.java index 4ed1f8bfe007..c12b7f0d1892 100644 --- a/python/src/com/jetbrains/python/packaging/PyPIPackageCache.java +++ b/python/src/com/jetbrains/python/packaging/PyPIPackageCache.java @@ -1,45 +1,22 @@ // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.packaging; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.List; /** * @author Mikhail Golubev */ -public class PyPIPackageCache { - private static final Logger LOG = Logger.getInstance(PyPIPackageCache.class); - private static final Gson ourGson = new GsonBuilder().create(); - private static PyPIPackageCache ourInstance = null; +public class PyPIPackageCache extends PyAbstractPackageCache { + private static final String CACHE_FILE_NAME = "pypi-cache.json"; + + private static PyPIPackageCache ourInstance; @NotNull public static synchronized PyPIPackageCache getInstance() { if (ourInstance == null) { - ourInstance = new PyPIPackageCache(); - try (Reader reader = Files.newBufferedReader(getCachePath(), StandardCharsets.UTF_8)) { - ourInstance = ourGson.fromJson(reader, PyPIPackageCache.class); - LOG.info("Loaded " + ourInstance.getPackageNames().size() + " packages from " + getCachePath()); - } - catch (IOException exception) { - LOG.warn("Cannot load PyPI package cache from the filesystem", exception); - } + ourInstance = PyAbstractPackageCache.load(PyPIPackageCache.class, new PyPIPackageCache(), CACHE_FILE_NAME); } return ourInstance; } @@ -47,67 +24,19 @@ public class PyPIPackageCache { @NotNull public static synchronized PyPIPackageCache reload(@NotNull List packageNames) { ourInstance = new PyPIPackageCache(packageNames); - try { - Files.createDirectories(getCachePath().getParent()); - try (Writer writer = Files.newBufferedWriter(getCachePath(), StandardCharsets.UTF_8)) { - ourGson.toJson(ourInstance, writer); - } - } - catch (IOException exception) { - LOG.warn("Cannot save PyPI package cache to the filesystem", exception); - } + store(ourInstance, CACHE_FILE_NAME); return ourInstance; } - // For GSON private PyPIPackageCache() { } public PyPIPackageCache(@NotNull List packageNames) { - myPackageNames = ContainerUtil.map(packageNames, PackageInfo::new); - } - - @NotNull - private static Path getCachePath() { - return Paths.get(PathManager.getSystemPath(), "python_packages", "pypi-cache.json"); - } - - @SerializedName("packages") - private List myPackageNames = new ArrayList<>(); - - @NotNull - public List getPackageNames() { - return Collections.unmodifiableList(ContainerUtil.map(myPackageNames, PackageInfo::getName)); - } - - /** - * Checks that the given name is among those available at PyPI case-insensitively. - *

- * Note that if the cache hasn't been initialized yet or there was an error during its loading, - * {@link #getInstance()} returns an empty sentinel value, and, therefore, this method will return {@code false}. - * It's worth writing code analysis so that this value doesn't lead to false positives in the editor - * when the cache is merely not ready. - */ - public boolean containsPackage(@NotNull String name) { - final Comparator caseInsensitiveNameComparator = Comparator.comparing(PackageInfo::getName, String.CASE_INSENSITIVE_ORDER); - return Collections.binarySearch(myPackageNames, new PackageInfo(name), caseInsensitiveNameComparator) >= 0; - } - - private static class PackageInfo { - @SerializedName("n") - private String myName; - - @SuppressWarnings("unused") - public PackageInfo() { - } - - public PackageInfo(@NotNull String name) { - myName = name; - } - - @NotNull - public String getName() { - return myName; + for (String name : packageNames) { + // Don't save null as a value, since GSON excludes such entries from serialization, + // unless GsonBuilder#serializeNulls() is used, but then object fields with null values + // are saved as well. + myPackages.put(name, PackageInfo.EMPTY); } } } diff --git a/python/src/com/jetbrains/python/packaging/ui/PyCondaManagementService.java b/python/src/com/jetbrains/python/packaging/ui/PyCondaManagementService.java index 24b8ca2058fe..d31e2053748a 100644 --- a/python/src/com/jetbrains/python/packaging/ui/PyCondaManagementService.java +++ b/python/src/com/jetbrains/python/packaging/ui/PyCondaManagementService.java @@ -27,7 +27,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.CatchingConsumer; import com.intellij.util.containers.ContainerUtil; import com.intellij.webcore.packaging.RepoPackage; -import com.jetbrains.python.packaging.CondaPackageCache; +import com.jetbrains.python.packaging.PyCondaPackageCache; import com.jetbrains.python.packaging.PyCondaPackageManagerImpl; import com.jetbrains.python.packaging.PyCondaPackageService; import com.jetbrains.python.packaging.PyPackageManager; @@ -153,7 +153,7 @@ public class PyCondaManagementService extends PyPackageManagementService { @NotNull private static List getCachedCondaPackages() { - final CondaPackageCache instance = CondaPackageCache.getInstance(); + final PyCondaPackageCache instance = PyCondaPackageCache.getInstance(); return ContainerUtil.map(instance.getPackageNames(), name -> { final String latestVersion = ContainerUtil.getFirstItem(instance.getVersions(name)); return new RepoPackage(name, null, latestVersion);