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.nio.file.Path; 022import java.util.EnumSet; 023import java.util.Set; 024import java.util.function.Function; 025import java.util.stream.Stream; 026import static org.jdrupes.builder.api.Intend.*; 027 028/// [Project]s are used to structure the build configuration. Every 029/// build configuration has a single root project and can have 030/// sub-projects. The root project is the entry point for the build. 031/// The resources provided by the builder are usually provided by the 032/// root project that serves as n entry point to the build configuration. 033/// 034/// Projects are [ResourceProvider]s that obtain resources from related 035/// [ResourceProvider]s. Projects can be thought of as routers for 036/// resources with their behavior depending on the intended usage of the 037/// resources from the related providers. The intended usage is specified 038/// by the [Intend] that attributes the relationship between a project 039/// and its related resource providers. 040/// 041/// ## Attributing relationships to providers 042/// 043/// ### Intend Supply 044/// 045///  046/// 047/// Resources from a provider added with [Intend#Supply] are provided 048/// by the project to entities that depend on the project. [Intend#Supply] 049/// implies that the resources are genuinely generated for the project 050/// (typically by a [Generator] that belongs to the project). 051/// 052/// ### Intend Consume 053/// 054///  055/// 056/// Resources from a provider added with [Intend#Consume] (typically 057/// another project) are only available to a project's generators 058/// through [Project#provided]. 059/// 060/// ### Intend Expose 061/// 062///  063/// 064/// Resources from a provider added with [Intend#Expose] (typically 065/// another project) are provided by the project to entities that 066/// depend on the project. They are also available to a project's 067/// generators through [Project#provided]. 068/// 069/// ### Intend Forward 070/// 071///  072/// 073/// Resources from a provider added with [Intend#Forward] (typically 074/// another project) are provided by the project to entities that 075/// depend on the project. They are not intended to be used by a 076/// project's generators, although these cannot be prevented from 077/// accessing them through [Project#provide]. 078/// 079/// ## Factory methods 080/// 081/// As a convenience, the interface also defines factory methods 082/// for objects used for defining the project. 083/// 084/// @startuml supply-demo.svg 085/// object "project: Project" as project 086/// object "dependant" as dependant 087/// dependant -right-> project 088/// object "generator: Generator" as generator 089/// project *-down-> generator: "<<Supply>>" 090/// @enduml 091/// 092/// @startuml expose-demo.svg 093/// object "project: Project" as project 094/// object "dependant" as dependant 095/// dependant -right-> project 096/// object "providing: Project" as providing 097/// project *-right-> providing: "<<Expose>>" 098/// object "generator: Generator" as generator 099/// project *-down-> generator: " " 100/// generator .up.> project: "provided" 101/// @enduml 102/// 103/// @startuml consume-demo.svg 104/// object "project: Project" as project 105/// object "dependant" as dependant 106/// dependant -right-> project 107/// object "providing: Project" as providing 108/// project *-right-> providing: "<<Consume>>" 109/// object "generator: Generator" as generator 110/// project *-down-> generator: " " 111/// generator .up.> project: "provided" 112/// @enduml 113/// 114/// @startuml forward-demo.svg 115/// object "project: Project" as project 116/// object "dependant" as dependant 117/// dependant -right-> project 118/// object "providing: Project" as providing 119/// project *-right-> providing: "<<Forward>>" 120/// object "generator: Generator" as generator 121/// project *-down-> generator 122/// @enduml 123/// 124public interface Project extends ResourceProvider { 125 126 /// The common project properties. 127 /// 128 @SuppressWarnings("PMD.FieldNamingConventions") 129 enum Properties implements PropertyKey { 130 131 /// The Build directory. Created artifacts should be put there. 132 /// Defaults to [Path] "build". 133 BuildDirectory(Path.of("build")), 134 135 /// The Encoding of files in the project. 136 Encoding("UTF-8"), 137 138 /// The version of the project. Surprisingly, there is no 139 /// agreed upon version type for Java (see e.g. 140 /// ["Version Comparison in Java"](https://www.baeldung.com/java-comparing-versions)). 141 /// Therefore the version is represented as a string with "0.0.0" 142 /// as default. 143 Version("0.0.0"); 144 145 private final Object defaultValue; 146 147 <T> Properties(T defaultValue) { 148 this.defaultValue = defaultValue; 149 } 150 151 @Override 152 @SuppressWarnings("unchecked") 153 public <T> T defaultValue() { 154 return (T)defaultValue; 155 } 156 } 157 158 /// Returns the root project. 159 /// 160 /// @return the project 161 /// 162 RootProject rootProject(); 163 164 /// Returns the instance of the given project class. Projects 165 /// are created lazily by the builder and must be accessed 166 /// via this method. 167 /// 168 /// @param project the requested project's type 169 /// @return the project 170 /// 171 ResourceProvider project(Class<? extends Project> project); 172 173 /// Returns the project's name. 174 /// 175 /// @return the string 176 /// 177 String name(); 178 179 /// Returns the project's directory. 180 /// 181 /// @return the path 182 /// 183 Path directory(); 184 185 /// Returns the build context. 186 /// 187 /// @return the builder configuration 188 /// 189 BuildContext context(); 190 191 /// Returns the directory where the project's [Generator]s should 192 /// create the artifacts. This is short for 193 /// `directory().resolve((Path) get(Properties.BuildDirectory))`. 194 /// 195 /// @return the path 196 /// 197 default Path buildDirectory() { 198 return directory().resolve((Path) get(Properties.BuildDirectory)); 199 } 200 201 /// Adds a provider to the project that generates resources which 202 /// are then provided by the project. This is short for 203 /// `dependency(provider, Intend.Provide)`. 204 /// 205 /// @param generator the provider 206 /// @return the project 207 /// 208 Project generator(Generator generator); 209 210 /// Uses the supplier to create a provider, passing this project as 211 /// argument and adds the result as a generator to this project. This 212 /// is a convenience method to add a provider to the project by writing 213 /// (in a project's constructor): 214 /// 215 /// ```java 216 /// generator(Provider::new); 217 /// ``` 218 /// instead of: 219 /// 220 /// ```java 221 /// generator(new Provider(this)); 222 /// ``` 223 /// 224 /// @param <T> the generic type 225 /// @param supplier the supplier 226 /// @return the project for method chaining 227 /// 228 default <T extends Generator> T generator(Function<Project, T> supplier) { 229 var provider = supplier.apply(this); 230 generator(provider); 231 return provider; 232 } 233 234 /// Adds a provider that contributes resources to the project with 235 /// the given intended usage. 236 /// 237 /// While this could be used to add a [Generator] to the project 238 /// as a provider with [Intend#Supply], it is recommended to use 239 /// one of the "generator" methods for better readability. 240 /// 241 /// @param intend the dependency type 242 /// @param provider the provider 243 /// @return the project for method chaining 244 /// @see generator(Generator) 245 /// @see generator(Function) 246 /// 247 Project dependency(Intend intend, ResourceProvider provider); 248 249 /// Uses the supplier to create a provider, passing this project as 250 /// argument and adds the result as a dependency to this project. This 251 /// is a convenience method to add a provider to the project by writing 252 /// (in a project's constructor): 253 /// 254 /// ```java 255 /// dependency(intend, Provider::new); 256 /// ``` 257 /// instead of: 258 /// 259 /// ```java 260 /// dependency(intend, new Provider(this)); 261 /// ``` 262 /// 263 /// @param <T> the generic type 264 /// @param intend the intend 265 /// @param supplier the supplier 266 /// @return the project for method chaining 267 /// 268 default <T extends ResourceProvider> T dependency(Intend intend, 269 Function<Project, T> supplier) { 270 var provider = supplier.apply(this); 271 dependency(intend, provider); 272 return provider; 273 } 274 275 /// Returns the providers that have been added with one of the given 276 /// intended usages as [Stream]. The stream may only be terminated 277 /// after all projects have been created. 278 /// 279 /// @param intends the intends 280 /// @return the stream 281 /// 282 Stream<ResourceProvider> providers(Set<Intend> intends); 283 284 /// Returns the providers that have been added with the given 285 /// intended usage as [Stream]. This is short for 286 /// `providers(Set.of(intend))`. 287 /// 288 /// @param intend the intend 289 /// @param intends more intends 290 /// @return the stream 291 /// 292 default Stream<ResourceProvider> providers( 293 Intend intend, Intend... intends) { 294 return providers(EnumSet.of(intend, intends)); 295 } 296 297 /// Invoke the given providers for the given request and return the 298 /// resulting stream. This method terminates the stream of providers. 299 /// 300 /// @param <T> the generic type 301 /// @param providers the providers 302 /// @param request the request 303 /// @return the stream 304 /// 305 <T extends Resource> Stream<T> getFrom( 306 Stream<ResourceProvider> providers, ResourceRequest<T> request); 307 308 /// Returns all resources that are provided for the given request 309 /// by providers associated with [Intend#Consume] or [Intend#Expose]. 310 /// 311 /// @param <T> the requested type 312 /// @param requested the requested 313 /// @return the provided resources 314 /// 315 default <T extends Resource> Stream<T> 316 provided(ResourceRequest<T> requested) { 317 return getFrom(providers(Consume, Expose), requested); 318 } 319 320 /// Returns all resources that are provided for the given request 321 /// by providers associated with [Intend#Supply]. 322 /// 323 /// @param <T> the requested type 324 /// @param requested the requested 325 /// @return the provided resources 326 /// 327 default <T extends Resource> Stream<T> 328 supplied(ResourceRequest<T> requested) { 329 return getFrom(providers(Supply), requested); 330 } 331 332 /// Short for `directory().relativize(other)`. 333 /// 334 /// @param other the other path 335 /// @return the relativized path 336 /// 337 default Path relativize(Path other) { 338 return directory().relativize(other); 339 } 340 341 /// Sets the given property to the given value. 342 /// 343 /// Regrettably, there is no way to enforce at compile time that the 344 /// type of the value passed to `set` matches the type of the property. 345 /// An implementation must check this at runtime by verifying that the 346 /// given value is assignable to the default value. 347 /// 348 /// @param property the property 349 /// @param value the value 350 /// @return the project 351 /// 352 Project set(PropertyKey property, Object value); 353 354 /// Returns value of the given property of the project. If the 355 /// property is not set, the parent project's value is returned. 356 /// If neither is set, the property's default value is returned. 357 /// 358 /// @param <T> the generic type 359 /// @param property the property 360 /// @return the t 361 /// 362 <T> T get(PropertyKey property); 363 364 /// Returns resources provided by the project. Short for 365 /// `context().get(this, request)`. 366 /// 367 /// @param <T> the generic type 368 /// @param request the request 369 /// @return the stream 370 /// 371 default <T extends Resource> Stream<T> get(ResourceRequest<T> request) { 372 return context().get(this, request); 373 } 374 375 /// "Syntactic sugar" that allows to obtain resources from a provider 376 /// with `from(provider).get(resourceRequest)` instead of 377 /// `context().get(provider, resourceRequest)`. 378 /// 379 /// @param provider the provider 380 /// @return the stream of resources 381 /// 382 default FromHelper from(ResourceProvider provider) { 383 return new FromHelper(context(), provider); 384 } 385 386 /// Returns a new resource with the given type. Short for invoking 387 /// [ResourceFactory#create] with the current project as first argument 388 /// and the given arguments appended. 389 /// 390 /// @param <T> the generic type 391 /// @param type the type 392 /// @param args the args 393 /// @return the t 394 /// 395 default <T extends Resource> T newResource(ResourceType<T> type, 396 Object... args) { 397 return ResourceFactory.create(type, this, args); 398 } 399}