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.Map; 023import java.util.Optional; 024import java.util.ServiceLoader; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.stream.StreamSupport; 027 028/// Defines both an interface for factories that create [Resource]s and 029/// factory methods for invoking an appropriate factory. 030/// 031@SuppressWarnings("PMD.ImplicitFunctionalInterface") 032public interface ResourceFactory { 033 034 /// The factories as found by the [ServiceLoader]. 035 Map<ClassLoader, List<ResourceFactory>> FACTORIES 036 = new ConcurrentHashMap<>(); 037 038 /// Returns a new resource with the given type, passing the given 039 /// arguments to the constructor of the resource. The implementation 040 /// uses [ServiceLoader] to find a [ResourceFactory] that creates the 041 /// resource, i.e. that does not return `Optional.empty()` when 042 /// [newResource] is called. 043 /// 044 /// The implementation uses [ServiceLoader#load(Class, ClassLoader)] 045 /// with the class loader of the [Project] if provided, or the 046 /// class loader of the current thread otherwise. 047 /// 048 /// @param <T> the generic resource type 049 /// @param type the resource type 050 /// @param project the project 051 /// @param args the additional arguments 052 /// @return the resource 053 /// 054 static <T extends Resource> T create(ResourceType<T> type, 055 Project project, Object... args) { 056 var clsLdr = Optional.ofNullable(project).map(Project::context) 057 .map(BuildContext::classLoader) 058 .orElseGet(() -> Thread.currentThread().getContextClassLoader()); 059 return FACTORIES.computeIfAbsent(clsLdr, 060 cl -> StreamSupport.stream(ServiceLoader.load( 061 ResourceFactory.class, cl).spliterator(), false).toList()) 062 .stream().map(f -> f.newResource(type, project, args)) 063 .filter(Optional::isPresent).map(Optional::get).findFirst() 064 .orElseThrow(() -> new ConfigurationException() 065 .message("No resource factory for %s", type)); 066 } 067 068 /// Short for `create(type, null, args)`. 069 /// 070 /// @param <T> the generic resource type 071 /// @param type the resource type 072 /// @param args the additional arguments 073 /// @return the resource 074 /// 075 static <T extends Resource> T create(ResourceType<T> type, 076 Object... args) { 077 return create(type, null, args); 078 } 079 080 /// Returns a new resource of the given type if the factory instance 081 /// can create it. 082 /// 083 /// @param <T> the generic resource type 084 /// @param type the resource type 085 /// @param project the project 086 /// @param args the additional arguments 087 /// @return the result. `Optional.empty()` if the resource cannot 088 /// be created by the factory instance. 089 /// 090 <T extends Resource> Optional<T> newResource(ResourceType<T> type, 091 Project project, Object... args); 092 093}