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