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        var providerStream = select(request.uses()).map(p -> {
105            if (p instanceof Project) {
106                logger.atFinest()
107                    .log("%s forwards % to %s", project, request, p);
108                return project.context().resources(p,
109                    projectRequest.updateAndGet(
110                        r -> r != null ? r : forwardedRequest(request)));
111            }
112            return project.context().resources(p, request);
113        });
114        if (project.providersUnlocked()) {
115            // If accessing providers is allowed, get the providers
116            // eagerly to trigger evaluation. Resources are still
117            // obtained lazily.
118            providerStream = providerStream.toList().stream();
119        }
120        return providerStream.flatMap(s -> s);
121    }
122
123    private <T extends Resource> ResourceRequest<T>
124            forwardedRequest(ResourceRequest<T> requested) {
125        Set<Intent> uses = preSelected != null ? preSelected : requested.uses();
126        Set<Intent> mapped = EnumSet.copyOf(uses);
127        if (uses.stream().filter(
128            EnumSet.of(Consume, Expose)::contains).findAny().isPresent()) {
129            mapped.remove(Consume);
130            mapped.addAll(EnumSet.of(Supply, Expose));
131        }
132        return requested.using(mapped);
133    }
134}