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.core; 020 021import com.google.common.flogger.FluentLogger; 022import io.github.azagniotov.matcher.AntPathMatcher; 023import java.io.IOException; 024import java.nio.file.AccessDeniedException; 025import java.nio.file.FileVisitResult; 026import java.nio.file.Files; 027import java.nio.file.NoSuchFileException; 028import java.nio.file.Path; 029import java.nio.file.SimpleFileVisitor; 030import java.nio.file.attribute.BasicFileAttributes; 031import java.time.Instant; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Iterator; 035import java.util.List; 036import java.util.Objects; 037import java.util.Optional; 038import java.util.Spliterators; 039import java.util.function.Consumer; 040import java.util.stream.Stream; 041import java.util.stream.StreamSupport; 042import org.jdrupes.builder.api.BuildException; 043import org.jdrupes.builder.api.FileResource; 044import org.jdrupes.builder.api.FileTree; 045import org.jdrupes.builder.api.InputResource; 046import org.jdrupes.builder.api.InputTree; 047import org.jdrupes.builder.api.InputTree.Entry; 048import org.jdrupes.builder.api.Project; 049import org.jdrupes.builder.api.ResourceFactory; 050import org.jdrupes.builder.api.ResourceType; 051 052/// The default implementation of a [FileTree]. 053/// 054/// @param <T> the type of the [FileResource]s in the tree. 055/// 056public class ZipFileTree<T extends InputResource> extends DefaultResources<T> 057 implements InputTree<T> { 058 private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 059 @SuppressWarnings("PMD.FieldNamingConventions") 060 private static final AntPathMatcher pathMatcher 061 = new AntPathMatcher.Builder().build(); 062 private Instant latestChange; 063 private final Path zipFile; 064 private final String[] patterns; 065 private final List<String> excludes = new ArrayList<>(); 066 private boolean withDirs; 067 private boolean filled; 068 069 /// Returns a new file tree. The file tree includes all files 070 /// matching `pattern` in the tree provided by the zip file. 071 /// 072 /// @param type the resource type 073 /// @param zipFile the zip file 074 /// @param patterns the patterns 075 /// 076 @SuppressWarnings({ "PMD.ArrayIsStoredDirectly", "PMD.UseVarargs" }) 077 protected ZipFileTree(ResourceType<?> type, Path zipFile, 078 String[] patterns) { 079 super(type); 080 this.zipFile = zipFile; 081 if (patterns.length == 0) { 082 this.patterns = new String[] { "**/*" }; 083 } else { 084 this.patterns = patterns; 085 } 086 } 087 088 @Override 089 public ZipFileTree<T> exclude(String pattern) { 090 excludes.add(pattern); 091 return this; 092 } 093 094// private void fill() { 095// if (filled) { 096// return; 097// } 098// try { 099// find(root(), patterns); 100// } catch (IOException e) { 101// logger.atSevere().withCause(e).log("Problem scanning files"); 102// throw new BuildException().from(project).cause(e); 103// } 104// filled = true; 105// } 106 107 @Override 108 public Optional<Instant> asOf() { 109// fill(); 110 return Optional.ofNullable(latestChange); 111 } 112 113// @SuppressWarnings({ "PMD.CognitiveComplexity", "PMD.UseVarargs" }) 114// private void find(Path root, String[] patterns) throws IOException { 115// if (!root.toFile().exists()) { 116// return; 117// } 118// Files.walkFileTree(root, new SimpleFileVisitor<>() { 119// 120// @Override 121// public FileVisitResult visitFile(Path path, 122// BasicFileAttributes attrs) throws IOException { 123// return testAndAdd(path); 124// } 125// 126// private FileVisitResult testAndAdd(Path path) { 127// Path pathInTree = root.relativize(path); 128// if (excludes.stream().anyMatch(ex -> pathMatcher 129// .isMatch(ex, pathInTree.toString()))) { 130// if (path.toFile().isDirectory()) { 131// return FileVisitResult.SKIP_SUBTREE; 132// } 133// return FileVisitResult.CONTINUE; 134// } 135// if (Arrays.stream(patterns).anyMatch(pattern -> pathMatcher 136// .isMatch(pattern, pathInTree.toString()))) { 137// @SuppressWarnings("unchecked") 138// T resource = (T) ResourceFactory 139// .create(type().containedType(), path); 140// ZipFileTree.this.add(resource); 141// if (resource.asOf().isPresent() && (latestChange == null 142// || resource.asOf().get().isAfter(latestChange))) { 143// latestChange = resource.asOf().get(); 144// } 145// } 146// return FileVisitResult.CONTINUE; 147// } 148// 149// @Override 150// public FileVisitResult preVisitDirectory(Path dir, 151// BasicFileAttributes attrs) throws IOException { 152// if (withDirs) { 153// return testAndAdd(dir); 154// } 155// return FileVisitResult.CONTINUE; 156// } 157// 158// @Override 159// public FileVisitResult postVisitDirectory(Path dir, IOException exc) 160// throws IOException { 161// if (!withDirs) { 162// return FileVisitResult.CONTINUE; 163// } 164// 165// // Directories (and their modification date) included 166// var dirMod = Instant.ofEpochMilli(dir.toFile().lastModified()); 167// if (latestChange == null || dirMod.isAfter(latestChange)) { 168// latestChange = dirMod; 169// } 170// return FileVisitResult.CONTINUE; 171// } 172// 173// @Override 174// public FileVisitResult visitFileFailed(Path file, IOException exc) 175// throws IOException { 176// if (exc instanceof AccessDeniedException) { 177// return FileVisitResult.SKIP_SUBTREE; 178// } 179// return FileVisitResult.CONTINUE; 180// } 181// }); 182// } 183 184 @Override 185 public Stream<T> stream() { 186 return StreamSupport 187 .stream(new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE, 0) { 188 189 private Iterator<T> theIterator; 190 191 private Iterator<T> iterator() { 192 if (theIterator == null) { 193// fill(); 194 theIterator = ZipFileTree.super.stream().iterator(); 195 } 196 return theIterator; 197 } 198 199 @Override 200 public void forEachRemaining(Consumer<? super T> action) { 201 iterator().forEachRemaining(action); 202 } 203 204 @Override 205 public boolean tryAdvance(Consumer<? super T> action) { 206 if (!iterator().hasNext()) { 207 return false; 208 } 209 action.accept(iterator().next()); 210 return true; 211 } 212 }, false); 213 } 214 215 @Override 216 public Stream<Path> paths() { 217 return null; // stream().map(fr -> root().relativize(fr.path())); 218 } 219 220 @Override 221 public Stream<Entry<T>> entries() { 222 // TODO Auto-generated method stub 223 return null; 224 } 225 226}