001/*
002 * JDrupes Builder
003 * Copyright (C) 2025, 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 static com.google.common.flogger.StackSize.*;
023import java.util.stream.Stream;
024import org.jdrupes.builder.api.Renamable;
025import org.jdrupes.builder.api.Resource;
026import org.jdrupes.builder.api.ResourceProvider;
027import org.jdrupes.builder.api.ResourceProviderSpi;
028import org.jdrupes.builder.api.ResourceRequest;
029import org.jdrupes.builder.api.ResourceType;
030import org.jdrupes.builder.api.Resources;
031
032/// A base implementation for[ResourceProvider]s.
033///
034public abstract class AbstractProvider implements ResourceProvider {
035
036    private static final FluentLogger logger = FluentLogger.forEnclosingClass();
037    private final DefaultBuildContext context;
038    private String name;
039
040    /// Initializes a new abstract provider.
041    ///
042    public AbstractProvider() {
043        if (DefaultBuildContext.context().isEmpty()) {
044            throw new IllegalStateException(
045                "Creating a provider outside of a project constructor"
046                    + " or a provider invocation");
047        }
048        context = DefaultBuildContext.context().get();
049        // Default name
050        name = getClass().getSimpleName();
051        if (name.isBlank()) {
052            name = "Adapted " + getClass().getSuperclass().getSimpleName();
053        }
054    }
055
056    @Override
057    public String name() {
058        return name;
059    }
060
061    /// Allows derived classes to change the provider's name in their
062    /// implementation of [Renamable#name]. Throws an
063    /// [UnsupportedOperationException] if the derived class does not
064    /// implement [Renamable].
065    ///
066    /// @param name the name
067    /// @return the abstract provider
068    ///
069    protected final AbstractProvider rename(String name) {
070        if (!(this instanceof Renamable)) {
071            throw new UnsupportedOperationException(getClass().getName()
072                + " does not implement " + Renamable.class.getName());
073        }
074        this.name = name;
075        return this;
076    }
077
078    /* default */ ResourceProviderSpi toSpi() {
079        return new ResourceProviderSpi() {
080            @Override
081            public <T extends Resource> Stream<T>
082                    provide(ResourceRequest<T> requested) {
083                if (!DefaultBuildContext.isProviderInvocationAllowed()) {
084                    logger.atWarning().withStackTrace(MEDIUM)
085                        .log("Direct invocation of %s is not allowed", this);
086                }
087                return doProvide(requested);
088            }
089        };
090    }
091
092    /// Invoked by [ResourceProviderSpi#provide] after checking if the
093    /// invocation is allowed.
094    ///
095    /// @param <T> the generic type
096    /// @param request the request for resources
097    /// @return the stream
098    ///
099    protected abstract <T extends Resource> Stream<T>
100            doProvide(ResourceRequest<T> request);
101
102    @Override
103    @SuppressWarnings("PMD.ShortMethodName")
104    public <T extends Resource> ResourceRequest<T>
105            of(ResourceType<? extends T> type) {
106        return new DefaultResourceRequest<>(type);
107    }
108
109    @Override
110    public DefaultBuildContext context() {
111        return context;
112    }
113
114    /// Retrieves the resources as a Vavr stream.
115    ///
116    /// @param <T> the resource type
117    /// @param resources the resources
118    /// @return the stream
119    ///
120    public <T extends Resource> io.vavr.collection.Stream<T>
121            vavrStream(Resources<T> resources) {
122        return io.vavr.collection.Stream.ofAll(resources.stream());
123    }
124
125    @Override
126    public String toString() {
127        if (name().equals(getClass().getSimpleName())) {
128            return getClass().getSimpleName();
129        }
130        return "Provider " + name();
131    }
132}