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}