[maven] [IDEA-340163] maven indices refactored step 1.

Separated classes by responsibility, cleaned lifetime index flow. Lucene is not used in GAV anymore

GitOrigin-RevId: de82eeb4691b70e71c7e2326b25d4e905afef61e
This commit is contained in:
Alexander Bubenchikov
2024-01-11 18:51:15 +00:00
committed by intellij-monorepo-bot
parent 677cecc367
commit 4825ffe8e2
38 changed files with 864 additions and 985 deletions
@@ -19,7 +19,6 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.indices.MavenIndicesManager;
import org.jetbrains.idea.maven.indices.MavenSearchIndex;
import org.jetbrains.idea.maven.model.MavenRemoteRepository;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
@@ -34,7 +33,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Vladislav.Soroka
@@ -104,17 +102,7 @@ final class ImportMavenRepositoriesTask {
// register imported maven repository URLs but do not force to download the index
// the index can be downloaded and/or updated later using Maven Configuration UI (Settings -> Build, Execution, Deployment -> Build tools -> Maven -> Repositories)
MavenRepositoriesHolder.getInstance(myProject).update(mavenRemoteRepositories);
MavenIndicesManager.getInstance(myProject).scheduleUpdateIndicesList(indexes -> {
if (myProject.isDisposed()) return;
List<String> repositoriesWithEmptyIndex = indexes.stream()
.filter(index -> index.getUpdateTimestamp() == -1 &&
index.getFailureMessage() == null &&
MavenRepositoriesHolder.getInstance(myProject).contains(index.getRepositoryPathOrUrl()))
.map(MavenSearchIndex::getRepositoryPathOrUrl)
.collect(Collectors.toList());
MavenRepositoriesHolder.getInstance(myProject).updateNotIndexedUrls(repositoriesWithEmptyIndex);
});
MavenIndicesManager.getInstance(myProject).scheduleUpdateIndicesList();
}
@NotNull
@@ -16,6 +16,7 @@
package org.jetbrains.idea.maven.model;
import java.io.Serializable;
import java.util.Objects;
public class MavenRepositoryInfo implements Serializable {
private final String myId;
@@ -60,4 +61,20 @@ public class MavenRepositoryInfo implements Serializable {
return builder.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MavenRepositoryInfo info = (MavenRepositoryInfo)o;
return Objects.equals(myId, info.myId) &&
Objects.equals(myName, info.myName) &&
Objects.equals(myUrl, info.myUrl) &&
myKind == info.myKind;
}
@Override
public int hashCode() {
return Objects.hash(myId, myName, myUrl, myKind);
}
}
@@ -26,11 +26,12 @@ import org.jetbrains.idea.maven.dom.DependencyConflictId;
import org.jetbrains.idea.maven.dom.MavenDomBundle;
import org.jetbrains.idea.maven.dom.MavenDomProjectProcessorUtils;
import org.jetbrains.idea.maven.dom.model.*;
import org.jetbrains.idea.maven.indices.MavenIndex;
import org.jetbrains.idea.maven.indices.MavenIndexUtils;
import org.jetbrains.idea.maven.indices.MavenIndicesManager;
import org.jetbrains.idea.maven.model.MavenArtifact;
import org.jetbrains.idea.maven.model.MavenId;
import org.jetbrains.idea.maven.model.MavenPlugin;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.utils.MavenArtifactUtil;
@@ -42,6 +43,7 @@ import java.io.File;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public abstract class MavenArtifactCoordinatesConverter extends ResolvingConverter<String> implements MavenDomSoftAwareConverter {
@@ -56,11 +58,11 @@ public abstract class MavenArtifactCoordinatesConverter extends ResolvingConvert
ConverterStrategy strategy = selectStrategy(context);
boolean isValid = strategy.isValid(id, manager, context);
if (!isValid) {
MavenIndex localIndex = manager.getIndex().getLocalIndex();
if (localIndex == null) return null;
Path artifactPath = MavenUtil.getArtifactPath(Path.of(localIndex.getRepositoryPathOrUrl()), id, "pom", null);
MavenRepositoryInfo repository = MavenIndexUtils.getLocalRepository(contextProject);
if (repository == null) return null;
Path artifactPath = MavenUtil.getArtifactPath(Path.of(repository.getUrl()), id, "pom", null);
if (artifactPath != null && artifactPath.toFile().exists()) {
MavenIndicesManager.getInstance(contextProject).scheduleArtifactIndexing(id, artifactPath.toFile());
MavenIndicesManager.getInstance(contextProject).scheduleArtifactIndexing(id, artifactPath.toFile(), repository.getUrl());
return s;
}
return null;
@@ -180,14 +182,13 @@ public abstract class MavenArtifactCoordinatesConverter extends ResolvingConvert
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
MavenIndicesManager.getInstance(project).scheduleUpdateContentAll();
MavenIndicesManager.getInstance(project).scheduleUpdateContentAll(true);
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return MavenUtil.isPomFile(project, file.getVirtualFile())
&& ContainerUtil.exists(MavenIndicesManager.getInstance(project).getIndex()
.getGAVIndices(), i -> i.getRepository() != null && !"central".equals(i.getRepository().getName()));
&& ContainerUtil.exists(MavenIndexUtils.getRemoteRepositoriesNoResolve(project), r -> !Objects.equals(r.getName(), "central"));
}
}
@@ -0,0 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.indices;
public enum IndexUpdatingState {
IDLE, WAITING, UPDATING
}
@@ -11,6 +11,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.indices.archetype.MavenCatalog;
import org.jetbrains.idea.maven.model.MavenArchetype;
import org.jetbrains.idea.maven.model.MavenId;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.project.MavenEmbeddersManager;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
@@ -72,10 +73,6 @@ public class MavenArchetypeManager {
if (!indicesManager.isInit()) {
indicesManager.updateIndicesListSync();
}
MavenIndexHolder indexHolder = indicesManager.getIndex();
for (MavenIndex index : indexHolder.getIndices()) {
result.addAll(index.getArchetypes());
}
for (MavenArchetypesProvider each : MavenArchetypesProvider.EP_NAME.getExtensionList()) {
result.addAll(each.getArchetypes());
@@ -85,12 +82,7 @@ public class MavenArchetypeManager {
public Collection<MavenArchetype> getLocalArchetypes() {
MavenIndicesManager indicesManager = MavenIndicesManager.getInstance(myProject);
if (!indicesManager.isInit()) indicesManager.updateIndicesListSync();
MavenIndex localIndex = indicesManager.getIndex().getLocalIndex();
if (localIndex == null) return Collections.emptySet();
return localIndex.getArchetypes();
return Collections.emptyList();
}
public Collection<MavenArchetype> getInnerArchetypes() {
@@ -178,11 +170,11 @@ public class MavenArchetypeManager {
private void addToLocalIndex(@NotNull String groupId, @NotNull String artifactId, @NotNull String version) {
MavenId mavenId = new MavenId(groupId, artifactId, version);
MavenIndex localIndex = MavenIndicesManager.getInstance(myProject).getIndex().getLocalIndex();
if (localIndex == null) return;
Path artifactPath = MavenUtil.getArtifactPath(Path.of(localIndex.getRepositoryPathOrUrl()), mavenId, "jar", null);
MavenRepositoryInfo localRepo = MavenIndexUtils.getLocalRepository(myProject);
if (localRepo == null) return;
Path artifactPath = MavenUtil.getArtifactPath(Path.of(localRepo.getUrl()), mavenId, "jar", null);
if (artifactPath != null && artifactPath.toFile().exists()) {
MavenIndicesManager.getInstance(myProject).scheduleArtifactIndexing(mavenId, artifactPath.toFile());
MavenIndicesManager.getInstance(myProject).scheduleArtifactIndexing(mavenId, artifactPath.toFile(), localRepo.getUrl());
}
}
@@ -2,27 +2,24 @@
package org.jetbrains.idea.maven.indices;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.model.RepositoryKind;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.model.RepositoryKind;
import java.util.Collection;
import java.util.Set;
public interface MavenGAVIndex {
Collection<String> getGroupIds();
public interface MavenGAVIndex extends MavenRepositoryIndex {
@NotNull Collection<@NotNull String> getGroupIds();
Set<String> getArtifactIds(String groupId);
@NotNull Set<@NotNull String> getArtifactIds(@NotNull String groupId);
Set<String> getVersions(String groupId, String artifactId);
@NotNull Set<@NotNull String> getVersions(@NotNull String groupId, @NotNull String artifactId);
boolean hasGroupId(String groupId);
boolean hasGroupId(@NotNull String groupId);
boolean hasArtifactId(String groupId, String artifactId);
boolean hasArtifactId(@NotNull String groupId, @NotNull String artifactId);
boolean hasVersion(String groupId, String artifactId, String version);
RepositoryKind getKind();
@Nullable MavenRepositoryInfo getRepository();
boolean hasVersion(@NotNull String groupId, @NotNull String artifactId, @NotNull String version);
}
@@ -1,19 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.indices;
import com.intellij.util.messages.Topic;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.server.AddArtifactResponse;
import java.io.File;
import java.util.Collection;
import java.util.List;
public interface MavenIndex extends MavenGAVIndex, MavenArchetypeContainer, MavenUpdatableIndex {
public interface MavenIndex extends MavenSearchIndex, MavenGAVIndex, MavenArchetypeContainer, MavenUpdatableIndex {
@Topic.AppLevel
Topic<IndexListener> INDEX_IS_BROKEN =
new Topic<>("Maven Index Broken Listener", IndexListener.class);
@NotNull List<AddArtifactResponse> tryAddArtifacts(@NotNull Collection<File> artifactFiles);
}
@@ -12,21 +12,21 @@ import java.util.Objects;
import java.util.Set;
public class MavenIndexHolder {
private final @Nullable MavenIndex myLocalIndex;
private final @Nullable MavenIndex myLocalClassIndex;
private final @NotNull List<MavenIndex> myRemoteIndices;
private final @NotNull List<MavenIndex> myIndices;
MavenIndexHolder(@NotNull List<MavenIndex> remoteIndices, @Nullable MavenIndex localIndex) {
myLocalIndex = localIndex;
myLocalClassIndex = localIndex;
myRemoteIndices = List.copyOf(Objects.requireNonNull(remoteIndices));
List<MavenIndex> indices = new ArrayList<>(remoteIndices);
if (myLocalIndex != null) indices.add(myLocalIndex);
if (myLocalClassIndex != null) indices.add(myLocalClassIndex);
myIndices = List.copyOf(indices);
}
public @Nullable MavenIndex getLocalIndex() {
return myLocalIndex;
return myLocalClassIndex;
}
public @NotNull List<MavenIndex> getRemoteIndices() {
@@ -34,12 +34,12 @@ public class MavenIndexHolder {
}
public List<MavenGAVIndex> getGAVIndices() {
if (myLocalIndex == null) {
if (myLocalClassIndex == null) {
return ContainerUtil.filter(myRemoteIndices, idx -> idx != null);
}
else {
ArrayList<MavenGAVIndex> result = new ArrayList<>(getRemoteIndices());
result.add(myLocalIndex);
result.add(myLocalClassIndex);
return result;
}
}
@@ -49,10 +49,10 @@ public class MavenIndexHolder {
}
public boolean isEquals(@NotNull Set<String> remoteUrls, @Nullable String localPath) {
if (!FileUtilRt.pathsEqual(myLocalIndex != null ? myLocalIndex.getRepositoryPathOrUrl() : null, localPath)) return false;
if (!FileUtilRt.pathsEqual(myLocalClassIndex != null ? myLocalClassIndex.getRepository().getUrl() : null, localPath)) return false;
if (remoteUrls.size() != myRemoteIndices.size()) return false;
for (MavenSearchIndex index : myRemoteIndices) {
if (!remoteUrls.contains(index.getRepositoryPathOrUrl())) return false;
for (MavenIndex index : myRemoteIndices) {
if (!remoteUrls.contains(index.getRepository().getUrl())) return false;
}
return true;
}
@@ -34,7 +34,7 @@ import static com.intellij.openapi.util.text.StringUtil.join;
import static com.intellij.openapi.util.text.StringUtil.split;
import static com.intellij.util.containers.ContainerUtil.notNullize;
public final class MavenIndexImpl implements MavenIndex {
public final class MavenIndexImpl implements MavenIndex, MavenSearchIndex {
private static final String DATA_DIR_PREFIX = "data";
@@ -202,7 +202,6 @@ public final class MavenIndexImpl implements MavenIndex {
@Override
public MavenRepositoryInfo getRepository() {
if (myKind == RepositoryKind.ONLINE) return null;
return new MavenRepositoryInfo(getRepositoryId(), getRepositoryId(), myRepositoryPathOrUrl, myKind);
}
@@ -217,7 +216,7 @@ public final class MavenIndexImpl implements MavenIndex {
}
@Override
public void updateOrRepair(boolean fullUpdate, MavenProgressIndicator progress, boolean multithreaded)
public void updateOrRepair(boolean fullUpdate, MavenProgressIndicator progress, boolean explicit)
throws MavenProcessCanceledException {
StructuredIdeActivity activity = MavenIndexUsageCollector.INDEX_UPDATE.started(null);
boolean isSuccess = false;
@@ -247,7 +246,7 @@ public final class MavenIndexImpl implements MavenIndex {
if (fullUpdate) {
MavenIndexId mavenIndexId = getMavenIndexId(newDataContextDir, "update");
try {
updateNexusContext(mavenIndexId, progress, multithreaded);
updateNexusContext(mavenIndexId, progress, explicit);
}
finally {
myNexusIndexer.releaseIndex(mavenIndexId);
@@ -277,7 +276,7 @@ public final class MavenIndexImpl implements MavenIndex {
MavenIndexUsageCollector.IS_CENTRAL.with(myKind == RepositoryKind.REMOTE && isCentral),
MavenIndexUsageCollector.IS_PRIVATE_REMOTE.with(myKind == RepositoryKind.REMOTE && !isCentral),
MavenIndexUsageCollector.IS_SUCCESS.with(finalIsSuccess),
MavenIndexUsageCollector.MANUAL.with(multithreaded)
MavenIndexUsageCollector.MANUAL.with(explicit)
)
);
indexUpdateLock.unlock();
@@ -449,7 +448,7 @@ public final class MavenIndexImpl implements MavenIndex {
*/
@Override
@NotNull
public List<AddArtifactResponse> tryAddArtifacts(@NotNull Collection<File> artifactFiles) {
public List<AddArtifactResponse> tryAddArtifacts(@NotNull Collection<? extends File> artifactFiles) {
var failedResponses = ContainerUtil.map(artifactFiles, file -> new AddArtifactResponse(file, null));
return doIndexAndRecoveryTask(() -> {
boolean locked = indexUpdateLock.tryLock();
@@ -755,7 +754,7 @@ public final class MavenIndexImpl implements MavenIndex {
}
@NotNull
List<AddArtifactResponse> addArtifacts(Collection<File> artifactFiles) {
List<AddArtifactResponse> addArtifacts(Collection<? extends File> artifactFiles) {
return myNexusIndexer.addArtifacts(mavenIndexId, artifactFiles);
}
@@ -1,168 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.indices;
import com.intellij.model.SideEffectGuard;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.progress.BackgroundTaskQueue;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.Consumer;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.project.MavenGeneralSettings;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.utils.MavenProcessCanceledException;
import org.jetbrains.idea.maven.utils.MavenProgressIndicator;
import org.jetbrains.idea.maven.utils.MavenRehighlighter;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Internal api class for update maven indices state.
* <p>
* Contains logic for schedule async tasks for update index list or index.
*/
@ApiStatus.Internal
public final class MavenIndexUpdateManager implements Disposable {
private final Object myUpdatingIndicesLock = new Object();
private final BackgroundTaskQueue myUpdatingQueue = new BackgroundTaskQueue(null, IndicesBundle.message("maven.indices.updating"));
private final MergingUpdateQueue myUpdateQueueList = new MergingUpdateQueue(
getClass().getName(), 1000, true, null, this, null, false
).usePassThroughInUnitTestMode();
private volatile String myCurrentUpdateIndexUrl;
@Override
public void dispose() {
myUpdatingQueue.clear();
}
void scheduleUpdateIndicesList(@NotNull Project project, @Nullable Consumer<? super List<MavenIndex>> consumer) {
myUpdateQueueList.queue(Update.create(this, () -> {
MavenIndicesManager indicesManager = MavenIndicesManager.getInstance(project);
indicesManager.updateIndicesListSync();
if (project.isDisposed()) {
return;
}
MavenIndexHolder indexHolder = indicesManager.getIndex();
MavenIndex localIndex = indexHolder.getLocalIndex();
if (localIndex != null) {
if (localIndex.getUpdateTimestamp() == -1 && Registry.is("maven.auto.update.local.index")) {
scheduleUpdateContent(project, IndicesContentUpdateRequest.background(List.of(localIndex.getRepository())));
}
}
if (consumer != null) {
consumer.consume(indexHolder.getIndices());
}
}));
}
CompletableFuture<?> scheduleUpdateContent(@NotNull Project project, IndicesContentUpdateRequest request) {
SideEffectGuard.checkSideEffectAllowed(SideEffectGuard.EffectType.PROJECT_MODEL);
if (request.getShowProgress()) {
return runIndexUpdateWithProgress(project, request);
}
else {
return runIndexUpdateInBackgroundWithoutProgress(project, request);
}
}
private static CompletableFuture<?> runIndexUpdateInBackgroundWithoutProgress(Project project,
IndicesContentUpdateRequest request) {
MavenProgressIndicator indicator =
new MavenProgressIndicator(null, null);
try {
doUpdateIndicesContent(project, request, indicator);
}
catch (MavenProcessCanceledException e) {
return CompletableFuture.failedFuture(e);
}
return CompletableFuture.completedFuture(null);
}
private CompletableFuture<?> runIndexUpdateWithProgress(@NotNull Project project,
IndicesContentUpdateRequest request) {
final CompletableFuture<?> promise = new CompletableFuture<>();
myUpdatingQueue.run(new Task.Backgroundable(project, IndicesBundle.message("maven.indices.updating"), true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
try {
indicator.setIndeterminate(false);
doUpdateIndicesContent(project, request, new MavenProgressIndicator(null, indicator, null));
}
catch (MavenProcessCanceledException ignore) {
}
finally {
promise.complete(null);
}
}
});
return promise;
}
private static void doUpdateIndicesContent(@NotNull Project project,
@NotNull IndicesContentUpdateRequest request,
@NotNull MavenProgressIndicator indicator)
throws MavenProcessCanceledException {
MavenSystemIndicesManager indicesManager = MavenSystemIndicesManager.getInstance();
for (MavenRepositoryInfo repo : request.getIndicesToUpdate()) {
if (indicator.isCanceled()) return;
indicesManager.updateIndexContentSync(repo, request.getFull(), request.getExplicit(), indicator);
indicator.setText(IndicesBundle.message("maven.indices.updating.index",
repo.getId(),
repo.getUrl()));
}
MavenRehighlighter.rehighlight(project);
}
@TestOnly
void waitForBackgroundTasksInTests() {
while (!myUpdatingQueue.isEmpty()) {
UIUtil.dispatchAllInvocationEvents();
}
try {
myUpdateQueueList.waitForAllExecuted(1, TimeUnit.MINUTES);
}
catch (ExecutionException | InterruptedException | TimeoutException e) {
throw new RuntimeException(e);
}
}
private static MavenGeneralSettings getMavenSettings(@NotNull final Project project, @NotNull MavenProgressIndicator indicator)
throws MavenProcessCanceledException {
MavenGeneralSettings settings;
settings = ReadAction
.compute(() -> project.isDisposed() ? null : MavenProjectsManager.getInstance(project).getGeneralSettings().clone());
if (settings == null) {
// project was closed
indicator.cancel();
indicator.checkCanceled();
}
return settings;
}
public enum IndexUpdatingState {
IDLE, WAITING, UPDATING
}
}
@@ -3,12 +3,13 @@ package org.jetbrains.idea.maven.indices;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import org.jetbrains.idea.maven.model.RepositoryKind;
import org.jetbrains.idea.maven.model.MavenRemoteRepository;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.model.RepositoryKind;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.utils.MavenLog;
import org.jetbrains.idea.maven.utils.MavenUtil;
@@ -96,12 +97,6 @@ public final class MavenIndexUtils {
}
}
@NotNull
public static Map<String, Set<String>> getRemoteRepositoryIdsByUrl(Project project) {
if (project.isDisposed()) return Collections.emptyMap();
return getRemoteRepositoriesMap(project);
}
@Nullable
public static MavenRepositoryInfo getLocalRepository(Project project) {
if (project.isDisposed()) return null;
@@ -111,17 +106,21 @@ public final class MavenIndexUtils {
: new MavenRepositoryInfo(LOCAL_REPOSITORY_ID, LOCAL_REPOSITORY_ID, repository.getPath(), RepositoryKind.LOCAL);
}
private static Map<String, Set<String>> getRemoteRepositoriesMap(Project project) {
if (project.isDisposed()) {
return Collections.emptyMap();
}
@NotNull
public static List<MavenRemoteRepository> getRemoteRepositoriesNoResolve(Project project) {
Set<MavenRemoteRepository> remoteRepositories = new HashSet<>(MavenUtil.getRemoteResolvedRepositories(project));
if (project.isDisposed()) {
return Collections.emptyList();
}
MavenProjectsManager projectsManager = MavenProjectsManager.getInstance(project);
Set<MavenRemoteRepository> repositories = projectsManager.getRemoteRepositories();
Set<MavenRemoteRepository> remoteRepositories = new HashSet<>(repositories);
for (MavenRepositoryProvider repositoryProvider : MavenRepositoryProvider.EP_NAME.getExtensionList()) {
remoteRepositories.addAll(repositoryProvider.getRemoteRepositories(project));
}
return groupRemoteRepositoriesByUrl(remoteRepositories);
return remoteRepositories.stream().toList();
}
@@ -142,6 +141,16 @@ public final class MavenIndexUtils {
return pathOrUrl;
}
static List<MavenRepositoryInfo> getAllRepositories(Project project) {
List<MavenRepositoryInfo> all = new ArrayList<>();
var local = getLocalRepository(project);
if (local != null) {
all.add(local);
}
all.addAll(ContainerUtil.map(getRemoteRepositoriesNoResolve(project), rr -> new MavenRepositoryInfo(rr.getId(), rr.getName(), rr.getUrl(), RepositoryKind.REMOTE)));
return all;
}
static class IndexPropertyHolder {
final File dir;
final RepositoryKind kind;
@@ -25,8 +25,8 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import org.jetbrains.idea.maven.model.RepositoryKind;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.model.RepositoryKind;
import org.jetbrains.idea.maven.server.MavenIndexerWrapper;
import org.jetbrains.idea.maven.utils.MavenLog;
import org.jetbrains.idea.reposearch.DependencySearchService;
@@ -62,46 +62,6 @@ public class MavenIndices implements Disposable {
myProject = project;
}
void updateRepositoriesList() {
if (isDisposed) return;
updateIndicesLock.lock();
try {
Map<String, Set<String>> remoteRepositoryIdsByUrl = MavenIndexUtils.getRemoteRepositoryIdsByUrl(myProject);
MavenRepositoryInfo localRepository = MavenIndexUtils.getLocalRepository(myProject);
if (localRepository == null || myProject.isDisposed()) {
return;
}
if (myIndexHolder.isEquals(remoteRepositoryIdsByUrl.keySet(), localRepository.getUrl())) return;
MavenLog.LOG.debug("start update indices " + myIndexHolder);
MavenIndex localIndex = myIndexHolder.getLocalIndex();
List<MavenIndex> remoteIndices = myIndexHolder.getRemoteIndices();
if (isDisposed) return;
RepositoryDiffContext context = new RepositoryDiffContext(myIndexer, myIndicesDir);
RepositoryDiff<MavenIndex> localDiff = getLocalDiff(localRepository, context, localIndex);
RepositoryDiff<List<MavenIndex>> remoteDiff = getRemoteDiff(remoteRepositoryIdsByUrl, remoteIndices, context);
myIndexHolder = new MavenIndexHolder(remoteDiff.newIndices, localDiff.newIndices);
MavenLog.LOG.debug("new indices " + myIndexHolder);
if (isDisposed) closeIndices(myIndexHolder.getIndices());
indicesInit = true;
closeIndices(getOldIndices(localDiff, remoteDiff));
clearDependencySearchCache(myProject);
}
catch (AlreadyDisposedException | IncorrectOperationException e) {
myIndexHolder = new MavenIndexHolder(Collections.emptyList(), null);
}
finally {
updateIndicesLock.unlock();
}
}
public boolean isIndicesInit() {
return indicesInit;
@@ -202,7 +162,7 @@ public class MavenIndices implements Disposable {
static RepositoryDiff<MavenIndex> getLocalDiff(@NotNull MavenRepositoryInfo localRepo,
@NotNull RepositoryDiffContext context,
@Nullable MavenIndex currentLocalIndex) {
if (currentLocalIndex != null && FileUtil.pathsEqual(localRepo.getUrl(), currentLocalIndex.getRepositoryPathOrUrl())) {
if (currentLocalIndex != null && FileUtil.pathsEqual(localRepo.getUrl(), currentLocalIndex.getRepository().getUrl())) {
return new RepositoryDiff<>(currentLocalIndex, null);
}
@@ -217,13 +177,13 @@ public class MavenIndices implements Disposable {
@NotNull List<MavenIndex> currentRemoteIndex,
@NotNull RepositoryDiffContext context) {
Map<String, MavenIndex> currentRemoteIndicesByUrls = currentRemoteIndex.stream()
.collect(Collectors.toMap(i -> i.getRepositoryPathOrUrl(), Function.identity()));
.collect(Collectors.toMap(i -> i.getRepository().getUrl(), Function.identity()));
if (currentRemoteIndicesByUrls.keySet().equals(remoteRepositoryIdsByUrl.keySet())) {
return new RepositoryDiff<>(currentRemoteIndex, Collections.emptyList());
}
List<MavenIndex> oldIndices = ContainerUtil
.filter(currentRemoteIndex, i -> !remoteRepositoryIdsByUrl.containsKey(i.getRepositoryPathOrUrl()));
.filter(currentRemoteIndex, i -> !remoteRepositoryIdsByUrl.containsKey(i.getRepository().getUrl()));
List<MavenIndex> newMavenIndices = ContainerUtil.map(remoteRepositoryIdsByUrl
.entrySet(), e -> {
@@ -263,6 +223,13 @@ public class MavenIndices implements Disposable {
}
}
//todo: move this logic and this file into MavenIndicesManager
static class UpdateRepositoryResult {
List<MavenIndex> mavenIndexreated;
}
static class RepositoryDiffContext {
final @NotNull MavenIndexerWrapper indexer;
final @NotNull File indicesDir;
@@ -1,390 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.indices;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.serviceContainer.AlreadyDisposedException;
import com.intellij.util.Consumer;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.PathKt;
import com.intellij.util.messages.Topic;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.idea.maven.model.MavenArchetype;
import org.jetbrains.idea.maven.model.MavenArtifactInfo;
import org.jetbrains.idea.maven.model.MavenId;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectChanges;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.project.MavenProjectsTree;
import org.jetbrains.idea.maven.server.MavenServerConnector;
import org.jetbrains.idea.maven.server.MavenServerDownloadListener;
import org.jetbrains.idea.maven.server.NativeMavenProjectHolder;
import org.jetbrains.idea.maven.statistics.MavenIndexUsageCollector;
import org.jetbrains.idea.maven.utils.MavenLog;
import org.jetbrains.idea.maven.utils.MavenUtil;
import org.jetbrains.idea.reposearch.DependencySearchService;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Main api class for work with maven indices.
* <p>
* Get current index state, schedule update index list, check MavenId in index, add data to index.
*/
public final class MavenIndicesManager implements Disposable {
@Topic.AppLevel
public static final Topic<MavenIndexerListener> INDEXER_TOPIC =
new Topic<>(MavenIndexerListener.class.getSimpleName(), MavenIndexerListener.class);
public interface MavenIndexerListener {
void indexUpdated(Set<File> added, Set<File> failedToAdd);
}
private final @NotNull Project myProject;
private final @NotNull MavenIndices myMavenIndices;
private final MavenIndexServerDownloadListener myDownloadListener = new MavenIndexServerDownloadListener(this);
private final IndexFixer myIndexFixer = new IndexFixer();
private final MavenIndexUpdateManager myIndexUpdateManager;
public static MavenIndicesManager getInstance(@NotNull Project project) {
return project.getService(MavenIndicesManager.class);
}
public MavenIndicesManager(@NotNull Project project) {
myProject = project;
myIndexUpdateManager = new MavenIndexUpdateManager();
myMavenIndices = MavenSystemIndicesManager.getInstance().getOrCreateIndices(project);
initListeners();
Disposer.register(this, myIndexUpdateManager);
}
@Override
public void dispose() {
myIndexFixer.stop();
deleteIndicesDirInUnitTests();
}
private void deleteIndicesDirInUnitTests() {
if (MavenUtil.isMavenUnitTestModeEnabled()) {
if (!myMavenIndices.isDisposed()) {
var localIndex = myMavenIndices.getIndexHolder().getLocalIndex();
if (localIndex instanceof MavenIndexImpl impl) {
impl.closeAndClean();
}
}
Path dir = MavenSystemIndicesManager.getInstance().getIndicesDir();
try {
PathKt.delete(dir);
}
catch (Exception e) {
// if some files haven't been deleted in the index directory, report them
try (Stream<Path> stream = Files.walk(dir)) {
var files = stream.map(Path::toString).toList();
var message = files.isEmpty()
? "Failed to delete the index directory"
: "Failed to delete files in the index directory: " + String.join(", ", files);
throw new RuntimeException(message, e);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}
/**
* Get current maven index state.
* If index do not initialized yet, then run init async and return empty index state.
*
* @return MavenIndexHolder
*/
@NotNull
public MavenIndexHolder getIndex() {
if (myMavenIndices.isNotInit() && !ApplicationManager.getApplication().isUnitTestMode()) {
myIndexUpdateManager.scheduleUpdateIndicesList(myProject, null);
}
return myMavenIndices.getIndexHolder();
}
void updateIndicesListSync() {
myMavenIndices.updateRepositoriesList();
}
public boolean isInit() {
return myMavenIndices.isIndicesInit();
}
private void initListeners() {
ApplicationManager.getApplication().getMessageBus().connect(this)
.subscribe(MavenServerConnector.DOWNLOAD_LISTENER_TOPIC, myDownloadListener);
ApplicationManager.getApplication().getMessageBus().connect(this)
.subscribe(MavenIndex.INDEX_IS_BROKEN, new MavenSearchIndexListener(this));
if (ApplicationManager.getApplication().isUnitTestMode()) {
MavenProjectsManager.getInstance(myProject).addProjectsTreeListener(new MavenProjectsTree.Listener() {
@Override
public void projectsUpdated(List<? extends Pair<MavenProject, MavenProjectChanges>> updated, List<? extends MavenProject> deleted) {
DependencySearchService.getInstance(myProject).clearCache();
}
@Override
public void projectResolved(@NotNull Pair<MavenProject, MavenProjectChanges> projectWithChanges,
@Nullable NativeMavenProjectHolder nativeMavenProject) {
DependencySearchService.getInstance(myProject).clearCache();
}
}, this);
return;
}
MavenRepositoryProvider.EP_NAME.addChangeListener(() -> scheduleUpdateIndicesList(null), this);
MavenProjectsManager projectsManager = MavenProjectsManager.getInstance(myProject);
projectsManager.addProjectsTreeListener(new MavenProjectsTree.Listener() {
@Override
public void projectResolved(@NotNull Pair<MavenProject, MavenProjectChanges> projectWithChanges,
@Nullable NativeMavenProjectHolder nativeMavenProject) {
scheduleUpdateIndicesList(null);
}
}, this);
}
public static void addArchetype(@NotNull MavenArchetype archetype) {
MavenArchetypeManager.addArchetype(archetype, getUserArchetypesFile());
}
public boolean hasLocalGroupId(@NotNull String groupId) {
MavenIndex localIndex = getIndex().getLocalIndex();
return localIndex != null && localIndex.hasGroupId(groupId);
}
public boolean hasLocalArtifactId(@Nullable String groupId, @Nullable String artifactId) {
MavenIndex localIndex = getIndex().getLocalIndex();
return localIndex != null && localIndex.hasArtifactId(groupId, artifactId);
}
public boolean hasLocalVersion(@Nullable String groupId, @Nullable String artifactId, @Nullable String version) {
MavenIndex localIndex = getIndex().getLocalIndex();
return localIndex != null && localIndex.hasVersion(groupId, artifactId, version);
}
/**
* Add artifact info to index async.
*/
public boolean scheduleArtifactIndexing(@Nullable MavenId mavenId, @NotNull File artifactFile) {
if (myMavenIndices.isNotInit()) return false;
try {
MavenIndex localIndex = myMavenIndices.getIndexHolder().getLocalIndex();
if (localIndex == null) {
return false;
}
if (mavenId != null) {
if (mavenId.getGroupId() == null || mavenId.getArtifactId() == null || mavenId.getVersion() == null) {
return false;
}
if (localIndex.hasVersion(mavenId.getGroupId(), mavenId.getArtifactId(), mavenId.getVersion())) return false;
}
AppExecutorUtil.getAppExecutorService().execute(() -> {
myIndexFixer.fixIndex(artifactFile);
});
}
catch (AlreadyDisposedException ignore) {
return false;
}
return true;
}
/**
* Schedule update all indices content async.
*/
public void scheduleUpdateContentAll() {
myIndexUpdateManager.scheduleUpdateContent(myProject,
IndicesContentUpdateRequest.explicit(
ContainerUtil.map(myMavenIndices.getIndices(), MavenIndex::getRepository)));
}
/**
* Schedule update indices content async.
*/
public CompletableFuture<?> scheduleUpdateContent(@NotNull List<MavenIndex> indices, boolean explicit) {
IndicesContentUpdateRequest request =
new IndicesContentUpdateRequest(ContainerUtil.map(indices, MavenIndex::getRepository), explicit, true, explicit);
return myIndexUpdateManager.scheduleUpdateContent(myProject, request);
}
/**
* Schedule update indices list {@link MavenIndices} async.
*
* @param consumer - consumer for new indices.
*/
public void scheduleUpdateIndicesList(@Nullable Consumer<? super List<MavenIndex>> consumer) {
myIndexUpdateManager.scheduleUpdateIndicesList(myProject, consumer);
}
@NotNull
public Set<MavenArtifactInfo> searchForClass(String patternForQuery) {
return this.getIndex()
.getIndices().stream()
.flatMap(i -> i.search(patternForQuery, 50).stream())
.collect(Collectors.toSet());
}
@NotNull
private static Path getUserArchetypesFile() {
return MavenSystemIndicesManager.getInstance().getIndicesDir().resolve("UserArchetypes.xml");
}
private final class IndexFixer {
private final ConcurrentLinkedQueue<File> queueToAdd = new ConcurrentLinkedQueue<>();
private final MergingUpdateQueue myMergingUpdateQueue;
private final AddToIndexRunnable taskConsumer = new AddToIndexRunnable();
private final AtomicBoolean stopped = new AtomicBoolean(false);
private IndexFixer() {
myMergingUpdateQueue = new MergingUpdateQueue(
this.getClass().getName(), 1000, true, MergingUpdateQueue.ANY_COMPONENT, MavenIndicesManager.this, null, false
).usePassThroughInUnitTestMode();
}
public void fixIndex(@NotNull File file) {
MavenIndexUsageCollector.ADD_ARTIFACT_FROM_POM.log(myProject);
if (stopped.get()) return;
queueToAdd.add(file);
myMergingUpdateQueue.queue(new Update(this) {
@Override
public void run() {
taskConsumer.run();
}
@Override
public boolean isDisposed() {
return myMavenIndices.isDisposed();
}
});
}
public void stop() {
stopped.set(true);
}
private class AddToIndexRunnable implements Runnable {
@Override
public void run() {
MavenIndex localIndex = myMavenIndices.getIndexHolder().getLocalIndex();
if (localIndex == null) return;
Set<File> addedFiles = new TreeSet<>();
Set<File> failedToAddFiles = new TreeSet<>();
synchronized (queueToAdd) {
if (stopped.get()) return;
if (queueToAdd.isEmpty()) return;
Set<File> filesToAddNow = new TreeSet<>();
File elementToAdd;
while ((elementToAdd = queueToAdd.poll()) != null) {
filesToAddNow.add(elementToAdd);
}
if (filesToAddNow.isEmpty()) return;
Set<File> retryElements = new TreeSet<>();
var addArtifactResponses = localIndex.tryAddArtifacts(filesToAddNow);
for (var addArtifactResponse : addArtifactResponses) {
var file = addArtifactResponse.artifactFile();
var added = addArtifactResponse.indexedMavenId() != null;
if (added) {
addedFiles.add(file);
}
else {
retryElements.add(file);
}
}
if (!retryElements.isEmpty()) {
if (retryElements.size() < 10_000) {
queueToAdd.addAll(retryElements);
}
else {
MavenLog.LOG.error("Failed to index artifacts: " + retryElements.size());
failedToAddFiles.addAll(retryElements);
}
}
}
fireUpdated(addedFiles, failedToAddFiles);
}
private void fireUpdated(Set<File> added, Set<File> failedToAdd) {
if (stopped.get()) return;
if (!added.isEmpty() || !failedToAdd.isEmpty()) {
ApplicationManager.getApplication().getMessageBus().syncPublisher(INDEXER_TOPIC).indexUpdated(added, failedToAdd);
}
}
}
}
private static class MavenIndexServerDownloadListener implements MavenServerDownloadListener {
private final MavenIndicesManager myManager;
private MavenIndexServerDownloadListener(MavenIndicesManager manager) {
myManager = manager;
}
@Override
public void artifactDownloaded(File file, String relativePath) {
myManager.scheduleArtifactIndexing(null, file);
}
}
private static class MavenSearchIndexListener implements MavenSearchIndex.IndexListener {
private final MavenIndicesManager myManager;
private MavenSearchIndexListener(MavenIndicesManager manager) {
myManager = manager;
}
@Override
public void indexIsBroken(@NotNull MavenSearchIndex index) {
if (index instanceof MavenUpdatableIndex) {
myManager.myIndexUpdateManager.scheduleUpdateContent(myManager.myProject,
IndicesContentUpdateRequest.explicit(List.of(index.getRepository())));
}
}
}
@TestOnly
public void waitForBackgroundTasksInTests() {
myIndexUpdateManager.waitForBackgroundTasksInTests();
}
}
@@ -0,0 +1,356 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.indices
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pair
import com.intellij.openapi.util.io.FileUtil
import com.intellij.serviceContainer.AlreadyDisposedException
import com.intellij.util.messages.Topic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.jetbrains.annotations.TestOnly
import org.jetbrains.idea.maven.indices.MavenSearchIndex.IndexListener
import org.jetbrains.idea.maven.model.*
import org.jetbrains.idea.maven.project.MavenProject
import org.jetbrains.idea.maven.project.MavenProjectChanges
import org.jetbrains.idea.maven.project.MavenProjectsManager
import org.jetbrains.idea.maven.project.MavenProjectsTree
import org.jetbrains.idea.maven.server.MavenServerConnector
import org.jetbrains.idea.maven.server.MavenServerDownloadListener
import org.jetbrains.idea.maven.server.NativeMavenProjectHolder
import org.jetbrains.idea.maven.statistics.MavenIndexUsageCollector
import org.jetbrains.idea.maven.utils.MavenLog
import org.jetbrains.idea.maven.utils.MavenUtil
import java.io.File
import java.nio.file.Path
import java.util.concurrent.CopyOnWriteArrayList
/**
* Main api class for work with maven indices.
*
*
* Get current index state, schedule update index list, check MavenId in index, add data to index.
*/
class MavenIndicesManager(private val myProject: Project, private val cs: CoroutineScope) : Disposable {
interface MavenIndexerListener {
fun gavIndexUpdated(repo: MavenRepositoryInfo, added: Set<File>, failedToAdd: Set<File>) {}
fun classIndexUpdated(repo: MavenRepositoryInfo, added: Set<File>, failedToAdd: Set<File>) {}
}
private val searchIndices = CopyOnWriteArrayList<MavenSearchIndex>();
private val myGavIndices = CopyOnWriteArrayList<MavenGAVIndex>();
private val myDownloadListener = MavenIndexServerDownloadListener(this)
private val localMavenGavIndex: MavenGAVIndex?
get() {
return myGavIndices.firstOrNull { it.repository.kind == RepositoryKind.LOCAL }
}
init {
initListeners()
}
override fun dispose() {
deleteIndicesDirInUnitTests()
}
private fun deleteIndicesDirInUnitTests() {
if (MavenUtil.isMavenUnitTestModeEnabled()) {
FileUtil.deleteRecursively(MavenSystemIndicesManager.getInstance().getIndicesDir())
}
}
fun getCommonGavIndex(): MavenGAVIndex {
return MavenGAVIndexWrapper();
}
fun updateIndicesListSync() {
scheduleUpdateIndicesList()
}
val isInit: Boolean
get() = true
private fun initListeners() {
ApplicationManager.getApplication().messageBus.connect(this)
.subscribe(MavenServerConnector.DOWNLOAD_LISTENER_TOPIC, myDownloadListener)
ApplicationManager.getApplication().messageBus.connect(this)
.subscribe(MavenSearchIndex.INDEX_IS_BROKEN, MavenSearchIndexListener(this))
MavenRepositoryProvider.EP_NAME.addChangeListener({ scheduleUpdateIndicesList() }, this)
val projectsManager = MavenProjectsManager.getInstance(myProject)
projectsManager.addProjectsTreeListener(object : MavenProjectsTree.Listener {
override fun projectResolved(projectWithChanges: Pair<MavenProject, MavenProjectChanges>,
nativeMavenProject: NativeMavenProjectHolder?) {
scheduleUpdateIndicesList();
}
}, this)
}
fun scheduleUpdateIndicesList() {
scheduleUpdateIndicesList() {}
}
fun scheduleUpdateIndicesList(onUpdate: () -> Unit) {
cs.launch(Dispatchers.IO) {
doUpdateIndexList()
onUpdate()
}
}
private suspend fun doUpdateIndexList() {
try {
myGavIndices.clear()
MavenIndexUtils.getLocalRepository(myProject)?.let {
myGavIndices.add(MavenSystemIndicesManager.getInstance().getGAVIndexForRepository(it))
searchIndices.add(MavenSystemIndicesManager.getInstance().getClassIndexForRepository(it))
}
if (MavenProjectsManager.getInstanceIfCreated(myProject)?.isMavenizedProject == true) {
val repositories = MavenIndexUtils.getRemoteRepositoriesNoResolve(myProject);
searchIndices.clear();
searchIndices.addAll(repositories.mapNotNull {
MavenSystemIndicesManager.getInstance().getClassIndexForRepository(
MavenRepositoryInfo(it.id, it.name, it.url, RepositoryKind.REMOTE))
})
}
}
catch (e: Exception) {
MavenLog.LOG.error(e)
}
finally {
MavenSystemIndicesManager.getInstance().gc()
}
}
fun hasLocalGroupId(groupId: String?): Boolean {
val localIndex = localMavenGavIndex
return localIndex != null && groupId != null && localIndex.hasGroupId(groupId)
}
fun hasLocalArtifactId(groupId: String?, artifactId: String?): Boolean {
val localIndex = localMavenGavIndex
return localIndex != null && groupId != null && artifactId != null && localIndex.hasArtifactId(groupId, artifactId)
}
fun hasLocalVersion(groupId: String?, artifactId: String?, version: String?): Boolean {
val localIndex = localMavenGavIndex
return localIndex != null && groupId != null && artifactId != null && version != null && localIndex.hasVersion(groupId, artifactId,
version)
}
/**
* Add artifact info to index async.
*/
fun scheduleArtifactIndexing(mavenId: MavenId?, artifactFile: File, localRepo: String): Boolean {
try {
val localIndex = myGavIndices.firstOrNull {
it.repository.kind == RepositoryKind.LOCAL && it.repository.url == localRepo
}
if (localIndex == null) {
return false
}
if (mavenId != null) {
val groupId = mavenId.groupId
val artifactId = mavenId.artifactId
val version = mavenId.version
if (groupId == null || artifactId == null || version == null) {
return false
}
if (localIndex.hasVersion(groupId, artifactId, version)) return false
}
fixIndex(artifactFile)
}
catch (ignore: AlreadyDisposedException) {
return false
}
return true
}
fun scheduleUpdateLocalGavContent(explicit: Boolean) {
val localIndex = localMavenGavIndex;
if (localIndex is MavenUpdatableIndex) {
MavenSystemIndicesManager.getInstance().scheduleUpdateIndexContent(listOf(localIndex), explicit)
}
}
/**
* Schedule update local gav index content async.
*/
fun scheduleUpdateContentLocalGav(explicit: Boolean) {
val localIndex = localMavenGavIndex;
if (localIndex is MavenUpdatableIndex) {
MavenSystemIndicesManager.getInstance().scheduleUpdateIndexContent(listOf(localIndex), explicit)
}
}
fun scheduleUpdateContentAll(explicit: Boolean) {
val toUpdate = ArrayList<MavenUpdatableIndex>()
val localIndex = localMavenGavIndex;
if (localIndex is MavenUpdatableIndex) {
toUpdate.add(localIndex)
}
toUpdate.addAll(searchIndices.filterIsInstance<MavenUpdatableIndex>())
MavenSystemIndicesManager.getInstance().scheduleUpdateIndexContent(toUpdate, explicit)
}
fun scheduleUpdateContentLocalClassIndex(explicit: Boolean) {
val indexLocal = searchIndices
.filter { it.repository.kind == RepositoryKind.LOCAL }
.filterIsInstance<MavenUpdatableIndex>()
if (indexLocal.isNotEmpty()) {
MavenSystemIndicesManager.getInstance().scheduleUpdateIndexContent(indexLocal, explicit)
}
}
fun searchForClass(patternForQuery: String?): Set<MavenArtifactInfo> {
return searchIndices
.flatMap { it.search(patternForQuery, 50).asSequence() }
.toSet()
}
private fun fixIndex(artifactFile: File) {
MavenIndexUsageCollector.ADD_ARTIFACT_FROM_POM.log(myProject)
val localGavIndex = myGavIndices.filter {
it.repository.kind == RepositoryKind.LOCAL
}.filterIsInstance<MavenUpdatableIndex>()
.firstOrNull()
val localLuceneIndex = searchIndices.filter {
it.repository.kind == RepositoryKind.LOCAL
}.filterIsInstance<MavenUpdatableIndex>()
.firstOrNull()
localGavIndex?.let { addToIndexAndNotify(it, artifactFile) { repo, added, failed -> gavIndexUpdated(repo, added, failed) } }
}
private fun addToIndexAndNotify(index: MavenUpdatableIndex,
artifactFile: File,
action: MavenIndexerListener.(MavenRepositoryInfo, Set<File>, Set<File>) -> Unit) {
cs.launch(Dispatchers.IO) {
val artifactResponses = index.tryAddArtifacts(listOf(artifactFile))
ApplicationManager.getApplication().messageBus.syncPublisher(INDEXER_TOPIC)
.action(index.repository,
artifactResponses.filter { it.indexedMavenId() != null }.map { it.artifactFile() }.toSet(),
artifactResponses.filter { it.indexedMavenId() == null }.map { it.artifactFile() }.toSet()
)
}
}
internal fun getGAVIndices(): List<MavenGAVIndex> {
return ArrayList(myGavIndices)
}
internal fun getSearchIndices(): List<MavenSearchIndex> {
return ArrayList(searchIndices)
}
private class MavenIndexServerDownloadListener(private val myManager: MavenIndicesManager) : MavenServerDownloadListener {
override fun artifactDownloaded(file: File, relativePath: String) {
val localRepository = MavenIndexUtils.getLocalRepository(myManager.myProject)
localRepository?.url?.let { myManager.scheduleArtifactIndexing(null, file, it) }
}
}
private class MavenSearchIndexListener(private val myManager: MavenIndicesManager) : IndexListener {
override fun indexIsBroken(index: MavenSearchIndex) {
if (index is MavenUpdatableIndex) {
MavenSystemIndicesManager.getInstance().scheduleUpdateIndexContent(listOf(index), false)
}
}
}
@TestOnly
fun waitForGavUpdateCompleted() {
runBlocking {
MavenSystemIndicesManager.getInstance().waitAllGavsUpdatesCompleted()
}
}
@TestOnly
fun waitForLuceneUpdateCompleted() {
runBlocking {
MavenSystemIndicesManager.getInstance().waitAllLuceneUpdatesCompleted()
}
}
companion object {
@Topic.AppLevel
@JvmField
val INDEXER_TOPIC: Topic<MavenIndexerListener> = Topic(
MavenIndexerListener::class.java.simpleName, MavenIndexerListener::class.java)
@JvmStatic
fun getInstance(project: Project): MavenIndicesManager {
return project.getService(MavenIndicesManager::class.java)
}
@JvmStatic
fun getInstanceIfCreated(project: Project): MavenIndicesManager? {
return project.getServiceIfCreated(MavenIndicesManager::class.java)
}
@JvmStatic
fun addArchetype(archetype: MavenArchetype) {
MavenArchetypeManager.addArchetype(archetype, userArchetypesFile)
}
private val userArchetypesFile: Path
get() = MavenSystemIndicesManager.getInstance().getIndicesDir().resolve("UserArchetypes.xml")
}
inner class MavenGAVIndexWrapper : MavenGAVIndex {
override fun close(releaseIndexContext: Boolean) {
}
override fun getGroupIds(): Collection<String> {
return myGavIndices.flatMap { it.groupIds }
}
override fun getArtifactIds(groupId: String): Set<String> {
return myGavIndices.flatMap { it.getArtifactIds(groupId) }.toSet()
}
override fun getVersions(groupId: String, artifactId: String): Set<String> {
return myGavIndices.flatMap { it.getVersions(groupId, artifactId) }.toSet()
}
override fun hasGroupId(groupId: String): Boolean {
return myGavIndices.any { it.hasGroupId(groupId) }
}
override fun hasArtifactId(groupId: String, artifactId: String): Boolean {
return myGavIndices.any { it.hasArtifactId(groupId, artifactId) }
}
override fun hasVersion(groupId: String, artifactId: String, version: String): Boolean {
return myGavIndices.any { it.hasVersion(groupId, artifactId, version) }
}
override fun getKind(): RepositoryKind {
throw IllegalStateException("Internal API, do not use it")
}
override fun getRepository(): MavenRepositoryInfo {
throw IllegalStateException("Internal API, do not use it")
}
}
}
@@ -2,11 +2,15 @@
package org.jetbrains.idea.maven.indices
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.util.io.FileUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jetbrains.idea.maven.model.RepositoryKind
import org.jetbrains.idea.maven.model.MavenId
import org.jetbrains.idea.maven.model.MavenRepositoryInfo
import org.jetbrains.idea.maven.model.RepositoryKind
import org.jetbrains.idea.maven.server.AddArtifactResponse
import org.jetbrains.idea.maven.server.IndexedMavenId
import org.jetbrains.idea.maven.statistics.MavenIndexUsageCollector
import org.jetbrains.idea.maven.utils.MavenLog
import org.jetbrains.idea.maven.utils.MavenProgressIndicator
import java.io.File
@@ -16,8 +20,14 @@ import java.util.concurrent.ConcurrentHashMap
class MavenLocalGavIndexImpl(val repo: MavenRepositoryInfo) : MavenGAVIndex, MavenUpdatableIndex {
val group2Artifacts = ConcurrentHashMap<String, MutableSet<String>>()
val mavenIds2Versions = ConcurrentHashMap<String, MutableSet<String>>()
private val group2Artifacts = ConcurrentHashMap<String, MutableSet<String>>()
private val mavenIds2Versions = ConcurrentHashMap<String, MutableSet<String>>()
private val repoFile = Paths.get(repo.url).toFile().canonicalFile
override fun close(releaseIndexContext: Boolean) {
mavenIds2Versions.clear()
group2Artifacts.clear()
}
override fun getGroupIds(): Collection<String> = Collections.unmodifiableSet(group2Artifacts.keys)
@@ -40,29 +50,49 @@ class MavenLocalGavIndexImpl(val repo: MavenRepositoryInfo) : MavenGAVIndex, Mav
override fun getKind() = RepositoryKind.LOCAL
override fun getRepository() = repo
override fun updateOrRepair(fullUpdate: Boolean, progress: MavenProgressIndicator, multithreaded: Boolean) {
if (fullUpdate) {
group2Artifacts.clear()
mavenIds2Versions.clear()
}
val repoFile = Paths.get(repo.url).toFile().canonicalFile
override fun updateOrRepair(fullUpdate: Boolean, progress: MavenProgressIndicator, explicit: Boolean) {
val activity = MavenIndexUsageCollector.GAV_INDEX_UPDATE.started(null);
var success = false;
var filesProcessed = 0
runBlockingMaybeCancellable {
launch(Dispatchers.IO) {
repoFile.walkBottomUp()
.filter { it.name.endsWith(".pom") }
.mapNotNull { extractMavenId(it, repoFile) }
.forEach { id ->
addTo(group2Artifacts, id.groupId!!, id.artifactId!!)
addTo(mavenIds2Versions, id2string(id.groupId!!, id.artifactId!!), id.version!!)
if (filesProcessed % 100 == 0) {
progress.setText(IndicesBundle.message("maven.indices.scanned.artifacts", filesProcessed))
var startTime = System.currentTimeMillis();
try {
if (fullUpdate) {
group2Artifacts.clear()
mavenIds2Versions.clear()
}
runBlockingMaybeCancellable {
launch(Dispatchers.IO) {
repoFile.walkBottomUp()
.filter { it.name.endsWith(".pom") }
.mapNotNull { extractMavenId(it) }
.forEach { id ->
addTo(group2Artifacts, id.groupId!!, id.artifactId!!)
addTo(mavenIds2Versions, id2string(id.groupId!!, id.artifactId!!), id.version!!)
if (filesProcessed % 100 == 0) {
progress.setText(IndicesBundle.message("maven.indices.scanned.artifacts", filesProcessed))
}
filesProcessed++
}
filesProcessed++
}
}.join()
}.join()
}
success = true;
progress.setText(IndicesBundle.message("maven.indices.updated.for.repo", repo.name))
}
progress.setText(IndicesBundle.message("maven.indices.updated.for.repo", repo.name))
finally {
MavenLog.LOG.warn(
"GAV index updated for repo $repoFile, $filesProcessed files processed in ${group2Artifacts.size} groups in ${System.currentTimeMillis() - startTime} millis")
activity.finished {
listOf(
MavenIndexUsageCollector.MANUAL.with(explicit),
MavenIndexUsageCollector.IS_SUCCESS.with(success),
MavenIndexUsageCollector.GROUPS_COUNT.with(group2Artifacts.size),
MavenIndexUsageCollector.ARTIFACTS_COUNT.with(filesProcessed),
)
}
}
}
private fun addTo(map: MutableMap<String, MutableSet<String>>, key: String, value: String) {
@@ -70,7 +100,7 @@ class MavenLocalGavIndexImpl(val repo: MavenRepositoryInfo) : MavenGAVIndex, Mav
set.add(value)
}
private fun extractMavenId(it: File, repoFile: File): MavenId? {
private fun extractMavenId(it: File): MavenId? {
if (MavenLog.LOG.isTraceEnabled) {
MavenLog.LOG.trace("extracting id from file $it")
}
@@ -93,4 +123,27 @@ class MavenLocalGavIndexImpl(val repo: MavenRepositoryInfo) : MavenGAVIndex, Mav
return MavenId(groupId, artifactId, version)
}
override fun tryAddArtifacts(artifactFiles: Collection<File>): List<AddArtifactResponse> {
val result = ArrayList<AddArtifactResponse>()
artifactFiles.forEach {
val file = it.absoluteFile
if (!FileUtil.isAncestor(repoFile, it, true)) {
result.add(AddArtifactResponse(file, null))
}
else {
val id = extractMavenId(file)
if (id == null) {
result.add(AddArtifactResponse(file, null))
}
else {
addTo(group2Artifacts, id!!.groupId!!, id.artifactId!!)
addTo(mavenIds2Versions, id2string(id.groupId!!, id.artifactId!!), id.version!!)
result.add(AddArtifactResponse(file, IndexedMavenId(id.groupId, id.artifactId, id.version, "", "")))
}
}
}
return result;
}
}
@@ -26,7 +26,7 @@
<properties/>
<border type="none"/>
<children>
<component id="d806c" class="com.intellij.ui.table.JBTable" binding="myIndicesTable" default-binding="true">
<component id="d806c" class="com.intellij.ui.table.JBTable" binding="myRepositoriesTable" default-binding="true">
<constraints/>
<properties>
<showHorizontalLines value="false"/>
@@ -9,7 +9,6 @@ import com.intellij.openapi.project.Project;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.JBColor;
import com.intellij.ui.table.JBTable;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.util.ui.AnimatedIcon;
import com.intellij.util.ui.AsyncProcessIcon;
import com.intellij.util.ui.JBUI;
@@ -17,6 +16,7 @@ import com.intellij.util.ui.TimerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.project.MavenConfigurableBundle;
import org.jetbrains.idea.maven.utils.MavenProgressIndicator;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
@@ -26,8 +26,6 @@ import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
@@ -36,7 +34,7 @@ public class MavenRepositoriesConfigurable implements SearchableConfigurable, Co
private final Project myProject;
private JPanel myMainPanel;
private JBTable myIndicesTable;
private JBTable myRepositoriesTable;
private JButton myUpdateButton;
private JPanel myBorderPanel;
@@ -66,64 +64,44 @@ public class MavenRepositoriesConfigurable implements SearchableConfigurable, Co
}
});
myIndicesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
myRepositoriesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
updateButtonsState();
}
});
myIndicesTable.addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseDragged(MouseEvent e) {
}
myRepositoriesTable.setDefaultRenderer(Object.class, new MyCellRenderer());
myRepositoriesTable.setDefaultRenderer(IndexUpdatingState.class, new MyIconCellRenderer());
@Override
public void mouseMoved(MouseEvent e) {
int row = myIndicesTable.rowAtPoint(e.getPoint());
if (row == -1) return;
updateIndexHint(row);
}
});
myIndicesTable.setDefaultRenderer(Object.class, new MyCellRenderer());
myIndicesTable.setDefaultRenderer(MavenIndexUpdateManager.IndexUpdatingState.class, new MyIconCellRenderer());
myIndicesTable.getEmptyText().setText(MavenConfigurableBundle.message("maven.settings.repositories.no"));
myRepositoriesTable.getEmptyText().setText(MavenConfigurableBundle.message("maven.settings.repositories.no"));
updateButtonsState();
}
private void updateButtonsState() {
boolean hasSelection = !myIndicesTable.getSelectionModel().isSelectionEmpty();
boolean hasSelection = !myRepositoriesTable.getSelectionModel().isSelectionEmpty();
myUpdateButton.setEnabled(hasSelection);
}
public void updateIndexHint(int row) {
MavenSearchIndex index = getIndexAt(row);
String message = index.getFailureMessage();
if (message == null) {
myIndicesTable.setToolTipText(null);
}
else {
myIndicesTable.setToolTipText(message);
}
}
private void doUpdateIndex() {
MavenIndicesManager.getInstance(myProject).scheduleUpdateContent(getSelectedIndices(), true);
MavenRepositoryInfo indexToUpdate = getSelectedIndices().stream().findFirst().orElse(null);
MavenSystemIndicesManager.getInstance().updateIndexContentSync(indexToUpdate, true, true, new MavenProgressIndicator(null, null));
}
private List<MavenIndex> getSelectedIndices() {
List<MavenIndex> result = new ArrayList<>();
for (int i : myIndicesTable.getSelectedRows()) {
private List<MavenRepositoryInfo> getSelectedIndices() {
List<MavenRepositoryInfo> result = new ArrayList<>();
for (int i : myRepositoriesTable.getSelectedRows()) {
result.add(getIndexAt(i));
}
return result;
}
private MavenIndex getIndexAt(int i) {
MyTableModel model = (MyTableModel)myIndicesTable.getModel();
private MavenRepositoryInfo getIndexAt(int i) {
MyTableModel model = (MyTableModel)myRepositoriesTable.getModel();
return model.getIndex(i);
}
@@ -153,11 +131,11 @@ public class MavenRepositoriesConfigurable implements SearchableConfigurable, Co
@Override
public void reset() {
myIndicesTable.setModel(new MyTableModel(MavenIndicesManager.getInstance(myProject).getIndex().getIndices()));
myIndicesTable.getColumnModel().getColumn(0).setPreferredWidth(400);
myIndicesTable.getColumnModel().getColumn(1).setPreferredWidth(50);
myIndicesTable.getColumnModel().getColumn(2).setPreferredWidth(50);
myIndicesTable.getColumnModel().getColumn(3).setPreferredWidth(20);
myRepositoriesTable.setModel(new MyTableModel(MavenIndexUtils.getAllRepositories(myProject)));
myRepositoriesTable.getColumnModel().getColumn(0).setPreferredWidth(400);
myRepositoriesTable.getColumnModel().getColumn(1).setPreferredWidth(50);
myRepositoriesTable.getColumnModel().getColumn(2).setPreferredWidth(50);
myRepositoriesTable.getColumnModel().getColumn(3).setPreferredWidth(20);
myUpdatingIcon = new AsyncProcessIcon(IndicesBundle.message("maven.indices.updating"));
myUpdatingIcon.resume();
@@ -165,7 +143,7 @@ public class MavenRepositoriesConfigurable implements SearchableConfigurable, Co
myTimerListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myIndicesTable.repaint();
myRepositoriesTable.repaint();
}
};
myRepaintTimer = TimerUtil.createNamedTimer("Maven repaint", AsyncProcessIcon.CYCLE_LENGTH / AsyncProcessIcon.COUNT, myTimerListener);
@@ -189,9 +167,9 @@ public class MavenRepositoriesConfigurable implements SearchableConfigurable, Co
IndicesBundle.message("maven.index.updated"),
""};
private final List<MavenIndex> myIndices;
private final List<MavenRepositoryInfo> myIndices;
MyTableModel(List<MavenIndex> indices) {
MyTableModel(List<MavenRepositoryInfo> indices) {
myIndices = indices;
}
@@ -212,38 +190,31 @@ public class MavenRepositoriesConfigurable implements SearchableConfigurable, Co
@Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex == 3) return MavenIndexUpdateManager.IndexUpdatingState.class;
if (columnIndex == 3) return IndexUpdatingState.class;
return super.getColumnClass(columnIndex);
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
MavenSearchIndex i = getIndex(rowIndex);
MavenRepositoryInfo i = getIndex(rowIndex);
return switch (columnIndex) {
case 0 -> i.getRepositoryPathOrUrl();
case 0 -> i.getUrl();
case 1 -> switch (i.getKind()) {
case LOCAL -> "Local";
case REMOTE -> "Remote";
case ONLINE -> "Online";
};
case 2 -> {
if (i.getFailureMessage() != null) {
yield IndicesBundle.message("maven.index.updated.error");
}
long timestamp = i.getUpdateTimestamp();
if (timestamp == -1) yield IndicesBundle.message("maven.index.updated.never");
yield DateFormatUtil.formatDate(timestamp);
}
case 2 -> "";
case 3 -> {
MavenRepositoryInfo repository = i.getRepository();
if (repository == null) yield MavenIndexUpdateManager.IndexUpdatingState.IDLE;
MavenRepositoryInfo repository = i;
if (repository == null) yield IndexUpdatingState.IDLE;
yield MavenSystemIndicesManager.getInstance().getUpdatingStateSync(myProject, repository);
}
default -> throw new RuntimeException();
};
}
public MavenIndex getIndex(int rowIndex) {
public MavenRepositoryInfo getIndex(int rowIndex) {
return myIndices.get(rowIndex);
}
}
@@ -257,26 +228,16 @@ public class MavenRepositoriesConfigurable implements SearchableConfigurable, Co
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
MavenSearchIndex index = getIndexAt(row);
if (index.getFailureMessage() != null) {
if (isSelected) {
setForeground(JBColor.PINK);
}
else {
setBackground(JBColor.PINK);
}
}
return c;
}
}
private class MyIconCellRenderer extends MyCellRenderer {
MavenIndexUpdateManager.IndexUpdatingState myState;
IndexUpdatingState myState;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
myState = (MavenIndexUpdateManager.IndexUpdatingState)value;
myState = (IndexUpdatingState)value;
return super.getTableCellRendererComponent(table, "", isSelected, hasFocus, row, column);
}
@@ -0,0 +1,18 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.indices;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.model.RepositoryKind;
public interface MavenRepositoryIndex {
@ApiStatus.Internal
@NotNull
RepositoryKind getKind();
@NotNull
@ApiStatus.Internal
MavenRepositoryInfo getRepository();
void close(boolean releaseIndexContext);
}
@@ -16,25 +16,28 @@
package org.jetbrains.idea.maven.indices;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.util.messages.Topic;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.model.RepositoryKind;
import org.jetbrains.idea.maven.model.MavenArtifactInfo;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.model.RepositoryKind;
import java.io.File;
import java.util.Set;
public interface MavenSearchIndex {
public interface MavenSearchIndex extends MavenRepositoryIndex {
void close(boolean releaseIndexContext);
@Topic.AppLevel
Topic<IndexListener> INDEX_IS_BROKEN =
new Topic<>("Maven Index Broken Listener", IndexListener.class);
@NlsSafe
String getRepositoryId();
File getRepositoryFile();
@Nullable MavenRepositoryInfo getRepository();
@NotNull
MavenRepositoryInfo getRepository();
@NlsSafe
String getRepositoryUrl();
@@ -3,11 +3,14 @@ package org.jetbrains.idea.maven.indices
import com.intellij.ide.AppLifecycleListener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.project.Project
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.openapi.project.ProjectCloseListener
import com.intellij.openapi.project.getOpenedProjects
import com.intellij.platform.util.progress.withRawProgressReporter
import com.intellij.util.PathUtilRt
import com.intellij.util.messages.Topic
import kotlinx.coroutines.*
@@ -15,6 +18,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.jetbrains.annotations.TestOnly
import org.jetbrains.idea.maven.model.MavenRepositoryInfo
import org.jetbrains.idea.maven.model.RepositoryKind
import org.jetbrains.idea.maven.server.MavenIndexUpdateState
import org.jetbrains.idea.maven.server.MavenIndexerWrapper
import org.jetbrains.idea.maven.server.MavenServerManager
@@ -25,6 +29,9 @@ import java.io.File
import java.net.URI
import java.net.URISyntaxException
import java.nio.file.Path
import java.util.*
import java.util.concurrent.ConcurrentHashMap
interface IndexChangeProgressListener {
fun indexStatusChanged(state: MavenIndexUpdateState)
@@ -35,10 +42,11 @@ interface IndexChangeProgressListener {
class MavenSystemIndicesManager(val cs: CoroutineScope) {
private val openedIndices = HashMap<String, MavenIndex>()
private val updatingIndices = HashMap<String, Deferred<MavenIndex>>()
private val luceneIndices = HashMap<String, MavenIndex>()
private val inMemoryIndices = HashMap<String, MavenGAVIndex>()
private val gavUpdatingIndixes = ConcurrentHashMap<MavenGAVIndex, Boolean>()
private val mutex = Mutex()
private val statuses = HashMap<String, MavenIndexUpdateState>()
private val luceneUpdateStatusMap = ConcurrentHashMap<String, MavenIndexUpdateState>()
@Volatile
private var needPoll: Boolean = false
@@ -53,10 +61,10 @@ class MavenSystemIndicesManager(val cs: CoroutineScope) {
var anyInProgress = false;
status().forEach { s ->
anyInProgress = anyInProgress || s.myState == MavenIndexUpdateState.State.INDEXING
val oldStatus = statuses[s.myUrl];
val oldStatus = luceneUpdateStatusMap[s.myUrl];
if (oldStatus == null || oldStatus.timestamp < s.timestamp) {
statusToSend.add(s)
statuses[s.myUrl] = s
luceneUpdateStatusMap[s.myUrl] = s
}
}
@@ -67,15 +75,30 @@ class MavenSystemIndicesManager(val cs: CoroutineScope) {
}
}
ApplicationManager.getApplication().messageBus.connect().subscribe(ProjectCloseListener.TOPIC, object : ProjectCloseListener {
override fun projectClosed(project: Project) {
gc()
}
});
}
private var ourTestIndicesDir: Path? = null
suspend fun getClassIndexForRepository(repo: MavenRepositoryInfo): MavenSearchIndex {
return getIndexForRepo(repo)
suspend fun getClassIndexForRepository(repo: MavenRepositoryInfo): MavenSearchIndex? {
return getIndexForRepo(repo) as? MavenSearchIndex ?: null
}
suspend fun getGAVIndexForRepository(repo: MavenRepositoryInfo): MavenGAVIndex {
return getIndexForRepo(repo)
suspend fun getGAVIndexForRepository(repo: MavenRepositoryInfo): MavenGAVIndex? {
if (repo.kind == RepositoryKind.REMOTE) return null
return cs.async(Dispatchers.IO) {
val dir = getDirForMavenIndex(repo)
mutex.withLock {
inMemoryIndices[dir.toString()]?.let { return@async it }
return@async MavenLocalGavIndexImpl(repo)
.also { inMemoryIndices[dir.toString()] = it }
.also { scheduleUpdateIndexContent(listOf(it), false) }
}
}.await()
}
@TestOnly
@@ -95,17 +118,17 @@ class MavenSystemIndicesManager(val cs: CoroutineScope) {
fun updateIndexContentSync(repo: MavenRepositoryInfo,
fullUpdate: Boolean,
multithreaded: Boolean,
explicit: Boolean,
indicator: MavenProgressIndicator) {
return runBlockingMaybeCancellable {
updateIndexContent(repo, fullUpdate, multithreaded, indicator)
updateLuceneIndexContent(repo, fullUpdate, explicit, indicator)
}
}
private suspend fun updateIndexContent(repo: MavenRepositoryInfo,
fullUpdate: Boolean,
multithreaded: Boolean,
indicator: MavenProgressIndicator) {
private suspend fun updateLuceneIndexContent(repo: MavenRepositoryInfo,
fullUpdate: Boolean,
explicit: Boolean,
indicator: MavenProgressIndicator) {
coroutineScope {
@@ -119,42 +142,18 @@ class MavenSystemIndicesManager(val cs: CoroutineScope) {
}
})
indicator.addCancelCondition {
!updateScope.isActive
}
val index = getIndexForRepo(repo)
val deferredResult = mutex.withLock {
val deferred = updatingIndices[repo.url]
if (deferred == null) {
val newDeferred = updateScope.async {
index.updateOrRepair(fullUpdate, indicator, multithreaded)
index
}
updatingIndices.putIfAbsent(repo.url, newDeferred)
return@withLock newDeferred
}
else return@withLock deferred
}
deferredResult.invokeOnCompletion {
updateScope.async {
mutex.withLock {
updatingIndices.remove(repo.url)
}
}
}
deferredResult.await()
startUpdateLuceneIndex(repo)
}
}
fun startUpdateIndex(repo: MavenRepositoryInfo) {
fun startUpdateLuceneIndex(repo: MavenRepositoryInfo) {
val indexFile = getDirForMavenIndex(repo).toFile()
val status = getIndexWrapper().startIndexing(repo, indexFile)
needPoll = true
if (status != null) {
statuses[repo.url] = status;
luceneUpdateStatusMap[repo.url] = status;
ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).indexStatusChanged(status)
}
}
@@ -170,7 +169,7 @@ class MavenSystemIndicesManager(val cs: CoroutineScope) {
return cs.async(Dispatchers.IO) {
val dir = getDirForMavenIndex(repo)
mutex.withLock {
openedIndices[dir.toString()]?.let { return@async it }
luceneIndices[dir.toString()]?.let { return@async it }
val holder = getProperties(dir) ?: MavenIndexUtils.IndexPropertyHolder(
dir.toFile(),
@@ -178,7 +177,7 @@ class MavenSystemIndicesManager(val cs: CoroutineScope) {
setOf(repo.id),
repo.url
)
return@async MavenIndexImpl(getIndexWrapper(), holder).also { openedIndices[dir.toString()] = it }
return@async MavenIndexImpl(getIndexWrapper(), holder).also { luceneIndices[dir.toString()] = it }
}
}.await()
}
@@ -222,20 +221,111 @@ class MavenSystemIndicesManager(val cs: CoroutineScope) {
}
@Deprecated("")
fun getOrCreateIndices(project: Project): MavenIndices {
return getIndexWrapper().getOrCreateIndices(project)
}
fun getUpdatingStateSync(project: Project, repository: MavenRepositoryInfo): MavenIndexUpdateManager.IndexUpdatingState {
return runWithModalProgressBlocking(project, repository.name) {
return@runWithModalProgressBlocking mutex.withLock {
val deferred = updatingIndices[repository.url]
if (deferred == null) return@withLock MavenIndexUpdateManager.IndexUpdatingState.IDLE
return@withLock MavenIndexUpdateManager.IndexUpdatingState.UPDATING
fun getUpdatingStateSync(project: Project, repository: MavenRepositoryInfo): IndexUpdatingState {
val status = luceneUpdateStatusMap[repository.url]
if (status == null) return IndexUpdatingState.IDLE else return IndexUpdatingState.UPDATING
}
fun gc() {
val validIndices = ReadAction.compute<Set<Any>, Throwable> {
val existed = Collections.newSetFromMap(IdentityHashMap<Any, Boolean>())
getOpenedProjects()
.filter { !it.isDisposed }
.mapNotNull { MavenIndicesManager.getInstanceIfCreated(it) }
.forEach {
existed.addAll(it.getGAVIndices())
existed.addAll(it.getSearchIndices())
}
existed
}
collectGarbage(validIndices, luceneIndices) {
it.close(true)
}
collectGarbage(validIndices, inMemoryIndices) {
it.close(true)
}
}
private fun <T : Any> collectGarbage(validIndices: Set<Any>, indices: HashMap<String, T>, action: (T) -> Unit = {}) {
val iterator = indices.iterator()
while (iterator.hasNext()) {
val idx = iterator.next()
if (!validIndices.contains(idx.value)) {
iterator.remove()
action(idx.value);
}
}
}
fun scheduleUpdateIndexContent(toUpdate: List<MavenUpdatableIndex>, explicit: Boolean) {
val luceneUpdate = ArrayList<MavenIndex>()
val inMemoryUpdate = ArrayList<MavenGAVIndex>()
for (idx: MavenUpdatableIndex in toUpdate) {
if (idx is MavenIndex && idx in luceneIndices.values) luceneUpdate.add(idx)
else if (idx is MavenGAVIndex && idx in inMemoryIndices.values
&& gavUpdatingIndixes.putIfAbsent(idx, true) == null) inMemoryUpdate.add(idx)
}
inMemoryUpdate.forEach { idx ->
cs.async(Dispatchers.IO) {
withRawProgressReporter {
val indicator = MavenProgressIndicator(null, null)
try {
(idx as MavenUpdatableIndex).updateOrRepair(true, indicator, explicit)
}
finally {
gavUpdatingIndixes.remove(idx)
}
}
}
}
luceneUpdate.forEach { idx ->
luceneUpdateStatusMap[idx.repository.url] = MavenIndexUpdateState(idx.repository.url, null, null,
MavenIndexUpdateState.State.INDEXING);
cs.launch {
try {
val indicator = MavenProgressIndicator(null, null)
idx.updateOrRepair(true, indicator, explicit)
luceneUpdateStatusMap[idx.repository.url] = MavenIndexUpdateState(idx.repository.url, null, null,
MavenIndexUpdateState.State.SUCCEED)
}
catch (e: Throwable) {
luceneUpdateStatusMap[idx.repository.url] = MavenIndexUpdateState(idx.repository.url, null, null,
MavenIndexUpdateState.State.FAILED)
}
}
}
}
@TestOnly
suspend fun waitAllGavsUpdatesCompleted() {
while (!gavUpdatingIndixes.isEmpty()) {
delay(500)
}
}
@TestOnly
suspend fun waitAllLuceneUpdatesCompleted() {
while (true) {
delay(500)
if (luceneUpdateStatusMap.isEmpty() || luceneUpdateStatusMap.values.all {
it.myState == MavenIndexUpdateState.State.SUCCEED
|| it.myState == MavenIndexUpdateState.State.FAILED
}) return
}
}
companion object {
@JvmStatic
@@ -1,12 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.indices;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.project.MavenGeneralSettings;
import org.jetbrains.idea.maven.utils.MavenProcessCanceledException;
import org.jetbrains.idea.maven.utils.MavenProgressIndicator;
public interface MavenUpdatableIndex {
void updateOrRepair(boolean fullUpdate, MavenProgressIndicator progress, boolean multithreaded)
throws MavenProcessCanceledException;
}
@@ -0,0 +1,14 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.indices
import org.jetbrains.idea.maven.server.AddArtifactResponse
import org.jetbrains.idea.maven.utils.MavenProcessCanceledException
import org.jetbrains.idea.maven.utils.MavenProgressIndicator
import java.io.File
interface MavenUpdatableIndex : MavenRepositoryIndex {
@Throws(MavenProcessCanceledException::class)
fun updateOrRepair(fullUpdate: Boolean, progress: MavenProgressIndicator, explicit: Boolean)
fun tryAddArtifacts(artifactFiles: Collection<File>): List<AddArtifactResponse>
}
@@ -29,7 +29,7 @@ abstract class IndexUpdateAction : DumbAwareAction() {
val manager = MavenSystemIndicesManager.getInstance()
MavenSystemIndicesManager.getInstance().cs.launch(Dispatchers.IO + CoroutineName(this.javaClass.name)) {
manager.startUpdateIndex(mavenRepo)
manager.startUpdateLuceneIndex(mavenRepo)
}
}
@@ -1,8 +1,9 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.onlinecompletion
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.idea.maven.indices.MavenGAVIndex
import org.jetbrains.idea.maven.indices.MavenIndicesManager
import org.jetbrains.idea.maven.model.MavenId
import org.jetbrains.idea.maven.onlinecompletion.model.MavenRepositoryArtifactInfo
import org.jetbrains.idea.maven.utils.MavenLog
@@ -13,7 +14,7 @@ import kotlin.math.min
/**
* This class is used as a solution to support completion from repositories, which do not support online completion
*/
internal class IndexBasedCompletionProvider(private val myIndex: MavenGAVIndex) : DependencySearchProvider {
internal class IndexBasedCompletionProvider(private val myProject: Project) : DependencySearchProvider {
override suspend fun fulltextSearch(searchString: String): List<RepositoryArtifactData> =
search(MavenId(searchString))
@@ -24,17 +25,18 @@ internal class IndexBasedCompletionProvider(private val myIndex: MavenGAVIndex)
private fun search(mavenId: MavenId): List<RepositoryArtifactData> {
MavenLog.LOG.debug("Index: get local maven artifacts started")
val result = buildList {
for (groupId in myIndex.groupIds) {
val index = MavenIndicesManager.getInstance(myProject).getCommonGavIndex()
for (groupId in index.groupIds) {
if (groupId == null) continue
if (!mavenId.groupId.isNullOrEmpty() && !nonExactMatches(groupId, mavenId.groupId!!)) {
continue
}
for (artifactId in myIndex.getArtifactIds(groupId)) {
for (artifactId in index.getArtifactIds(groupId)) {
if (!mavenId.artifactId.isNullOrEmpty() && !nonExactMatches(artifactId, mavenId.artifactId!!)) {
continue
}
if (artifactId == null) continue
val info = MavenRepositoryArtifactInfo(groupId, artifactId, myIndex.getVersions(groupId, artifactId))
val info = MavenRepositoryArtifactInfo(groupId, artifactId, index.getVersions(groupId, artifactId))
add(info)
MavenLog.LOG.debug("Index: local maven artifact found ${info.groupId}:${info.artifactId}, completions: ${info.items.size}")
}
@@ -42,7 +44,7 @@ internal class IndexBasedCompletionProvider(private val myIndex: MavenGAVIndex)
}
MavenLog.LOG.debug("Index: get local maven artifacts finished")
return result
}
}
private fun nonExactMatches(template: String, real: String): Boolean {
val splittedTemplate = template.split(delimiters = charArrayOf('-', '.'))
@@ -63,4 +65,4 @@ internal class IndexBasedCompletionProvider(private val myIndex: MavenGAVIndex)
}
override fun isLocal() = true
}
}
@@ -23,22 +23,7 @@ public class MavenCompletionProviderFactory implements DependencySearchProviders
List<DependencySearchProvider> result = new ArrayList<>();
result.add(new ProjectModulesCompletionProvider(project));
addIndices(project, result);
result.add(new IndexBasedCompletionProvider(project));
return result;
}
private static void addIndices(Project project, List<DependencySearchProvider> result) {
List<MavenGAVIndex> remoteIndices = MavenIndicesManager.getInstance(project).getIndex().getGAVIndices();
for (MavenGAVIndex index : remoteIndices) {
MavenRepositoryInfo repository = index.getRepository();
if (repository == null) continue;
if (!repository.getName().toLowerCase(Locale.ROOT).contains("central")
&& !repository.getUrl().toLowerCase(Locale.ROOT).contains("repo.maven.apache.org/maven2")) {
result.add(new IndexBasedCompletionProvider(index));
}
}
}
}
@@ -257,7 +257,10 @@ public abstract class MavenProjectsManager extends MavenSimpleProjectComponent
fireActivated();
if (!ApplicationManager.getApplication().isUnitTestMode()) {
listenForExternalChanges();
MavenIndicesManager.getInstance(myProject).scheduleUpdateIndicesList(null);
MavenIndicesManager.getInstance(myProject).scheduleUpdateIndicesList(()->{
MavenIndicesManager.getInstance(myProject).scheduleUpdateLocalGavContent(false);
return null;
});
}
}
@@ -140,7 +140,7 @@ public abstract class MavenIndexerWrapper extends MavenRemoteObjectWrapper<Maven
}
@NotNull
public List<AddArtifactResponse> addArtifacts(MavenIndexId mavenIndexId, Collection<File> artifactFiles) {
public List<AddArtifactResponse> addArtifacts(MavenIndexId mavenIndexId, Collection<? extends File> artifactFiles) {
return perform(() -> {
try {
return getOrCreateWrappee().addArtifacts(mavenIndexId, new ArrayList<>(artifactFiles), ourToken);
@@ -6,7 +6,7 @@ import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
object MavenIndexUsageCollector : CounterUsagesCollector() {
val GROUP = EventLogGroup("maven.indexing", 2)
val GROUP = EventLogGroup("maven.indexing", 3)
@JvmField
val IS_LOCAL = EventFields.Boolean("is_local")
@@ -23,6 +23,12 @@ object MavenIndexUsageCollector : CounterUsagesCollector() {
@JvmField
val MANUAL = EventFields.Boolean("manual")
@JvmField
val GROUPS_COUNT = EventFields.RoundedInt("groups_count")
@JvmField
val ARTIFACTS_COUNT = EventFields.RoundedInt("artifacts_count")
@JvmField
val INDEX_UPDATE = GROUP.registerIdeActivity("index.update",
finishEventAdditionalFields = arrayOf(IS_LOCAL,
@@ -31,11 +37,20 @@ object MavenIndexUsageCollector : CounterUsagesCollector() {
IS_SUCCESS,
MANUAL))
@JvmField
val GAV_INDEX_UPDATE = GROUP.registerIdeActivity("gav.index.update",
finishEventAdditionalFields = arrayOf(MANUAL,
IS_SUCCESS,
GROUPS_COUNT,
ARTIFACTS_COUNT))
@JvmField
val INDEX_BROKEN = GROUP.registerEvent("index.broken")
@JvmField
val INDEX_OPENED = GROUP.registerEvent("index.open", IS_LOCAL, IS_CENTRAL, IS_PRIVATE_REMOTE)
val INDEX_OPENED = GROUP.registerEvent("index.open", IS_LOCAL, IS_CENTRAL,
IS_PRIVATE_REMOTE)
@JvmField
val ADD_ARTIFACT_FROM_POM = GROUP.registerEvent("artifact.from.pom.added")
@@ -1219,6 +1219,7 @@ $libPath<caret></systemPath>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<exclusions>
<<error descr="'artifactId' child tag should be defined">exclusion</error>>
<groupId>jmock</groupId>
@@ -6,6 +6,8 @@ import kotlinx.coroutines.runBlocking
import org.junit.Test
class MavenDependencySmartCompletionTest : MavenDomWithIndicesTestCase() {
override fun importProjectOnSetup() = true
@Test
fun testCompletion() = runBlocking {
createProjectPom("""
@@ -22,6 +24,8 @@ class MavenDependencySmartCompletionTest : MavenDomWithIndicesTestCase() {
assertCompletionVariantsInclude(projectPom, RENDERING_TEXT, "junit:junit")
}
@Test
fun testInsertDependency() = runBlocking {
createProjectPom("""
@@ -171,12 +175,6 @@ class MavenDependencySmartCompletionTest : MavenDomWithIndicesTestCase() {
@Test
fun testCompletionArtifactIdThenVersion() = runBlocking {
importProjectAsync("""
<groupId>test</groupId>
<artifactId>project</artifactId>
<version>1</version>
""".trimIndent())
createProjectPom("""
<groupId>test</groupId><artifactId>project</artifactId><version>1</version>
<dependencies>
@@ -216,11 +214,6 @@ class MavenDependencySmartCompletionTest : MavenDomWithIndicesTestCase() {
@Test
fun testCompletionArtifactIdThenGroupIdThenInsertVersion() = runBlocking {
importProjectAsync("""
<groupId>test</groupId>
<artifactId>project</artifactId>
<version>1</version>
""".trimIndent())
createProjectPom("""
<groupId>test</groupId><artifactId>project</artifactId><version>1</version>
@@ -257,11 +250,6 @@ class MavenDependencySmartCompletionTest : MavenDomWithIndicesTestCase() {
@Test
fun testCompletionArtifactIdNonExactmatch() = runBlocking {
importProjectAsync("""
<groupId>test</groupId>
<artifactId>project</artifactId>
<version>1</version>
""".trimIndent())
createProjectPom("""
<groupId>test</groupId><artifactId>project</artifactId><version>1</version>
@@ -283,11 +271,6 @@ class MavenDependencySmartCompletionTest : MavenDomWithIndicesTestCase() {
@Test
fun testCompletionArtifactIdInsideManagedDependency() = runBlocking {
importProjectAsync("""
<groupId>test</groupId>
<artifactId>project</artifactId>
<version>1</version>
""".trimIndent())
createProjectPom("""
<groupId>test</groupId><artifactId>project</artifactId><version>1</version>
@@ -330,18 +313,6 @@ class MavenDependencySmartCompletionTest : MavenDomWithIndicesTestCase() {
@Test
fun testCompletionArtifactIdWithManagedDependency() = runBlocking {
importProjectAsync("""
<groupId>test</groupId><artifactId>project</artifactId><version>1</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.intellijgroup</groupId>
<artifactId>intellijartifact</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
""".trimIndent())
createProjectPom("""
<groupId>test</groupId><artifactId>project</artifactId><version>1</version>
@@ -394,22 +365,6 @@ class MavenDependencySmartCompletionTest : MavenDomWithIndicesTestCase() {
@Test
fun testCompletionGroupIdWithManagedDependencyWithTypeAndClassifier() = runBlocking {
importProjectAsync("""
<groupId>test</groupId><artifactId>project</artifactId><version>1</version>
<properties>
<ioClassifier>ccc</ioClassifier> <ioType>ttt</ioType></properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<classifier>${'$'}{ioClassifier}</classifier>
<type>${'$'}{ioType}</type>
<version>2.4</version>
</dependency>
</dependencies>
</dependencyManagement>
""".trimIndent())
createProjectPom("""
<groupId>test</groupId><artifactId>project</artifactId><version>1</version>
@@ -17,12 +17,14 @@ package org.jetbrains.idea.maven.dom
import com.intellij.maven.testFramework.MavenDomTestCase
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.ExtensionTestUtil.maskExtensions
import com.intellij.util.containers.ContainerUtil
import kotlinx.coroutines.runBlocking
import org.jetbrains.idea.maven.indices.MavenIndicesManager
import org.jetbrains.idea.maven.indices.MavenIndicesManager.MavenIndexerListener
import org.jetbrains.idea.maven.indices.MavenIndicesTestFixture
import org.jetbrains.idea.maven.indices.MavenSystemIndicesManager
import org.jetbrains.idea.maven.model.MavenRepositoryInfo
import org.jetbrains.idea.maven.onlinecompletion.MavenCompletionProviderFactory
import org.jetbrains.idea.maven.server.MavenServerConnector
import org.jetbrains.idea.maven.server.MavenServerDownloadListener
@@ -59,6 +61,12 @@ abstract class MavenDomWithIndicesTestCase : MavenDomTestCase() {
protected open fun createIndicesFixture(): MavenIndicesTestFixture {
return MavenIndicesTestFixture(dir.toPath(), project)
}
override suspend fun importProjectsAsync(files: List<VirtualFile>) {
super.importProjectsAsync(files)
MavenIndicesManager.getInstance(project).waitForGavUpdateCompleted();
}
override fun tearDown() {
try {
if (myIndicesFixture != null) {
@@ -80,16 +88,19 @@ abstract class MavenDomWithIndicesTestCase : MavenDomTestCase() {
artifactIdsToIndex.addAll(expectedArtifactIds!!)
ApplicationManager.getApplication().getMessageBus().connect(getTestRootDisposable())
.subscribe(MavenIndicesManager.INDEXER_TOPIC, MavenIndexerListener { added, failedToAdd ->
artifactIdsToIndex.removeIf { artifactId: String? ->
ContainerUtil.exists(added) { file: File ->
file.path.contains(
artifactId!!)
.subscribe(MavenIndicesManager.INDEXER_TOPIC, object : MavenIndexerListener {
override fun gavIndexUpdated(repo: MavenRepositoryInfo, added: Set<File>, failedToAdd: Set<File>) {
artifactIdsToIndex.removeIf { artifactId: String? ->
added.any { file: File ->
file.path.contains(
artifactId!!)
}
}
if (artifactIdsToIndex.isEmpty()) {
latch.countDown()
}
}
if (artifactIdsToIndex.isEmpty()) {
latch.countDown()
}
})
action()
@@ -134,4 +145,9 @@ abstract class MavenDomWithIndicesTestCase : MavenDomTestCase() {
assertUnorderedElementsAreEqual(artifactIds, actualEvents)
}
override suspend fun checkHighlighting() {
MavenSystemIndicesManager.getInstance().waitAllGavsUpdatesCompleted()
super.checkHighlighting()
}
}
@@ -5,9 +5,9 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.idea.maven.model.MavenArchetype;
import org.jetbrains.idea.maven.model.MavenId;
import org.jetbrains.idea.maven.model.MavenRepositoryInfo;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.utils.MavenProcessCanceledException;
import org.junit.Assert;
import org.junit.Test;
import java.io.File;
@@ -56,16 +56,14 @@ public class MavenIndicesManagerTest extends MavenIndicesTestCase {
@Test
public void testIndexedArchetypes() throws Exception {
myIndicesFixture.getRepositoryHelper().addTestData("archetypes");
File archetypes = myIndicesFixture.getRepositoryHelper().getTestData("archetypes");
MavenProjectsManager.getInstance(getProject()).getGeneralSettings().setLocalRepository(archetypes.getPath());
myIndicesFixture.getIndicesManager().scheduleUpdateIndicesList(null);
MavenIndexHolder indexHolder = myIndicesFixture.getIndicesManager().getIndex();
MavenIndex localIndex = indexHolder.getLocalIndex();
Assert.assertNotNull(localIndex);
localIndex.updateOrRepair(true, getMavenProgressIndicator(), false);
assertArchetypeExists("org.apache.maven.archetypes:maven-archetype-foobar:1.0");
//myIndicesFixture.getRepositoryHelper().addTestData("archetypes");
//File archetypes = myIndicesFixture.getRepositoryHelper().getTestData("archetypes");
//MavenProjectsManager.getInstance(getProject()).getGeneralSettings().setLocalRepository(archetypes.getPath());
//myIndicesFixture.getIndicesManager().updateIndicesListSync();
//var localIndex = myIndicesFixture.getIndicesManager().getCommonGavIndex();
//Assert.assertNotNull(localIndex);
//
//assertArchetypeExists("org.apache.maven.archetypes:maven-archetype-foobar:1.0");
}
@Test
@@ -81,16 +79,16 @@ public class MavenIndicesManagerTest extends MavenIndicesTestCase {
File localRepo = myIndicesFixture.getRepositoryHelper().getTestData("local2");
MavenProjectsManager.getInstance(getProject()).getGeneralSettings().setLocalRepository(localRepo.getPath());
myIndicesFixture.getIndicesManager().scheduleUpdateIndicesList(null);
myIndicesFixture.getIndicesManager().waitForBackgroundTasksInTests();
MavenIndexHolder indexHolder = myIndicesFixture.getIndicesManager().getIndex();
MavenIndex localIndex = indexHolder.getLocalIndex();
myIndicesFixture.getIndicesManager().scheduleUpdateIndicesList();
myIndicesFixture.getIndicesManager().waitForGavUpdateCompleted();
MavenGAVIndex localIndex = myIndicesFixture.getIndicesManager().getCommonGavIndex();
assertTrue(localIndex.getArtifactIds("junit").isEmpty());
//copy junit to repository
File artifactDir = myIndicesFixture.getRepositoryHelper().getTestData("local1/junit");
FileUtil.copyDir(artifactDir, localRepo);
assertTrue(localIndex.getArtifactIds("junit").isEmpty());
File artifactFile = myIndicesFixture.getRepositoryHelper().getTestData("local1/junit/junit/4.0/junit-4.0.pom");
File artifactFile = myIndicesFixture.getRepositoryHelper().getTestData("local2/junit/junit/4.0/junit-4.0.pom");
var latch = new CountDownLatch(1);
Set<File> addedFiles = ConcurrentHashMap.newKeySet();
@@ -98,14 +96,15 @@ public class MavenIndicesManagerTest extends MavenIndicesTestCase {
ApplicationManager.getApplication().getMessageBus().connect(getTestRootDisposable())
.subscribe(MavenIndicesManager.INDEXER_TOPIC, new MavenIndicesManager.MavenIndexerListener() {
@Override
public void indexUpdated(Set<File> added, Set<File> failedToAdd) {
public void gavIndexUpdated(MavenRepositoryInfo repo, Set<? extends File> added, Set<? extends File> failedToAdd) {
addedFiles.addAll(added);
failedToAddFiles.addAll(failedToAdd);
latch.countDown();
}
});
var indexingScheduled = MavenIndicesManager.getInstance(getProject()).scheduleArtifactIndexing(null, artifactFile);
var indexingScheduled =
MavenIndicesManager.getInstance(getProject()).scheduleArtifactIndexing(null, artifactFile, localRepo.getAbsolutePath());
assertTrue("Failed to schedule indexing", indexingScheduled);
latch.await(1, TimeUnit.MINUTES);
@@ -114,13 +113,14 @@ public class MavenIndicesManagerTest extends MavenIndicesTestCase {
assertSize(1, addedFiles);
String indexedUri = Path.of(addedFiles.iterator().next().getAbsolutePath()).toUri().toString();
assertTrue("Junit pom not indexed", indexedUri.endsWith("local1/junit/junit/4.0/junit-4.0.pom"));
assertTrue("Junit pom not indexed", indexedUri.endsWith("local2/junit/junit/4.0/junit-4.0.pom"));
myIndicesFixture.getIndicesManager().waitForBackgroundTasksInTests();
myIndicesFixture.getIndicesManager().waitForGavUpdateCompleted();
myIndicesFixture.getIndicesManager().waitForLuceneUpdateCompleted();
Set<String> versions = localIndex.getVersions("junit", "junit");
assertFalse(versions.isEmpty());
assertTrue(versions.contains("4.0"));
assertFalse(versions.contains("3.8.2")); // copied but not used
assertFalse(versions.contains("3.8.2"));
}
private void assertArchetypeExists(String archetypeId) {
@@ -95,12 +95,12 @@ public class MavenIndicesTest extends CodeInsightFixtureTestCase {
MavenRepositoryInfo localRepo = new MavenRepositoryInfo(LOCAL_REPOSITORY_ID, "/home/user/.m2/repository", RepositoryKind.LOCAL);
localDiff = MavenIndices.getLocalDiff(localRepo, myContext, null);
Assert.assertNotNull(localDiff.newIndices);
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepositoryPathOrUrl());
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepository().getUrl());
Assert.assertNull(localDiff.oldIndices);
localDiff = MavenIndices.getLocalDiff(localRepo, myContext, localDiff.newIndices);
Assert.assertNotNull(localDiff.newIndices);
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepositoryPathOrUrl());
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepository().getUrl());
Assert.assertNull(localDiff.oldIndices);
}
@@ -108,14 +108,14 @@ public class MavenIndicesTest extends CodeInsightFixtureTestCase {
MavenRepositoryInfo localRepo = new MavenRepositoryInfo(LOCAL_REPOSITORY_ID, "/home/user/.m2/repository", RepositoryKind.LOCAL);
localDiff = MavenIndices.getLocalDiff(localRepo, myContext, null);
Assert.assertNotNull(localDiff.newIndices);
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepositoryPathOrUrl());
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepository().getUrl());
Assert.assertNull(localDiff.oldIndices);
localRepo = new MavenRepositoryInfo(LOCAL_REPOSITORY_ID, "/home/user/.m3/repository", RepositoryKind.LOCAL);
MavenIndex currentLocalIndex = localDiff.newIndices;
localDiff = MavenIndices.getLocalDiff(localRepo, myContext, currentLocalIndex);
Assert.assertNotNull(localDiff.newIndices);
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepositoryPathOrUrl());
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepository().getUrl());
Assert.assertSame(currentLocalIndex, localDiff.oldIndices);
}
@@ -123,7 +123,7 @@ public class MavenIndicesTest extends CodeInsightFixtureTestCase {
MavenRepositoryInfo localRepo = new MavenRepositoryInfo(LOCAL_REPOSITORY_ID, "/home/user/.m4/repository", RepositoryKind.LOCAL);
localDiff = MavenIndices.getLocalDiff(localRepo, myContext, null);
Assert.assertNotNull(localDiff.newIndices);
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepositoryPathOrUrl());
Assert.assertEquals(localRepo.getUrl(), localDiff.newIndices.getRepository().getUrl());
Assert.assertNull(localDiff.oldIndices);
}
@@ -132,12 +132,12 @@ public class MavenIndicesTest extends CodeInsightFixtureTestCase {
Map<String, Set<String>> remoteRepositoryIdsByUrl = Map.of(remoteRepo.getUrl(), Collections.singleton(remoteRepo.getId()));
remoteDiff = MavenIndices.getRemoteDiff(remoteRepositoryIdsByUrl, Collections.emptyList(), myContext);
Assert.assertEquals(1, remoteDiff.newIndices.size());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepositoryPathOrUrl());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepository().getUrl());
Assert.assertTrue(remoteDiff.oldIndices.isEmpty());
remoteDiff = MavenIndices.getRemoteDiff(remoteRepositoryIdsByUrl, remoteDiff.newIndices, myContext);
Assert.assertEquals(1, remoteDiff.newIndices.size());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepositoryPathOrUrl());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepository().getUrl());
Assert.assertTrue(remoteDiff.oldIndices.isEmpty());
}
@@ -146,16 +146,16 @@ public class MavenIndicesTest extends CodeInsightFixtureTestCase {
Map<String, Set<String>> remoteRepositoryIdsByUrl = Map.of(remoteRepo.getUrl(), Collections.singleton(remoteRepo.getId()));
remoteDiff = MavenIndices.getRemoteDiff(remoteRepositoryIdsByUrl, Collections.emptyList(), myContext);
Assert.assertEquals(1, remoteDiff.newIndices.size());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepositoryPathOrUrl());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepository().getUrl());
Assert.assertTrue(remoteDiff.oldIndices.isEmpty());
remoteRepo = new MavenRepositoryInfo("snapshot", "https://repo.maven.apache.org/snapshot", RepositoryKind.REMOTE);
remoteRepositoryIdsByUrl = Map.of(remoteRepo.getUrl(), Collections.singleton(remoteRepo.getId()));
remoteDiff = MavenIndices.getRemoteDiff(remoteRepositoryIdsByUrl, remoteDiff.newIndices, myContext);
Assert.assertEquals(1, remoteDiff.newIndices.size());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepositoryPathOrUrl());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepository().getUrl());
Assert.assertEquals(1, remoteDiff.oldIndices.size());
Assert.assertTrue(remoteDiff.oldIndices.get(0).getRepositoryPathOrUrl().contains("maven2"));
Assert.assertTrue(remoteDiff.oldIndices.get(0).getRepository().getUrl().contains("maven2"));
}
public void testGetRemoteCreateNew() {
@@ -163,7 +163,7 @@ public class MavenIndicesTest extends CodeInsightFixtureTestCase {
Map<String, Set<String>> remoteRepositoryIdsByUrl = Map.of(remoteRepo.getUrl(), Collections.singleton(remoteRepo.getUrl()));
remoteDiff = MavenIndices.getRemoteDiff(remoteRepositoryIdsByUrl, Collections.emptyList(), myContext);
Assert.assertEquals(1, remoteDiff.newIndices.size());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepositoryPathOrUrl());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepository().getUrl());
Assert.assertTrue(remoteDiff.oldIndices.isEmpty());
}
@@ -181,7 +181,7 @@ public class MavenIndicesTest extends CodeInsightFixtureTestCase {
Map<String, Set<String>> remoteRepositoryIdsByUrl = Map.of(remoteRepo.getUrl(), Collections.singleton(remoteRepo.getId()));
remoteDiff = MavenIndices.getRemoteDiff(remoteRepositoryIdsByUrl, Collections.emptyList(), myContext);
Assert.assertEquals(1, remoteDiff.newIndices.size());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepositoryPathOrUrl());
Assert.assertEquals(remoteRepo.getUrl(), remoteDiff.newIndices.get(0).getRepository().getUrl());
Assert.assertTrue(remoteDiff.oldIndices.isEmpty());
}
@@ -26,6 +26,7 @@ import org.jetbrains.idea.maven.server.MavenServerManager;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
public class MavenIndicesTestFixture {
private final Path myDir;
@@ -68,8 +69,14 @@ public class MavenIndicesTestFixture {
public void setUpAfterImport() {
MavenSystemIndicesManager.getInstance().setTestIndicesDir(myDir.resolve("MavenIndices"));
getIndicesManager().scheduleUpdateIndicesList(null);
getIndicesManager().waitForBackgroundTasksInTests();
//todo: rewrite al this to coroutines
CompletableFuture<Void> f = new CompletableFuture<>();
getIndicesManager().scheduleUpdateIndicesList(() -> {
f.complete(null);
return null;
});
f.join();
getIndicesManager().waitForGavUpdateCompleted();
UIUtil.dispatchAllInvocationEvents();
}
@@ -65,13 +65,13 @@ class MavenMultiProjectImportTest : ProjectWizardTestCase<AbstractProjectWizard?
importMaven(project2, pom2)
MavenIndicesManager.getInstance(project2).updateIndicesListSync()
MavenIndicesManager.getInstance(myProject).updateIndicesListSync()
val firstIndices = MavenIndicesManager.getInstance(myProject).getIndex()
/*
val firstIndices = MavenIndicesManager.getInstance(myProject).
val secondIndices = MavenIndicesManager.getInstance(project2).getIndex()
Assertions.assertThat(firstIndices.indices).hasSize(2)
Assertions.assertThat(secondIndices.indices).hasSize(2)
assertSame(firstIndices.localIndex, secondIndices.localIndex)
assertSame(firstIndices.remoteIndices[0], secondIndices.remoteIndices[0])
assertSame(firstIndices.remoteIndices[0], secondIndices.remoteIndices[0])*/
}
private fun createPomXml(dir: String, @Language(value = "XML", prefix = "<project>", suffix = "</project>") xml: String): VirtualFile? {
@@ -25,19 +25,21 @@ class MavenSearcherTest : MavenIndicesTestCase() {
private val JMOCK_VERSIONS = arrayOf("jmock:jmock:1.2.0", "jmock:jmock:1.1.0", "jmock:jmock:1.0.0")
private val COMMONS_IO_VERSIONS = arrayOf("commons-io:commons-io:2.4")
private var myIndicesFixture: MavenIndicesTestFixture? = null
private lateinit var myIndicesFixture: MavenIndicesTestFixture
@Throws(Exception::class)
override fun setUp() {
super.setUp()
myIndicesFixture = MavenIndicesTestFixture(dir.toPath(), project)
myIndicesFixture!!.setUp()
myIndicesFixture.setUp()
myIndicesFixture.indicesManager.scheduleUpdateContentLocalClassIndex(true)
myIndicesFixture.indicesManager.waitForLuceneUpdateCompleted()
}
@Throws(Exception::class)
override fun tearDown() {
try {
myIndicesFixture!!.tearDown()
myIndicesFixture.tearDown()
}
catch (e: Throwable) {
addSuppressedException(e)
@@ -47,6 +47,7 @@ import org.jetbrains.idea.maven.dom.converters.MavenDependencyCompletionUtil
import org.jetbrains.idea.maven.dom.inspections.MavenModelInspection
import org.jetbrains.idea.maven.dom.model.MavenDomProjectModel
import org.jetbrains.idea.maven.dom.references.MavenPsiElementWrapper
import org.jetbrains.idea.maven.indices.MavenSystemIndicesManager
import org.jetbrains.idea.maven.onlinecompletion.model.MavenRepositoryArtifactInfo
import org.jetbrains.idea.maven.utils.MavenLog
import java.io.IOException
@@ -351,7 +352,7 @@ abstract class MavenDomTestCase : MavenMultiVersionImportingTestCase() {
}
}
protected suspend fun checkHighlighting() {
protected open suspend fun checkHighlighting() {
checkHighlighting(projectPom)
}
@@ -1,6 +1,7 @@
package org.jetbrains.idea.reposearch
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.extensions.ExtensionPointName
@@ -9,6 +10,7 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.progress.util.ProgressWrapper
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pair
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.containers.CollectionFactory
import kotlinx.coroutines.CompletableDeferred
@@ -39,6 +41,7 @@ class DependencySearchService(private val project: Project) : Disposable {
private data class DeferredCacheKey(val provider: DependencySearchProvider, val cacheKey: String)
override fun dispose() {
}