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.core; 020 021import java.lang.reflect.Method; 022import java.lang.reflect.Proxy; 023import java.nio.file.Path; 024import java.util.Arrays; 025import java.util.Optional; 026import static java.util.function.Predicate.not; 027import java.util.function.Supplier; 028import java.util.stream.Collectors; 029import org.jdrupes.builder.api.ExecResult; 030import org.jdrupes.builder.api.FileResource; 031import org.jdrupes.builder.api.FileTree; 032import org.jdrupes.builder.api.Project; 033import org.jdrupes.builder.api.Proxyable; 034import org.jdrupes.builder.api.Resource; 035import org.jdrupes.builder.api.ResourceFactory; 036import org.jdrupes.builder.api.ResourceProvider; 037import org.jdrupes.builder.api.ResourceType; 038import org.jdrupes.builder.api.Resources; 039import org.jdrupes.builder.api.TestResult; 040 041/// A factory for creating the Core resource objects. 042/// 043public class CoreResourceFactory implements ResourceFactory { 044 045 /// Instantiates a new core resource factory. 046 /// 047 public CoreResourceFactory() { 048 // Make javadoc happy. 049 } 050 051 /// Creates a narrowed resource. Given a wanted interface type, an 052 /// implemented interface type and a supplier that returns an 053 /// instance of the implemented type, returns an instance of the 054 /// wanted type if possible. 055 /// 056 /// Returning an implementation of the wanted type is possible if 057 /// the following conditions are met: 058 /// 059 /// 1. The wanted type has no superclass (i.e. is an interface). 060 /// 061 /// 2. The wanted type is a subclass of the implemented type. 062 /// 063 /// 3. The wanted type does not add any methods to the 064 /// implemented type. 065 /// 066 /// The implementation uses a dynamic proxy to wrap the 067 /// implemented instance together with a [ForwardingHandler], 068 /// that simply forwards all invocations to the proxied object 069 /// (hence the requirement that the wanted type does not add 070 /// any methods to the implemented type). 071 /// 072 /// @param <T> the wanted type 073 /// @param <I> the implemented (available) type 074 /// @param wanted the wanted 075 /// @param implemented the implemented interface 076 /// @param supplier the supplier of a class that implements I 077 /// @return an instance if possible 078 /// 079 @SuppressWarnings("unchecked") 080 public static <T extends Resource, I extends Resource> Optional<T> 081 createNarrowed(ResourceType<T> wanted, Class<I> implemented, 082 Supplier<? extends I> supplier) { 083 if (implemented.isAssignableFrom(wanted.rawType()) 084 // we now know that T extends I 085 && wanted.rawType().getSuperclass() == null 086 && !addsMethod(implemented, 087 (Class<? extends I>) wanted.rawType())) { 088 return Optional.of(narrow(wanted, supplier.get())); 089 } 090 return Optional.empty(); 091 } 092 093 /// Checks if the derived interface adds any methods to the 094 /// base interface. 095 /// 096 /// @param <T> the generic type 097 /// @param base the base 098 /// @param derived the derived 099 /// @return true, if successful 100 /// 101 public static <T> boolean addsMethod( 102 Class<T> base, Class<? extends T> derived) { 103 var baseItfs = ResourceType.getAllInterfaces(base) 104 .collect(Collectors.toSet()); 105 return ResourceType.getAllInterfaces(derived) 106 .filter(not(baseItfs::contains)) 107 .filter(itf -> Arrays.stream(itf.getDeclaredMethods()) 108 .filter(not(Method::isDefault)).findAny().isPresent()) 109 .findAny().isPresent(); 110 } 111 112 @SuppressWarnings({ "unchecked" }) 113 private static <T extends Resource> T narrow(ResourceType<T> type, 114 Resource instance) { 115 return (T) Proxy.newProxyInstance(type.rawType().getClassLoader(), 116 new Class<?>[] { type.rawType(), Proxyable.class }, 117 new ForwardingHandler(instance)); 118 } 119 120 /// New resource. 121 /// 122 /// @param <T> the generic type 123 /// @param type the type 124 /// @param project the project 125 /// @param args the args 126 /// @return the optional 127 /// 128 @Override 129 @SuppressWarnings({ "unchecked" }) 130 public <T extends Resource> Optional<T> newResource(ResourceType<T> type, 131 Project project, Object... args) { 132 // ? extends FileResource 133 var candidate = createNarrowed(type, FileResource.class, 134 () -> new DefaultFileResource( 135 (ResourceType<? extends FileResource>) type, (Path) args[0])); 136 if (candidate.isPresent()) { 137 return candidate; 138 } 139 140 // ? extends TestResult 141 candidate = createNarrowed(type, TestResult.class, 142 () -> new DefaultTestResult(project, (ResourceProvider) args[0], 143 (String) args[1], (long) args[2], (long) args[3])); 144 if (candidate.isPresent()) { 145 return candidate; 146 } 147 148 // ? extends ExecResult 149 candidate = createNarrowed(type, ExecResult.class, 150 () -> new DefaultExecResult((ResourceProvider) args[0], 151 (String) args[1], (int) args[2])); 152 if (candidate.isPresent()) { 153 return candidate; 154 } 155 156 // ? extends Resources 157 candidate = createNarrowed(type, Resources.class, 158 () -> new DefaultResources<>( 159 (ResourceType<? extends Resources<?>>) type)); 160 if (candidate.isPresent()) { 161 return candidate; 162 } 163 164 // ? extends FileTree 165 candidate = createNarrowed(type, FileTree.class, 166 () -> new DefaultFileTree<>( 167 (ResourceType<? extends FileTree<?>>) type, 168 project, (Path) args[0], (String) args[1])); 169 if (candidate.isPresent()) { 170 return candidate; 171 } 172 173 // Finally, try resource 174 return createNarrowed(type, Resource.class, 175 () -> new ResourceObject((ResourceType<?>) type) {}); 176 } 177 178}