001/* 002 * JDrupes Builder 003 * Copyright (C) 2026 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.core; 020 021import com.google.common.flogger.FluentLogger; 022import java.util.EnumSet; 023import java.util.Set; 024import java.util.concurrent.atomic.AtomicReference; 025import java.util.function.Consumer; 026import java.util.function.Predicate; 027import java.util.stream.Stream; 028import org.jdrupes.builder.api.Intent; 029import static org.jdrupes.builder.api.Intent.Consume; 030import static org.jdrupes.builder.api.Intent.Expose; 031import static org.jdrupes.builder.api.Intent.Supply; 032import org.jdrupes.builder.api.Project; 033import org.jdrupes.builder.api.ProviderSelection; 034import org.jdrupes.builder.api.Resource; 035import org.jdrupes.builder.api.ResourceProvider; 036import org.jdrupes.builder.api.ResourceRequest; 037 038/// The Class DefaultBoundResourceQuery. 039/// 040public class DefaultProviderSelection implements ProviderSelection { 041 042 private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 043 private final AbstractProject project; 044 private final Set<Intent> preSelected; 045 private Predicate<ResourceProvider> filter = _ -> true; 046 private Consumer<ResourceProvider> onBeforeUse = _ -> { 047 }; 048 049 /* default */ DefaultProviderSelection(AbstractProject project) { 050 this.project = project; 051 this.preSelected = null; 052 } 053 054 /* default */ DefaultProviderSelection(AbstractProject project, 055 Set<Intent> intends) { 056 this.project = project; 057 this.preSelected = intends; 058 } 059 060 @Override 061 public ProviderSelection filter(Predicate<ResourceProvider> filter) { 062 this.filter = this.filter.and(filter); 063 return this; 064 } 065 066 @Override 067 public DefaultProviderSelection without(ResourceProvider provider) { 068 filter = filter.and(p -> !p.equals(provider)); 069 return this; 070 } 071 072 @Override 073 public DefaultProviderSelection 074 without(Class<? extends ResourceProvider> providerType) { 075 filter = filter.and(p -> !providerType.isAssignableFrom(p.getClass())); 076 return this; 077 } 078 079 @Override 080 public DefaultProviderSelection 081 onBeforeUse(Consumer<ResourceProvider> hook) { 082 this.onBeforeUse = hook; 083 return this; 084 } 085 086 @Override 087 public Stream<ResourceProvider> select(Set<Intent> intents) { 088 if (preSelected != null) { 089 if (!intents.isEmpty()) { 090 throw new IllegalArgumentException( 091 "Duplicates selection of intents."); 092 } 093 intents = preSelected; 094 } 095 return project.dependencies(intents).filter(filter) 096 .peek(onBeforeUse::accept); 097 } 098 099 @Override 100 public <T extends Resource> Stream<T> 101 resources(ResourceRequest<T> request) { 102 AtomicReference<ResourceRequest<T>> projectRequest 103 = new AtomicReference<>(); 104 final var snapshot = ScopedValueContext.snapshot(); 105 // Refrain from making this a parallel stream. Project.resoures 106 // simply forwards and other ResourceProvider.resoures will 107 // return a FutureStream. Making this a parallel stream should 108 // gain little or nothing but may result in deadlocks. 109 return select(request.uses()).map(p -> { 110 if (p instanceof Project) { 111 logger.atFinest() 112 .log("%s forwards % to %s", project, request, p); 113 return snapshot.call(() -> project.context().resources(p, 114 projectRequest.updateAndGet( 115 r -> r != null ? r : forwardedRequest(request)))); 116 } 117 return snapshot.call(() -> project.context().resources(p, request)); 118 }).flatMap(s -> s); 119 } 120 121 private <T extends Resource> ResourceRequest<T> 122 forwardedRequest(ResourceRequest<T> requested) { 123 Set<Intent> uses = preSelected != null ? preSelected : requested.uses(); 124 Set<Intent> mapped = EnumSet.copyOf(uses); 125 if (uses.stream().filter( 126 EnumSet.of(Consume, Expose)::contains).findAny().isPresent()) { 127 mapped.remove(Consume); 128 mapped.addAll(EnumSet.of(Supply, Expose)); 129 } 130 return requested.using(mapped); 131 } 132}