/* Copyright (c) 2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.plumbing.diff;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.storage.NodePathStorageOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
/**
* A helper class for {@link PathFilteringDiffConsumer} that evaluates whether path filters apply
* whenever the consumer gets an tree/bucket/feature event.
*
* @see PathFilteringDiffConsumer
*/
final class DiffPathFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(DiffPathFilter.class);
private static final NodePathStorageOrder ORDER = new NodePathStorageOrder();
private List<String> pathFilters;
public DiffPathFilter(List<String> filters) {
Preconditions.checkNotNull(filters, "filter list is null");
Preconditions.checkArgument(!filters.isEmpty(), "Don't use an empty filter list");
this.pathFilters = new ArrayList<String>(new HashSet<String>(filters));
for (String s : this.pathFilters) {
if (Strings.isNullOrEmpty(s)) {
throw new IllegalArgumentException(String.format(
"Empty or null filters not allowed: %s",
Arrays.toString(this.pathFilters.toArray())));
}
}
}
public String name(Node left, Node right) {
return left == null ? right.getName() : left.getName();
}
/**
* Tests whether the given tree path applies to one of the path filters.
* <p>
* Given a path filer {@code roads/highway}, the following {@code treePaths}:
* <ul>
* <li>{@code roads]} applies
* <li>{@code roads/highway]} applies
* <li>{@code roads/secondary} does not apply
* <li>{@code roads/highway/principal} applies
* <li>{@code buildings[/**]} does not apply
* </ul>
*
* @param treePath
* @return {@code true} if {@code treePath} is a parent of, or equals to, one of the path
* filters
*/
public boolean treeApplies(final String treePath) {
String filter;
boolean applies = false;
for (int i = 0; i < pathFilters.size(); i++) {
filter = pathFilters.get(i);
if (filter.equals(treePath)) {
applies = true;
} else {
boolean filterIsChildOfTree = NodeRef.isChild(treePath, filter);
if (filterIsChildOfTree) {
applies = true;
}
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Filter: '{}', tree: '{}', applies: {}", filter, treePath, applies);
}
if (applies) {
return true;
}
}
return false;
}
/**
* If this method is called then {@link #treeApplies(String)} returned {@code true} for the pair
* of trees the buckets belong to, meaning that {@code treePath} either matches exactly one of
* the filters, or is a filter children. If the former, all tree buckets apply. If the later,
* only the ones whose simple name
* <ul>
* <li>a filter refers to exactly the same tree than {@code treePath}, in which case all buckets
* apply
* <li>a filter is a child of {@code treePath}, in which case the bucket applies
* </ul>
*
* @param treePath the path of the tree the bucket belong to
* @param bucketIndex
* @param bucketDepth
* @return
*/
public boolean bucketApplies(final String treePath, final int bucketIndex, final int bucketDepth) {
String filter;
for (int i = 0; i < pathFilters.size(); i++) {
filter = pathFilters.get(i);
if (filter.equals(treePath)) {
// all buckets apply
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Filter: '{}', tree: '{}', depth: {}, bucket idx {}, applies: {}",
filter, treePath, bucketDepth, bucketIndex, true);
}
return true;
}
boolean filterIsChildOfTree = NodeRef.isChild(treePath, filter);
if (filterIsChildOfTree) {
ImmutableList<String> filterSteps = NodeRef.split(filter);
ImmutableList<String> treeSteps = NodeRef.split(treePath);
String childName = filterSteps.get(treeSteps.size());
int childBucket = ORDER.bucket(childName, bucketDepth);
boolean applies = childBucket == bucketIndex;
if (LOGGER.isTraceEnabled()) {
LOGGER.trace(
"Filter: '{}', tree: '{}', depth: {}, bucket idx {}, child bucket: {}, child name: '{}', applies: {}",
filter, treePath, bucketDepth, bucketIndex, childBucket, childName,
applies);
}
if (applies) {
return true;
}
}
}
return false;
}
/**
* @param featurePath path o a feature node (e.g. {@code 'roads/road1'}
* @return {@code true} if {@code featurePath} is a child of, or equals to, one of the path
* filters.
*/
public boolean featureApplies(final String featurePath) {
String filter;
boolean applies = false;
for (int i = 0; i < pathFilters.size(); i++) {
filter = pathFilters.get(i);
if (filter.equals(featurePath)) {
applies = true;
} else if (NodeRef.isChild(filter, featurePath)) {
applies = true;
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Filter: '{}', feature: '{}', applies: {}", filter, featurePath,
applies);
}
if (applies) {
return true;
}
}
return false;
}
}