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/// ![Intend Supply](supply-demo.svg)
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/// ![Intend Consume](consume-demo.svg)
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/// ![Intend Expose](expose-demo.svg)
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/// ![Intend Forward](forward-demo.svg)
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}