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}