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