001/*
002 * JDrupes Builder
003 * Copyright (C) 2025, 2026 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.Optional;
026import java.util.Set;
027import java.util.function.Function;
028
029/// [Project]s are used to structure the build configuration. Every build
030/// configuration has a single root project and may contain sub-projects.
031/// The root project serves as the entry point for the build. Resources
032/// provided by the builder are typically provided by the root project,
033/// which acts as the central access point of the build configuration.
034///
035/// Projects are [ResourceProvider]s that obtain resources from related
036/// [ResourceProvider]s. Conceptually, a project acts as a router for
037/// requests and resources, with its behavior depending on the intended
038/// usage of the resources obtained from the providers registered as
039/// dependencies. The intended usage is indicated by the [Intent] that
040/// attributes the relationship between a project and its related
041/// resource providers.
042///
043/// ## Attributing relationships to providers
044///
045/// ### Intent Supply
046///
047/// ![Intent Supply](supply-demo.svg)
048///
049/// Resources from a provider added with [Intent#Supply] are made available
050/// by the project to entities that depend on it. [Intent#Supply] implies
051/// that the resources are generated specifically for the project,
052/// typically by a [Generator] that belongs to the project.
053///
054/// ### Intent Consume and Reveal
055///
056/// ![Intent Consume](consume-demo.svg)
057///
058/// Resources from a provider added with [Intent#Consume] or
059/// [Intent#Reveal] are typically used only by a project's generators.
060/// If a provider is added with [Intent#Reveal], its resources are also
061/// provided by the project when they are explicitly included in a
062/// request.
063///
064/// ### Intent Expose
065///
066/// ![Intent Expose](expose-demo.svg)
067///
068/// Resources from a provider added with [Intent#Expose] (typically
069/// another project) are used by a project's generators and are also
070/// provided by the project to entities that depend on it.
071///
072/// ### Intent Forward
073///
074/// ![Intent Forward](forward-demo.svg)
075///
076/// Resources from a provider added with [Intent#Forward] (typically
077/// another project) are provided by the project to entities that depend
078/// on it. These resources are not intended to be used by the project's
079/// generators. This cannot be enforced, however, as generators may still
080/// access them via [Project#providers()].
081///
082/// ## Behavior as resource provider
083///
084/// In its role as a [ResourceProvider], a [Project]
085/// [provides resources][ResourceProvider#resources] obtained from its
086/// dependencies. A [ResourceRequest] controls to which dependencies a
087/// request is forwarded by including the respective [Intent]s in the
088/// set returned by [ResourceRequest#uses].
089///
090/// Concepts from other build tools, such as Gradle’s dependency
091/// configurations, can be mapped to resource requests for classpath
092/// elements using sets of [Intent]s as follows:
093///
094/// <table class="simple-table">
095///   <thead>
096///     <tr>
097///       <th style="white-space: nowrap;">Intent \ Config.</th>
098///       <th>Api</th>
099///       <th>Implementation</th>
100///       <th>Compile only</th>
101///       <th>Runtime only</th>
102///     </tr>
103///   </thead>
104///   <tbody>
105///     <tr><td>Supply</td><td>X</td><td>X</td><td></td><td></td></tr>
106///     <tr><td>Consume</td><td></td><td></td><td>X</td><td></td></tr>
107///     <tr><td>Reveal</td><td></td><td>X</td><td></td><td></td></tr>
108///     <tr><td>Expose</td><td>X</td><td>X</td><td></td><td></td></tr>
109///     <tr><td>Forward</td><td></td><td></td><td></td><td>X</td></tr>
110///   </tbody>
111/// </table>
112///
113/// To ensure consistent results, a project adjusts a request before
114/// forwarding it to a dependency of type [Project] (typically a
115/// sub-project). If the request uses `Consume` or `Expose`, `Consume`
116/// is removed and `Expose` and `Supply` are added. The reason is that,
117/// regardless of how a sub-project contributes to another project, the
118/// resources it contributes are always those that are part of its API.
119///
120/// To avoid misuse of [Intent]s, all intents are removed from a request
121/// before it is forwarded to a dependency that is not a project.
122///
123/// ## Factory method for resources
124///
125/// As a convenience, this interface also defines a shortcut for creating
126/// [Resource]s.
127///
128/// @startuml supply-demo.svg
129/// object "project: Project" as project
130/// object "dependant" as dependant
131/// dependant -right-> project
132/// object "generator: Generator" as generator
133/// project *-down-> generator: "<<Supply>>"
134/// @enduml
135///
136/// @startuml expose-demo.svg
137/// object "project: Project" as project
138/// object "dependant" as dependant
139/// dependant -right-> project
140/// object "providing: Project" as providing
141/// project *-right-> providing: "<<Expose>>"
142/// object "generator: Generator" as generator
143/// project *-down-> generator: "   "
144/// generator .up.> project: "provided"
145/// @enduml
146///
147/// @startuml consume-demo.svg
148/// object "project: Project" as project
149/// object "dependant" as dependant
150/// dependant -right-> project
151/// object "providing: Project" as providing
152/// project *-right-> providing: "<<Consume>>"
153/// object "generator: Generator" as generator
154/// project *-down-> generator: "   "
155/// generator .up.> project: "provided"
156/// @enduml
157///
158/// @startuml forward-demo.svg
159/// object "project: Project" as project
160/// object "dependant" as dependant
161/// dependant -right-> project
162/// object "providing: Project" as providing
163/// project *-right-> providing: "<<Forward>>"
164/// object "generator: Generator" as generator
165/// project *-down-> generator
166/// @enduml
167///
168public interface Project extends ResourceProvider {
169
170    /// The common project properties.
171    ///
172    @SuppressWarnings("PMD.FieldNamingConventions")
173    enum Properties implements PropertyKey {
174
175        /// The Build directory. Created artifacts should be put there.
176        /// Defaults to [Path] "build".
177        BuildDirectory(Path.of("build")),
178        
179        /// The Encoding of files in the project.
180        Encoding("UTF-8"),
181        
182        /// The version of the project. Surprisingly, there is no
183        /// agreed upon version type for Java (see e.g. 
184        /// ["Version Comparison in Java"](https://www.baeldung.com/java-comparing-versions)).
185        /// Therefore the version is represented as a string with "0.0.0"
186        /// as default.
187        Version("0.0.0");
188
189        private final Object defaultValue;
190
191        <T> Properties(T defaultValue) {
192            this.defaultValue = defaultValue;
193        }
194
195        @Override
196        @SuppressWarnings("unchecked")
197        public <T> T defaultValue() {
198            return (T)defaultValue;
199        }
200    }
201
202    /// Returns the root project.
203    ///
204    /// @return the project
205    ///
206    RootProject rootProject();
207
208    /// Returns the instance of the given project class. Projects
209    /// are created lazily by the builder and must be accessed
210    /// via this method.
211    ///
212    /// @param project the requested project's type
213    /// @return the project
214    ///
215    Project project(Class<? extends Project> project);
216
217    /// Returns the parent project. The root project has no parent.
218    ///
219    /// @return the parent project
220    ///
221    Optional<Project> parentProject();
222    
223    /// Returns the project's directory.
224    ///
225    /// @return the path
226    ///
227    Path directory();
228
229    /// Returns the project's name and its directory in parentheses.
230    /// Appending the directory is omitted if it is the same as the name.
231    ///
232    /// @return the string
233    ///
234    default String nameWithDirectory() {
235        StringBuilder result = new StringBuilder(name());
236        if (directory() != null) {
237            var relDir = rootProject().directory().relativize(directory());
238            if (!relDir.toString().equals(name())
239                    && !relDir.toString().isEmpty()) {
240                result.append(" (in ").append(relDir).append(')');
241            }
242        }
243        return result.toString();
244        
245    }
246    
247    /// Returns the directory where the project's [Generator]s should
248    /// create the artifacts. This is short for 
249    /// `directory().resolve((Path) get(Properties.BuildDirectory))`.
250    ///
251    /// @return the path
252    ///
253    default Path buildDirectory() {
254        return directory().resolve((Path) get(Properties.BuildDirectory));
255    }
256
257    /// Adds a provider to the project that generates resources which
258    /// are then provided by the project. For "normal" projects, the
259    /// generated resources are assumed to be provided to dependents of
260    /// the project, so the invocation is shorthand for  
261    /// `dependency(Intent.Supply, generator)`.
262    ///
263    /// For projects that implement [MergedTestProject], generated resources
264    /// are usually intended to be used by the project itself only, so
265    /// the invocation is short for `dependency(Intent.Consume, generator)`.
266    ///
267    /// @param generator the provider
268    /// @return the project
269    ///
270    Project generator(Generator generator);
271
272    /// Uses the supplier to create a provider, passing this project as 
273    /// argument and adds the result as a generator to this project. This
274    /// is a convenience method to add a provider to the project by writing
275    /// (in a project's constructor):
276    /// 
277    /// ```java
278    /// generator(Provider::new);
279    /// ```
280    /// instead of:
281    /// 
282    /// ```java
283    /// generator(new Provider(this));
284    /// ```
285    ///
286    /// @param <T> the generic type
287    /// @param supplier the supplier
288    /// @return the project for method chaining
289    ///
290    default <T extends Generator> T generator(Function<Project, T> supplier) {
291        var provider = supplier.apply(this);
292        generator(provider);
293        return provider;
294    }
295
296    /// Adds a provider that contributes resources to the project with
297    /// the given intended usage.
298    ///
299    /// While this could be used to add a [Generator] to the project
300    /// as a provider with [Intent#Supply], it is recommended to use
301    /// one of the "generator" methods for better readability.
302    ///
303    /// @param intent the dependency type
304    /// @param provider the provider
305    /// @return the project for method chaining
306    /// @see generator(Generator)
307    /// @see generator(Function)
308    ///
309    Project dependency(Intent intent, ResourceProvider provider);
310
311    /// Uses the supplier to create a provider, passing this project as 
312    /// argument and adds the result as a dependency to this project. This
313    /// is a convenience method to add a provider to the project by writing
314    /// (in a project's constructor):
315    /// 
316    /// ```java
317    /// dependency(intent, Provider::new);
318    /// ```
319    /// instead of:
320    /// 
321    /// ```java
322    /// dependency(intent, new Provider(this));
323    /// ```
324    ///
325    /// @param <T> the generic type
326    /// @param intent the intent
327    /// @param supplier the supplier
328    /// @return the project for method chaining
329    ///
330    default <T extends ResourceProvider> T dependency(Intent intent,
331            Function<Project, T> supplier) {
332        var provider = supplier.apply(this);
333        dependency(intent, provider);
334        return provider;
335    }
336    
337    /// Return a provider selection without any restrictions.
338    ///
339    /// @return the provider selection
340    ///
341    ProviderSelection providers();
342    
343    /// Return a provider selection that is restricted to the given intents.
344    ///
345    /// @param intents the intents
346    /// @return the provider selection
347    ///
348    ProviderSelection providers(Set<Intent> intents);
349    
350    /// Return a provider selection that is restricted to the given intents.
351    ///
352    /// @param intent the intent
353    /// @param intents the intents
354    /// @return the provider selection
355    ///
356    default ProviderSelection providers(Intent intent, Intent... intents) {
357        return providers(EnumSet.of(intent, intents));
358    }
359
360    /// Short for `directory().relativize(other)`.
361    ///
362    /// @param other the other path
363    /// @return the relativized path
364    ///
365    default Path relativize(Path other) {
366        return directory().relativize(other);
367    }
368
369    /// Sets the given property to the given value.
370    /// 
371    /// Regrettably, there is no way to enforce at compile time that the
372    /// type of the value passed to `set` matches the type of the property.
373    /// An implementation must check this at runtime by verifying that the
374    /// given value is assignable to the default value. 
375    ///
376    /// @param property the property
377    /// @param value the value
378    /// @return the project
379    ///
380    Project set(PropertyKey property, Object value);
381    
382    /// Returns value of the given property of the project. If the
383    /// property is not set, the parent project's value is returned.
384    /// If neither is set, the property's default value is returned.
385    ///
386    /// @param <T> the property type
387    /// @param property the property
388    /// @return the property
389    ///
390    <T> T get(PropertyKey property);
391   
392    /// Convenience method for reading the content of a file into a
393    /// [String]. The path is resolved against the project's directory.
394    ///
395    /// @param path the path
396    /// @return the string
397    ///
398    default String readString(Path path) {
399        try {
400            return Files.readString(directory().resolve(path));
401        } catch (IOException e) {
402            throw new BuildException().from(this).cause(e);
403        }
404    }
405
406    
407}