diff --git a/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/autocomplete/processors/AttributeValueCompletionProcessor.java b/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/autocomplete/processors/AttributeValueCompletionProcessor.java index 24224154d3..525623cd1d 100644 --- a/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/autocomplete/processors/AttributeValueCompletionProcessor.java +++ b/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/autocomplete/processors/AttributeValueCompletionProcessor.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.pde.internal.genericeditor.target.extension.autocomplete.processors; +import java.net.URI; import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -111,6 +112,11 @@ public ICompletionProposal[] getCompletionProposals() { } } + if (ITargetConstants.REPOSITORY_LOCATION_ATTR.equalsIgnoreCase(acKey)) { + List children = RepositoryCache.fetchChildrenOfRepo(searchTerm); + return toProposals(children.stream().map(URI::toString)); + } + return new ICompletionProposal[] {}; } diff --git a/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/model/RepositoryCache.java b/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/model/RepositoryCache.java index 9cde42368e..c11caa1211 100644 --- a/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/model/RepositoryCache.java +++ b/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/model/RepositoryCache.java @@ -24,15 +24,18 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.equinox.p2.metadata.IVersionedId; +import org.eclipse.equinox.p2.metadata.VersionedId; import org.eclipse.osgi.util.NLS; import org.eclipse.pde.internal.genericeditor.target.extension.p2.Messages; import org.eclipse.pde.internal.genericeditor.target.extension.p2.P2Fetcher; +import org.eclipse.pde.internal.genericeditor.target.extension.p2.P2Fetcher.RepositoryContent; /** * This class is used to cache the p2 repositories completion information order @@ -48,7 +51,10 @@ private RepositoryCache() { // avoid instantiation } - private static final Map>>> CACHE = new ConcurrentHashMap<>(); + private static record RepositoryMetadata(Map> units, List children) { + } + + private static final Map> CACHE = new ConcurrentHashMap<>(); /** * Fetches information and caches it. @@ -64,19 +70,24 @@ private RepositoryCache() { */ public static Map> fetchP2UnitsFromRepos(List repositories) { if (repositories.size() == 1) { - return getFutureValue(fetchP2DataOfRepo(repositories.get(0))); + return getFutureValue(fetchP2DataOfRepo(repositories.get(0)), RepositoryMetadata::units, Map.of()); } var repos = repositories.stream().map(RepositoryCache::fetchP2DataOfRepo).toList(); // Fetch all repos at once to await pending metadata in parallel - return toSortedMap(repos.stream().map(RepositoryCache::getFutureValue) // + return toSortedMap(repos.stream() + .map(r -> getFutureValue(r, RepositoryMetadata::units, Map.>of())) .map(Map::values).flatMap(Collection::stream).flatMap(List::stream)); } + public static List fetchChildrenOfRepo(String repository) { + return getFutureValue(fetchP2DataOfRepo(repository), RepositoryMetadata::children, List.of()); + } + public static void prefetchP2MetadataOfRepository(String repository) { fetchP2DataOfRepo(repository); } - private static Future>> fetchP2DataOfRepo(String repository) { + private static Future fetchP2DataOfRepo(String repository) { URI location; try { location = new URI(repository); @@ -87,13 +98,15 @@ private static Future>> fetchP2DataOfRepo(String if (f != null && (!f.isDone() || !f.isCompletedExceptionally() && !f.isCancelled())) { return f; // computation is running or has succeeded } - CompletableFuture>> future = new CompletableFuture<>(); + CompletableFuture future = new CompletableFuture<>(); // Fetching P2 repository information is a costly operation // time-wise. Thus it is done in a job. Job job = Job.create(NLS.bind(Messages.UpdateJob_P2DataFetch, repo), m -> { try { - Map> units = toSortedMap(P2Fetcher.fetchAvailableUnits(repo, m)); - future.complete(units); + RepositoryContent content = P2Fetcher.fetchAvailableUnits(repo, m); + Map> units = toSortedMap( + content.units().stream().map(iu -> new VersionedId(iu.getId(), iu.getVersion()))); + future.complete(new RepositoryMetadata(units, content.children())); } catch (Throwable e) { future.completeExceptionally(e); // Only log the failure, don't open an error-dialog. @@ -115,11 +128,12 @@ private static Map> toSortedMap(Stream Collectors.groupingBy(IVersionedId::getId, LinkedHashMap::new, Collectors.toUnmodifiableList())); } - private static Map> getFutureValue(Future>> future) { + private static T getFutureValue(Future future, Function getter, + T defaultValue) { try { - return future.get(); + return getter.apply(future.get()); } catch (Exception e) { // interrupted, canceled or execution failure - return Map.of(); + return defaultValue; } } diff --git a/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/p2/P2Fetcher.java b/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/p2/P2Fetcher.java index cf09b8cf4c..c08e579954 100644 --- a/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/p2/P2Fetcher.java +++ b/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/p2/P2Fetcher.java @@ -14,6 +14,7 @@ package org.eclipse.pde.internal.genericeditor.target.extension.p2; import java.net.URI; +import java.util.List; import java.util.stream.Stream; import org.eclipse.core.runtime.CoreException; @@ -21,11 +22,12 @@ import org.eclipse.core.runtime.SubMonitor; import org.eclipse.equinox.p2.core.IProvisioningAgent; import org.eclipse.equinox.p2.core.IProvisioningAgentProvider; +import org.eclipse.equinox.p2.core.ProvisionException; import org.eclipse.equinox.p2.metadata.IInstallableUnit; -import org.eclipse.equinox.p2.metadata.IVersionedId; -import org.eclipse.equinox.p2.metadata.VersionedId; import org.eclipse.equinox.p2.query.IQueryResult; import org.eclipse.equinox.p2.query.QueryUtil; +import org.eclipse.equinox.p2.repository.ICompositeRepository; +import org.eclipse.equinox.p2.repository.IRepository; import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager; import org.eclipse.pde.internal.genericeditor.target.extension.model.UnitNode; @@ -39,6 +41,9 @@ */ public class P2Fetcher { + public static record RepositoryContent(IQueryResult units, List children) { + } + /** * This methods goes 'online' to make contact with a p2 repo and query it. * @@ -46,7 +51,7 @@ public class P2Fetcher { * URL string of a p2 repository * @return List of available installable unit models. See {@link UnitNode} */ - public static Stream fetchAvailableUnits(URI repositoryLocation, IProgressMonitor monitor) + public static RepositoryContent fetchAvailableUnits(URI repositoryLocation, IProgressMonitor monitor) throws CoreException { SubMonitor subMonitor = SubMonitor.convert(monitor, 31); BundleContext context = FrameworkUtil.getBundle(P2Fetcher.class).getBundleContext(); @@ -57,10 +62,23 @@ public static Stream fetchAvailableUnits(URI repositoryLocation, I IMetadataRepositoryManager manager = agent.getService(IMetadataRepositoryManager.class); IMetadataRepository repository = manager.loadRepository(repositoryLocation, subMonitor.split(30)); IQueryResult allUnits = repository.query(QueryUtil.ALL_UNITS, subMonitor.split(1)); - return allUnits.stream().map(iu -> new VersionedId(iu.getId(), iu.getVersion())); + List children = allChildren(repository, manager).toList(); + return new RepositoryContent(allUnits, children); } finally { context.ungetService(sr); } } + private static Stream allChildren(IRepository repository, IMetadataRepositoryManager manager) { + if (repository instanceof ICompositeRepository composite) { + return composite.getChildren().stream().flatMap(uri -> { + try { // repository should already been cached + return Stream.concat(Stream.of(uri), allChildren(manager.loadRepository(uri, null), manager)); + } catch (ProvisionException e) { + return Stream.of(uri); + } + }); + } + return Stream.empty(); + } }