/*
 * Copyright 2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.internal.snapshot.impl;

import org.gradle.internal.file.FileMetadata;
import org.gradle.internal.snapshot.DirectorySnapshot;
import org.gradle.internal.snapshot.FileSystemLocationSnapshot;
import org.gradle.internal.snapshot.FileSystemSnapshotHierarchyVisitor;
import org.gradle.internal.snapshot.MerkleDirectorySnapshotBuilder;
import org.gradle.internal.snapshot.SnapshotVisitResult;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Consumer;

public class FilteredTrackingMerkleDirectorySnapshotBuilder extends MerkleDirectorySnapshotBuilder {
    private final Deque<Boolean> isCurrentLevelUnfiltered = new ArrayDeque<>();
    private final Consumer<FileSystemLocationSnapshot> unfilteredSnapshotConsumer;

    public static FilteredTrackingMerkleDirectorySnapshotBuilder sortingRequired(Consumer<FileSystemLocationSnapshot> unfilteredSnapshotConsumer) {
        return new FilteredTrackingMerkleDirectorySnapshotBuilder(true, unfilteredSnapshotConsumer);
    }

    private FilteredTrackingMerkleDirectorySnapshotBuilder(boolean sortingRequired, Consumer<FileSystemLocationSnapshot> unfilteredSnapshotConsumer) {
        super(sortingRequired);
        this.unfilteredSnapshotConsumer = unfilteredSnapshotConsumer;
        // The root starts out as unfiltered.
        isCurrentLevelUnfiltered.addLast(true);
    }

    @Override
    public void enterDirectory(FileMetadata.AccessType accessType, String absolutePath, String name, EmptyDirectoryHandlingStrategy emptyDirectoryHandlingStrategy) {
        isCurrentLevelUnfiltered.addLast(true);
        super.enterDirectory(accessType, absolutePath, name, emptyDirectoryHandlingStrategy);
    }

    public void markCurrentLevelAsFiltered() {
        isCurrentLevelUnfiltered.removeLast();
        isCurrentLevelUnfiltered.addLast(false);
    }

    public boolean isCurrentLevelUnfiltered() {
        return isCurrentLevelUnfiltered.getLast();
    }

    @Override
    public FileSystemLocationSnapshot leaveDirectory() {
        FileSystemLocationSnapshot directorySnapshot = super.leaveDirectory();
        boolean leftLevelUnfiltered = isCurrentLevelUnfiltered.removeLast();
        isCurrentLevelUnfiltered.addLast(isCurrentLevelUnfiltered.removeLast() && leftLevelUnfiltered);
        if (!leftLevelUnfiltered && directorySnapshot != null) {
            directorySnapshot.accept(new FileSystemSnapshotHierarchyVisitor() {
                private boolean isRoot = true;

                @Override
                public void enterDirectory(DirectorySnapshot directorySnapshot) {
                    isRoot = false;
                }

                @Override
                public SnapshotVisitResult visitEntry(FileSystemLocationSnapshot snapshot) {
                    if (isRoot) {
                        return SnapshotVisitResult.CONTINUE;
                    } else {
                        unfilteredSnapshotConsumer.accept(snapshot);
                    }

                    return SnapshotVisitResult.SKIP_SUBTREE;
                }
            });
        }
        return directorySnapshot;
    }
}

@@ -747,32 +747,154 @@
 ectorySnapshot;%0A
+import org.gradle.internal.snapshot.DirectorySnapshotBuilder;%0Aimport org.gradle.internal.snapshot.FileSystemLeafSnapshot;%0A
 import org.gradl
@@ -1139,16 +1139,50 @@
 esult;%0A%0A
+import javax.annotation.Nullable;%0A
 import j
@@ -1327,22 +1327,19 @@
 der 
-extend
+implement
 s 
-Merkle
 Dire
@@ -1523,16 +1523,69 @@
 onsumer;
+%0A    private final DirectorySnapshotBuilder delegate;
 %0A%0A    pu
@@ -1796,14 +1796,8 @@
 der(
-true, 
 unfi
@@ -1891,33 +1891,8 @@
 der(
-boolean sortingRequired, 
 Cons
@@ -1966,14 +1966,55 @@
     
-super(
+this.delegate = MerkleDirectorySnapshotBuilder.
 sort
@@ -2024,16 +2024,17 @@
 Required
+(
 );%0A     
@@ -2191,32 +2191,270 @@
 t(true);%0A    %7D%0A%0A
+    @Override%0A    public void enterDirectory(DirectorySnapshot directorySnapshot, EmptyDirectoryHandlingStrategy emptyDirectoryHandlingStrategy) %7B%0A        delegate.enterDirectory(directorySnapshot, emptyDirectoryHandlingStrategy);%0A    %7D%0A%0A
     @Override%0A  
@@ -2673,21 +2673,24 @@
         
-super
+delegate
 .enterDi
@@ -2761,32 +2761,309 @@
 rategy);%0A    %7D%0A%0A
+    @Override%0A    public void visitLeafElement(FileSystemLeafSnapshot snapshot) %7B%0A        delegate.visitLeafElement(snapshot);%0A    %7D%0A%0A    @Override%0A    public void visitDirectory(DirectorySnapshot directorySnapshot) %7B%0A        delegate.visitDirectory(directorySnapshot);%0A    %7D%0A%0A
     public void 
@@ -3432,13 +3432,16 @@
 t = 
-super
+delegate
 .lea
@@ -4471,11 +4471,135 @@
 t;%0A    %7D
+%0A%0A    @Nullable%0A    @Override%0A    public FileSystemLocationSnapshot getResult() %7B%0A        return delegate.getResult();%0A    %7D
 %0A%7D%0A

Use composition instead of inheritance in FilteredTrackingMerkleDirectorySnapshotBuilder