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.startup;
020
021import java.net.MalformedURLException;
022import java.net.URL;
023import java.net.URLClassLoader;
024import java.util.Arrays;
025import java.util.Collections;
026import java.util.logging.Logger;
027import java.util.stream.Stream;
028import org.jdrupes.builder.api.BuildException;
029import org.jdrupes.builder.api.FileResource;
030import org.jdrupes.builder.api.FileTree;
031import static org.jdrupes.builder.api.Intend.*;
032import org.jdrupes.builder.api.Launcher;
033import org.jdrupes.builder.api.Project;
034import org.jdrupes.builder.api.Resource;
035import org.jdrupes.builder.api.ResourceRequest;
036import org.jdrupes.builder.api.ResourceType;
037import org.jdrupes.builder.api.RootProject;
038import org.jdrupes.builder.core.LauncherSupport;
039import org.jdrupes.builder.java.ClasspathElement;
040import org.jdrupes.builder.java.CompilationResources;
041import org.jdrupes.builder.mvnrepo.MvnRepoLookup;
042
043/// A default implementation of a [Launcher]. The launcher first builds
044/// the user's JDrupes Builder project, using the JDrupes Builder project
045/// defined by [BootstrapRoot] and [BootstrapBuild]. The default action
046/// of [BootstrapRoot] adds the results from the bootstrap build 
047/// to the classpath and launches the actual JDrupes Builder project.
048///
049public class BootstrapLauncher extends AbstractLauncher {
050
051    /// The log.
052    protected final Logger log = Logger.getLogger(getClass().getName());
053    private RootProject rootProject;
054
055    /// Instantiates a new bootstrap launcher. An instance of the class
056    /// passed as argument is created and used as root project for the
057    /// build.
058    /// 
059    /// Unless the root project is the only project, the root project
060    /// must declare dependencies, else the subprojects won't be
061    /// instantiated.
062    ///
063    /// @param rootPrjCls the root project
064    /// @param args the args
065    ///
066    @SuppressWarnings("PMD.UseVarargs")
067    public BootstrapLauncher(
068            Class<? extends RootProject> rootPrjCls, String[] args) {
069        super(args);
070        unwrapBuildException(() -> {
071            rootProject = LauncherSupport.createProjects(
072                rootPrjCls, Collections.emptyList(), jdbldProps, commandLine);
073
074            // Add build extensions to the build project.
075            var mvnLookup = new MvnRepoLookup();
076            Arrays.asList(jdbldProps
077                .getProperty(BootstrapBuild.BUILD_EXTENSIONS, "").split(","))
078                .stream().map(String::trim).filter(c -> !c.isBlank())
079                .forEach(mvnLookup::resolve);
080            ((Project) rootProject.project(BootstrapBuild.class))
081                .dependency(Expose, mvnLookup);
082            @SuppressWarnings("PMD.UseDiamondOperator")
083            var cpUrls = rootProject.get(
084                new ResourceRequest<ClasspathElement>(
085                    new ResourceType<CompilationResources>() {}))
086                .map(cpe -> {
087                    try {
088                        if (cpe instanceof FileTree tree) {
089                            return tree.root().toFile().toURI().toURL();
090                        }
091                        return ((FileResource) cpe).path().toFile().toURI()
092                            .toURL();
093                    } catch (MalformedURLException e) {
094                        // Cannot happen
095                        throw new BuildException(e);
096                    }
097                }).toArray(URL[]::new);
098            new DirectLauncher(new URLClassLoader(cpUrls,
099                Thread.currentThread().getContextClassLoader()), args);
100            return null;
101        });
102    }
103
104    @Override
105    public <T extends Resource> Stream<T> provide(ResourceRequest<T> request) {
106        return unwrapBuildException(() -> {
107            // Provide requested resource, handling all exceptions here
108            var result = rootProject.get(request).toList();
109            return result.stream();
110        });
111    }
112
113    /// The main method.
114    ///
115    /// @param args the arguments
116    ///
117    public static void main(String[] args) {
118        new BootstrapLauncher(BootstrapRoot.class, args);
119    }
120}