Skip to content

Commit

Permalink
Cache is loaded on first instance and on project refresh (#45)
Browse files Browse the repository at this point in the history
Cache is loaded on first instance and on project refresh
  • Loading branch information
FinlayRJW authored Oct 11, 2024
1 parent 4ce7f10 commit 3da694f
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 94 deletions.
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-45.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: fix
fix:
description: Cache is loaded on first instance and on project refresh
links:
- https://github.com/palantir/gradle-consistent-versions-idea-plugin/pull/45
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager;
import com.intellij.openapi.project.Project;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
Expand All @@ -32,14 +31,9 @@

public class FolderCompletionContributor extends CompletionContributor {

private final GradleCacheExplorer gradleCacheExplorer = new GradleCacheExplorer();

private final RepositoryExplorer repositoryExplorer = new RepositoryExplorer();

public FolderCompletionContributor() {
// We add listener at this stage so that we can invalidate the cache when the gradle project refreshed
ExternalSystemProgressNotificationManager.getInstance()
.addNotificationListener(new InvalidateCacheOnGradleProjectRefresh(gradleCacheExplorer));
cacheCompletion(VersionPropsTypes.GROUP_PART);
cacheCompletion(VersionPropsTypes.NAME_KEY);
remoteCompletion(VersionPropsTypes.GROUP_PART);
Expand Down Expand Up @@ -74,7 +68,9 @@ protected void addCompletions(

Project project = parameters.getOriginalFile().getProject();

gradleCacheExplorer.getCompletions(RepositoryLoader.loadRepositories(project), group).stream()
GradleCacheExplorer.getInstance()
.getCompletions(RepositoryLoader.loadRepositories(project), group)
.stream()
.map(suggestion -> LookupElementBuilder.create(GroupPartOrPackageName.of(suggestion)))
.forEach(resultSet::addElement);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@

package com.palantir.gradle.versions.intellij;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Stopwatch;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -31,9 +27,7 @@
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
Expand All @@ -43,78 +37,59 @@ public class GradleCacheExplorer {

private static final Logger log = LoggerFactory.getLogger(GradleCacheExplorer.class);
private static final String GRADLE_CACHE_PATH = System.getProperty("user.home") + "/.gradle/caches/modules-2/";
private final Cache<String, Set<String>> cache =
Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build();
private final AtomicReference<Set<String>> cache = new AtomicReference<>(Collections.emptySet());

public final void invalidateCache() {
cache.invalidateAll();
GradleCacheExplorer() {
loadCache();
}

public final void loadCache() {
cache.set(extractStrings());
}

public final Set<String> getCompletions(Set<String> repoUrls, DependencyGroup input) {
Stopwatch stopWatch = Stopwatch.createStarted();

String parsedInput = String.join(".", input.parts());
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator == null) {
return Collections.emptySet();

Set<String> results = cache.get().stream()
.map(url -> extractGroupAndArtifactFromUrl(repoUrls, url))
.flatMap(Optional::stream)
.collect(Collectors.toSet());

if (parsedInput.isEmpty()) {
return results;
}

try {
Callable<Set<String>> task = () -> extractStrings(repoUrls, indicator);
Future<Set<String>> future = ApplicationManager.getApplication().executeOnPooledThread(task);
Set<String> results =
com.intellij.openapi.application.ex.ApplicationUtil.runWithCheckCanceled(future::get, indicator);
stopWatch.stop();
log.debug("Completion matching time: {} ms", stopWatch.elapsed().toMillis());

if (parsedInput.isEmpty()) {
return results;
}
return results.stream()
.filter(result -> result.startsWith(parsedInput))
.map(result -> result.substring(parsedInput.length() + 1))
.collect(Collectors.toSet());
}

private Set<String> extractStrings() {
try (Stream<Path> allFolders = Files.list(Paths.get(GRADLE_CACHE_PATH))) {

Stream<Path> metadataFolders =
allFolders.filter(path -> path.getFileName().toString().startsWith("metadata-"));

return results.stream()
.filter(result -> result.startsWith(parsedInput))
.map(result -> result.substring(parsedInput.length() + 1))
return metadataFolders
.map(metadataFolder -> metadataFolder.resolve("resource-at-url.bin"))
.filter(Files::exists)
.flatMap(this::extractStringsFromBinFile)
.filter(this::isValidResourceUrl)
.collect(Collectors.toSet());
} catch (ProcessCanceledException e) {
log.debug("Operation was cancelled", e);
} catch (Exception e) {
log.warn("Failed to get completions", e);
} catch (IOException e) {
log.error("Failed to list metadata folders", e);
return Collections.emptySet();
}
return Collections.emptySet();
}

private Set<String> extractStrings(Set<String> repoUrls, ProgressIndicator indicator) {
return cache.get("metadata", key -> {
try (Stream<Path> allFolders = Files.list(Paths.get(GRADLE_CACHE_PATH))) {

Stream<Path> metadataFolders =
allFolders.filter(path -> path.getFileName().toString().startsWith("metadata-"));

return metadataFolders
.peek(metadataFolder -> {
if (indicator.isCanceled()) {
throw new RuntimeException(
new InterruptedException("Operation was canceled by the user."));
}
})
.map(metadataFolder -> metadataFolder.resolve("resource-at-url.bin"))
.filter(Files::exists)
.flatMap(this::extractStringsFromBinFile)
.filter(url -> isValidResourceUrl(repoUrls, url))
.map(url -> extractGroupAndArtifactFromUrl(repoUrls, url))
.flatMap(Optional::stream)
.collect(Collectors.toSet());
} catch (IOException e) {
log.error("Failed to list metadata folders", e);
return Collections.emptySet();
} catch (RuntimeException e) {
if (e.getCause() instanceof InterruptedException) {
log.debug("Operation was cancelled", e);
return Collections.emptySet();
}
throw e;
}
});
}

final boolean isValidResourceUrl(Set<String> repoUrls, String url) {
return repoUrls.stream().anyMatch(url::startsWith) && (url.endsWith(".pom") || url.endsWith(".jar"));
final boolean isValidResourceUrl(String url) {
return (url.startsWith("https://")) && (url.endsWith(".pom") || url.endsWith(".jar"));
}

final Stream<String> extractStringsFromBinFile(Path binFile) {
Expand Down Expand Up @@ -182,4 +157,8 @@ Optional<String> extractGroupAndArtifactFromUrl(Set<String> repoUrls, String url
return Optional.of(String.format("%s:%s", group, artifact));
});
}

static GradleCacheExplorer getInstance() {
return ApplicationManager.getApplication().getService(GradleCacheExplorer.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class InvalidateCacheOnGradleProjectRefresh implements ExternalSystemTaskNotificationListener {
private static final Logger log = LoggerFactory.getLogger(InvalidateCacheOnGradleProjectRefresh.class);

private final GradleCacheExplorer gradleCacheExplorer;

public InvalidateCacheOnGradleProjectRefresh(GradleCacheExplorer gradleCacheExplorer) {
this.gradleCacheExplorer = gradleCacheExplorer;
}
public class LoadCacheOnGradleProjectRefresh implements ExternalSystemTaskNotificationListener {
private static final Logger log = LoggerFactory.getLogger(LoadCacheOnGradleProjectRefresh.class);

@Override
public final void onSuccess(ExternalSystemTaskId id) {
if (GradleConstants.SYSTEM_ID.equals(id.getProjectSystemId())
&& id.getType() == ExternalSystemTaskType.RESOLVE_PROJECT) {
log.info("Gradle project refresh finished");
gradleCacheExplorer.invalidateCache();
log.debug("Gradle project refresh finished");
GradleCacheExplorer.getInstance().loadCache();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,7 @@
<lang.commenter language="VersionProps" implementationClass="com.palantir.gradle.versions.intellij.VersionPropsCommenter"/>
<vfs.asyncListener implementation="com.palantir.gradle.versions.intellij.VersionPropsFileListener"/>
<annotator language="VersionProps" implementationClass="com.palantir.gradle.versions.intellij.CommentAnnotator" />
<externalSystemTaskNotificationListener implementation="com.palantir.gradle.versions.intellij.LoadCacheOnGradleProjectRefresh" />
<applicationService serviceImplementation="com.palantir.gradle.versions.intellij.GradleCacheExplorer" />
</extensions>
</idea-plugin>
</idea-plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,21 @@ void beforeEach() {

@Test
void test_gets_valid_urls_only() {
Set<String> projectUrls = Set.of("https://repo.maven.apache.org/maven2/", "https://jcenter.bintray.com/");

assertThat(explorer.isValidResourceUrl(
projectUrls, "https://repo.maven.apache.org/maven2/com/example/artifact/1.0/artifact-1.0.pom"))
"https://repo.maven.apache.org/maven2/com/example/artifact/1.0/artifact-1.0.pom"))
.as("because the URL is from a known valid repository and ends with .pom")
.isTrue();

assertThat(explorer.isValidResourceUrl(
projectUrls, "https://jcenter.bintray.com/com/example/artifact/1.0/artifact-1.0.jar"))
assertThat(explorer.isValidResourceUrl("https://jcenter.bintray.com/com/example/artifact/1.0/artifact-1.0.jar"))
.as("because the URL is from a known valid repository and ends with .jar")
.isTrue();

assertThat(explorer.isValidResourceUrl(
projectUrls, "https://example.com/com/example/artifact/1.0/artifact-1.0.pom"))
.as("because the URL is not from a known valid repository")
assertThat(explorer.isValidResourceUrl("example.com/com/example/artifact/1.0/artifact-1.0.pom"))
.as("because the URL is not a valid URL")
.isFalse();

assertThat(explorer.isValidResourceUrl(
projectUrls, "https://repo.maven.apache.org/maven2/com/example/artifact/1.0/artifact-1.0.txt"))
"https://repo.maven.apache.org/maven2/com/example/artifact/1.0/artifact-1.0.txt"))
.as("because the URL ends with an invalid extension")
.isFalse();
}
Expand Down

0 comments on commit 3da694f

Please sign in to comment.