001/*
002 * JDrupes Builder
003 * Copyright (C) 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.distribution.internal;
020
021import com.google.common.flogger.FluentLogger;
022import java.io.ByteArrayOutputStream;
023import java.io.IOException;
024import java.nio.file.Files;
025import java.nio.file.Path;
026import java.util.ArrayList;
027import java.util.List;
028import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
029import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
030import org.jdrupes.builder.api.BuildException;
031import org.jdrupes.builder.api.FileResource;
032import org.jdrupes.builder.api.FileTree;
033import static org.jdrupes.builder.api.ResourceType.BaseFileTreeType;
034import org.jdrupes.builder.api.Resources;
035import org.jdrupes.builder.distribution.ApplicationTarFile;
036import org.jdrupes.builder.java.ClasspathElement;
037import static org.jdrupes.builder.java.JavaTypes.JarFileType;
038
039/// A distribution builder that builds a TAR file.
040///
041public class TarDistributionBuilder extends DistributionBuilder {
042    private static final FluentLogger logger = FluentLogger.forEnclosingClass();
043
044    /// Initializes a new tar distribution builder.
045    ///
046    public TarDistributionBuilder() {
047        // Make javadoc happy
048    }
049
050    /// Builds the tar.
051    ///
052    /// @param file the file
053    /// @param config the config
054    /// @param cpElements the cp elements
055    ///
056    @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", "PMD.NcssCount",
057        "PMD.AvoidUsingOctalValues" })
058    public void build(ApplicationTarFile file,
059            ApplicationConfigurationData config,
060            Resources<ClasspathElement> cpElements) {
061        try (var tos = new TarArchiveOutputStream(
062            Files.newOutputStream(file.path()))) {
063            List<String> distClassPath = new ArrayList<>();
064            int classTreeCount = 0;
065            for (var cpe : cpElements.get()) {
066                if (JarFileType.isAssignableFrom(cpe.type())) {
067                    var entryName = Path.of("lib").resolve(
068                        cpe.toPath().getFileName()).toString();
069                    distClassPath.add("$APP_HOME/" + entryName);
070                    tos.putArchiveEntry(
071                        new TarArchiveEntry(cpe.toPath(), entryName));
072                    Files.copy(cpe.toPath(), tos);
073                    tos.closeArchiveEntry();
074                    continue;
075                }
076                if (BaseFileTreeType.isAssignableFrom(cpe.type())) {
077                    @SuppressWarnings("unchecked")
078                    FileTree<FileResource> fileTree
079                        = (FileTree<FileResource>) cpe;
080                    if (fileTree.isEmpty()) {
081                        continue;
082                    }
083                    classTreeCount += 1;
084                    addClassTree(tos, distClassPath, classTreeCount, fileTree);
085                    continue;
086                }
087                logger.atWarning().log("Cannot add %s to distribution", cpe);
088            }
089
090            // Add generated scripts, first Unix
091            var model = buildModel(config, distClassPath);
092            var bos = new ByteArrayOutputStream();
093            addUnixScript(bos, model);
094            byte[] data = bos.toByteArray();
095            var entry = new TarArchiveEntry(
096                Path.of("bin", config.executableName()).toString());
097            entry.setSize(data.length);
098            entry.setMode(0755);
099            tos.putArchiveEntry(entry);
100            tos.write(data);
101            tos.closeArchiveEntry();
102
103            // Now Windows
104            bos.reset();
105            addWindowsBat(bos, model);
106            data = bos.toByteArray();
107            entry = new TarArchiveEntry(Path.of("bin",
108                config.executableName() + ".bat").toString());
109            entry.setSize(data.length);
110            entry.setMode(0755);
111            tos.putArchiveEntry(entry);
112            tos.write(data);
113            tos.closeArchiveEntry();
114        } catch (IOException e) {
115            throw new BuildException().cause(e);
116        }
117    }
118
119    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
120    private void addClassTree(TarArchiveOutputStream tos,
121            List<String> distClassPath, int classTreeCount,
122            FileTree<FileResource> tree) throws IOException {
123        var treeDir = Path.of("classes")
124            .resolve(Integer.toString(classTreeCount));
125        distClassPath.add("$APP_HOME/" + treeDir.toString());
126        var iter = tree.entries().iterator();
127        while (iter.hasNext()) {
128            var entry = iter.next();
129            tos.putArchiveEntry(
130                new TarArchiveEntry(tree.root().resolve(entry.path()),
131                    treeDir.resolve(entry.path()).toString()));
132            Files.copy(tree.root().resolve(entry.path()), tos);
133            tos.closeArchiveEntry();
134        }
135    }
136
137}