001/*
002 * JDrupes Builder
003 * Copyright (C) 2025 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.api;
020
021import java.util.List;
022import java.util.Optional;
023import java.util.ServiceLoader;
024import java.util.stream.StreamSupport;
025
026/// Defines both an interface for factories that create [Resource]s and
027/// factory methods for invoking an appropriate factory.
028///
029@SuppressWarnings("PMD.ImplicitFunctionalInterface")
030public interface ResourceFactory {
031
032    /// The factories as found by the [ServiceLoader].
033    List<ResourceFactory> FACTORIES = StreamSupport.stream(
034        ServiceLoader.load(ResourceFactory.class).spliterator(), false)
035        .toList();
036
037    /// Returns a new resource with the given type, passing the given
038    /// arguments to the constructor of the resource. The implementation
039    /// uses [ServiceLoader] to find a [ResourceFactory] that creates the
040    /// resource, i.e. that does not return `Optional.empty()` when
041    /// [newResource] is called.
042    ///
043    /// @param <T> the generic resource type
044    /// @param type the resource type
045    /// @param project the project
046    /// @param args the additional arguments
047    /// @return the resource
048    ///
049    static <T extends Resource> T create(ResourceType<T> type,
050            Project project, Object... args) {
051        return FACTORIES.stream().map(f -> f.newResource(type, project, args))
052            .filter(Optional::isPresent).map(Optional::get).findFirst()
053            .orElseThrow(() -> new ConfigurationException()
054                .message("No resource factory for %s", type));
055    }
056
057    /// Short for `create(type, null, args)`.
058    ///
059    /// @param <T> the generic resource type
060    /// @param type the resource type
061    /// @param args the additional arguments
062    /// @return the resource
063    ///
064    static <T extends Resource> T create(ResourceType<T> type,
065            Object... args) {
066        return create(type, null, args);
067    }
068
069    /// Returns a new resource of the given type if the factory instance
070    /// can create it.
071    ///
072    /// @param <T> the generic resource type
073    /// @param type the resource type
074    /// @param project the project
075    /// @param args the additional arguments
076    /// @return the result. `Optional.empty()` if the resource cannot
077    /// be created by the factory instance.
078    ///
079    <T extends Resource> Optional<T> newResource(ResourceType<T> type,
080            Project project, Object... args);
081
082}