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}