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.IOException;
023import java.nio.file.Files;
024import java.nio.file.Path;
025import java.util.ArrayList;
026import java.util.List;
027import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
028import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
029import org.jdrupes.builder.api.BuildException;
030import org.jdrupes.builder.api.FileResource;
031import org.jdrupes.builder.api.FileTree;
032import static org.jdrupes.builder.api.ResourceType.BaseFileTreeType;
033import org.jdrupes.builder.api.Resources;
034import org.jdrupes.builder.distribution.ApplicationZipFile;
035import org.jdrupes.builder.java.ClasspathElement;
036import static org.jdrupes.builder.java.JavaTypes.JarFileType;
037
038/// A distribution builder that builds a ZIP file.
039///
040public class ZipDistributionBuilder extends DistributionBuilder {
041    private static final FluentLogger logger = FluentLogger.forEnclosingClass();
042
043    /// Initializes a new zip distribution builder.
044    ///
045    public ZipDistributionBuilder() {
046        super();
047    }
048
049    /// Builds the zip.
050    ///
051    /// @param file the file
052    /// @param config the config
053    /// @param cpElements the cp elements
054    ///
055    @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops",
056        "PMD.AvoidUsingOctalValues" })
057    public void build(ApplicationZipFile file,
058            ApplicationConfigurationData config,
059            Resources<ClasspathElement> cpElements) {
060        try (var zos = new ZipArchiveOutputStream(
061            Files.newOutputStream(file.path()))) {
062            List<String> distClassPath = new ArrayList<>();
063            int classTreeCount = 0;
064            for (var cpe : cpElements.get()) {
065                if (JarFileType.isAssignableFrom(cpe.type())) {
066                    String entryName = Path.of("lib")
067                        .resolve(cpe.toPath().getFileName()).toString();
068                    distClassPath.add("$APP_HOME/" + entryName);
069                    zos.putArchiveEntry(new ZipArchiveEntry(entryName));
070                    Files.copy(cpe.toPath(), zos);
071                    zos.closeArchiveEntry();
072                    continue;
073                }
074                if (BaseFileTreeType.isAssignableFrom(cpe.type())) {
075                    @SuppressWarnings("unchecked")
076                    FileTree<FileResource> fileTree
077                        = (FileTree<FileResource>) cpe;
078                    if (fileTree.isEmpty()) {
079                        continue;
080                    }
081                    classTreeCount += 1;
082                    addClassTree(
083                        zos, distClassPath, classTreeCount, fileTree);
084                    continue;
085                }
086                logger.atWarning().log("Cannot add %s to distribution", cpe);
087            }
088
089            // Add generated scripts
090            var model = buildModel(config, distClassPath);
091            var entry = new ZipArchiveEntry(
092                Path.of("bin", config.executableName()).toString());
093            entry.setUnixMode(0755);
094            zos.putArchiveEntry(entry);
095            addUnixScript(zos, model);
096            zos.closeArchiveEntry();
097            entry = new ZipArchiveEntry(
098                Path.of("bin", config.executableName()).toString() + ".bat");
099            entry.setUnixMode(0755);
100            zos.putArchiveEntry(entry);
101            addWindowsBat(zos, model);
102            zos.closeArchiveEntry();
103        } catch (IOException e) {
104            throw new BuildException().cause(e);
105        }
106    }
107
108    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
109    private void addClassTree(ZipArchiveOutputStream zos,
110            List<String> distClassPath, int classTreeCount,
111            FileTree<FileResource> tree) throws IOException {
112        var treeDir = Path.of("classes")
113            .resolve(Integer.toString(classTreeCount));
114        distClassPath.add("$APP_HOME/" + treeDir.toString());
115        var iter = tree.entries().iterator();
116        while (iter.hasNext()) {
117            var entry = iter.next();
118            zos.putArchiveEntry(new ZipArchiveEntry(treeDir
119                .resolve(entry.path()).toString()));
120            Files.copy(tree.root().resolve(entry.path()), zos);
121            zos.closeArchiveEntry();
122        }
123    }
124
125}