/*
* Copyright 2017-present Facebook, Inc.
*
* 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.facebook.buck.ide.intellij.aggregation;
import com.facebook.buck.ide.intellij.model.IjModuleType;
import com.facebook.buck.util.MoreCollectors;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* A node in {@link AggregationTree}.
*
* <p>May point to a module or null if not module is present.
*/
class AggregationTreeNode {
private final Map<Path, AggregationTreeNode> children = new HashMap<>();
private AggregationModule module;
private Path moduleBasePath;
public AggregationTreeNode(AggregationModule module) {
this.module = module;
}
public AggregationTreeNode(AggregationModule module, Path moduleBasePath) {
this.module = module;
this.moduleBasePath = moduleBasePath;
}
public AggregationTreeNode(Path moduleBasePath) {
this.moduleBasePath = moduleBasePath;
}
public AggregationModule getModule() {
return module;
}
public void setModule(AggregationModule module) {
this.module = module;
}
public Path getModuleBasePath() {
return module == null ? moduleBasePath : module.getModuleBasePath();
}
public void setModuleBasePath(Path moduleBasePath) {
this.moduleBasePath = moduleBasePath;
}
public AggregationTreeNode getChild(Path pathComponent) {
return children.get(pathComponent);
}
public ImmutableCollection<AggregationTreeNode> getChildren() {
return ImmutableList.copyOf(children.values());
}
public ImmutableCollection<Path> getChildrenPaths() {
return ImmutableList.copyOf(children.keySet());
}
public void addChild(Path newNodePathComponents, AggregationModule module) {
Preconditions.checkNotNull(module);
addChild(newNodePathComponents, module, module.getModuleBasePath());
}
/**
* Adding a new child doing all necessary adjustments to the structure of the nodes to keep the
* tree valid.
*/
public void addChild(Path newNodePathComponents, AggregationModule module, Path moduleBasePath) {
Path firstPathComponent = newNodePathComponents.getName(0);
for (Path childPathComponents : children.keySet()) {
if (childPathComponents.startsWith(firstPathComponent)) {
if (newNodePathComponents.equals(childPathComponents)) {
replaceCurrentModule(childPathComponents, module, moduleBasePath);
} else if (newNodePathComponents.startsWith(childPathComponents)) {
putNewNodeAfterChild(newNodePathComponents, childPathComponents, module, moduleBasePath);
} else if (childPathComponents.startsWith(newNodePathComponents)) {
putNewNodeBeforeChild(newNodePathComponents, childPathComponents, module, moduleBasePath);
} else {
addModuleWithCommonSubpath(
newNodePathComponents, childPathComponents, module, moduleBasePath);
}
return;
}
}
children.put(newNodePathComponents, new AggregationTreeNode(module, moduleBasePath));
}
private void replaceCurrentModule(
Path childPathComponents, AggregationModule module, Path moduleBasePath) {
AggregationTreeNode child = children.get(childPathComponents);
child.setModule(module);
child.setModuleBasePath(moduleBasePath);
}
/**
* Covers the following situation:
*
* <p>new node location: a/b/c/d/e
*
* <p>existing node location: a/b/c
*
* <p>This should result in: a/b/c (existing) -> d/e (new)
*/
private void putNewNodeAfterChild(
Path newNodePath, Path childPath, AggregationModule module, Path moduleBasePath) {
AggregationTreeNode existingChild = children.get(childPath);
existingChild.addChild(childPath.relativize(newNodePath), module, moduleBasePath);
}
/**
* Covers the following situation:
*
* <p>new node location: a/b/c
*
* <p>existing node location: a/b/c/d/e
*
* <p>This should result in: a/b/c (new) -> d/e (existing)
*/
private void putNewNodeBeforeChild(
Path newNodePath, Path childPath, AggregationModule module, Path moduleBasePath) {
AggregationTreeNode newChild = new AggregationTreeNode(module, moduleBasePath);
AggregationTreeNode existingChild = children.get(childPath);
newChild.children.put(newNodePath.relativize(childPath), existingChild);
children.put(newNodePath, newChild);
children.remove(childPath);
}
/**
* Covers the following situation:
*
* <p>new node location: a/b/c/d/e
*
* <p>existing node location: a/b/c/f/g
*
* <p>This should result in: a/b/c (artificial) -> d/e (new), f/g (existing)
*/
private void addModuleWithCommonSubpath(
Path newNodePathComponents,
Path childPathComponents,
AggregationModule module,
Path moduleBasePath) {
Path commonPath = findCommonPath(newNodePathComponents, childPathComponents);
AggregationTreeNode oldChild = children.remove(childPathComponents);
AggregationTreeNode dummyNode =
new AggregationTreeNode(getModuleBasePath().resolve(commonPath));
children.put(commonPath, dummyNode);
dummyNode.children.put(commonPath.relativize(childPathComponents), oldChild);
dummyNode.children.put(
commonPath.relativize(newNodePathComponents),
new AggregationTreeNode(module, moduleBasePath));
}
private static Path findCommonPath(Path path1, Path path2) {
int nameIndex = 1;
while (nameIndex <= path1.getNameCount()
&& nameIndex <= path2.getNameCount()
&& path1.subpath(0, nameIndex).equals(path2.subpath(0, nameIndex))) {
nameIndex++;
}
return path1.subpath(0, nameIndex - 1);
}
/** Collects nodes located at a path with the provided length. */
public Collection<AggregationTreeNode> collectNodes(int minimumPathDepth) {
ImmutableList.Builder<AggregationTreeNode> result = ImmutableList.builder();
collectNodes(minimumPathDepth, result);
return result.build();
}
private void collectNodes(int depth, ImmutableList.Builder<AggregationTreeNode> result) {
if (depth == 0) {
result.add(this);
return;
}
for (Map.Entry<Path, AggregationTreeNode> child : children.entrySet()) {
Path childPath = child.getKey();
Preconditions.checkArgument(childPath.getNameCount() <= depth);
if (childPath.getNameCount() < depth) {
child.getValue().collectNodes(depth - childPath.getNameCount(), result);
} else {
result.add(child.getValue());
}
}
}
/** Removes a child and promotes its children to the current node. */
public void removeChild(Path childPath) {
AggregationTreeNode childNode = getChild(childPath);
Map<Path, AggregationTreeNode> nodesToKeep = new HashMap<>();
for (Map.Entry<Path, AggregationTreeNode> grandChild : childNode.children.entrySet()) {
nodesToKeep.put(childPath.resolve(grandChild.getKey()), grandChild.getValue());
}
children.remove(childPath);
nodesToKeep.entrySet().forEach(n -> children.put(n.getKey(), n.getValue()));
}
public ImmutableSet<Path> getChildrenPathsByModuleType(IjModuleType moduleType) {
return children
.entrySet()
.stream()
.filter(
e ->
e.getValue().getModule() != null
&& moduleType.equals(e.getValue().getModule().getModuleType()))
.map(Map.Entry::getKey)
.collect(MoreCollectors.toImmutableSet());
}
public ImmutableSet<Path> getChildrenPathsByModuleTypeOrTag(
IjModuleType moduleType, String aggregationTag) {
return children
.entrySet()
.stream()
.filter(e -> e.getValue().getModule() != null)
.filter(
e -> {
String childAggregationTag = e.getValue().getModule().getAggregationTag();
return Objects.equals(aggregationTag, childAggregationTag)
|| moduleType.equals(e.getValue().getModule().getModuleType());
})
.map(Map.Entry::getKey)
.collect(MoreCollectors.toImmutableSet());
}
}