/*
* Copyright 2016 DiffPlug
*
* 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 com.diffplug.common.base;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* A function which defines a tree structure.
* @see TreeStream
* @see TreeQuery
* @see TreeComparison
* @see TreeIterable
* @see TreeNode
*/
@FunctionalInterface
public interface TreeDef<T> {
/** Returns all the children of the given node. */
List<T> childrenOf(T node);
/** Creates a new TreeDef which whose {@code childrenOf} method is filtered by the given predicate. */
default TreeDef<T> filter(Predicate<T> predicate) {
return TreeDef.of(node -> filteredList(childrenOf(node), predicate));
}
/** Creates a TreeDef which is implemented by the given function. */
public static <T> TreeDef<T> of(Function<T, List<T>> childFunc) {
return new TreeDef<T>() {
@Override
public List<T> childrenOf(T node) {
return childFunc.apply(node);
}
};
}
/**
* A pair of functions which define a doubly-linked tree, where nodes know about both their parent and their children.
* <p>
* It is <i>critical</i> that the {@code TreeDef.Parented} is consistent - if Vader claims that Luke
* and Leia are his children, then both Luke and Leia must say that Vader is their parent.
* <p>
* If Luke or Leia don't agree that Vader is their father, then the algorithms that use
* this {@code TreeDef.Parented} are likely to fail in unexpected ways.
*/
public interface Parented<T> extends TreeDef<T> {
/** Returns the parent of the given node. */
T parentOf(T node);
/** Creates a new {@code TreeDef.Parented} whose {@code childrenOf} and {@code parentOf} methods are filtered by {@code predicate}. */
@Override
default Parented<T> filter(Predicate<T> predicate) {
return of(node -> filteredList(childrenOf(node), predicate), node -> {
if (predicate.test(node)) {
return parentOf(node);
} else {
return null;
}
});
}
/** Creates a new {@code TreeDef.Parented} which is implemented by the two given functions. */
public static <T> TreeDef.Parented<T> of(Function<T, List<T>> childFunc, Function<T, T> parentFunc) {
return new TreeDef.Parented<T>() {
@Override
public List<T> childrenOf(T node) {
return childFunc.apply(node);
}
@Override
public T parentOf(T node) {
return parentFunc.apply(node);
}
};
}
}
/** Returns a filtered version of the given list. */
static <T> List<T> filteredList(List<T> unfiltered, Predicate<T> filter) {
return unfiltered.stream().filter(filter).collect(Collectors.toList());
}
/** An instance of {@code TreeDef.Parented} for {@link File}. */
public static TreeDef.Parented<File> forFile(Consumer<Throwable> errorPolicy) {
Errors.Handling errors = Errors.createHandling(errorPolicy);
return TreeDef.Parented.of(
file -> errors.<List<File>> getWithDefault(() -> {
if (file.isDirectory()) {
return Arrays.asList(file.listFiles());
} else {
return Collections.emptyList();
}
}, Collections.emptyList()),
file -> errors.<File> getWithDefault(() -> {
return file.getParentFile();
}, null));
}
/** An instance of {@code TreeDef.Parented} for {@link Path}. */
public static TreeDef.Parented<Path> forPath(Consumer<Throwable> errorPolicy) {
Errors.Handling errors = Errors.createHandling(errorPolicy);
return TreeDef.Parented.of(
path -> errors.<List<Path>> getWithDefault(() -> {
if (Files.isDirectory(path)) {
return Files.list(path).collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}, Collections.emptyList()),
path -> errors.<Path> getWithDefault(() -> {
return path.getParent();
}, null));
}
}