blob: 929399a2ba836a6ad633ea1b3425d412b432840b [file] [log] [blame]
// Copyright (C) 2013 The Android Open Source Project
//
// 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.google.gerrit.server.project;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.AllProjectsName;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
/**
* Iterates from a project up through its parents to All-Projects.
*
* <p>If a cycle is detected the cycle is broken and All-Projects is visited.
*/
class ProjectHierarchyIterator implements Iterator<ProjectState> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final ProjectCache cache;
private final AllProjectsName allProjectsName;
private final Set<Project.NameKey> seen;
private ProjectState next;
ProjectHierarchyIterator(ProjectCache c, AllProjectsName all, ProjectState firstResult) {
cache = c;
allProjectsName = all;
seen = Sets.newLinkedHashSet();
seen.add(firstResult.getNameKey());
next = firstResult;
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public ProjectState next() {
ProjectState n = next;
if (n == null) {
throw new NoSuchElementException();
}
next = computeNext(n);
return n;
}
@Nullable
private ProjectState computeNext(ProjectState n) {
Project.NameKey parentName = n.getProject().getParent();
if (parentName != null && visit(parentName)) {
Optional<ProjectState> p = cache.get(parentName);
if (p.isPresent()) {
return p.get();
}
}
// Parent does not exist or was already visited.
// Fall back to visit All-Projects exactly once.
if (seen.add(allProjectsName)) {
return cache.getAllProjects();
}
return null;
}
private boolean visit(Project.NameKey parentName) {
if (seen.add(parentName)) {
return true;
}
List<String> order = Lists.newArrayListWithCapacity(seen.size() + 1);
for (Project.NameKey p : seen) {
order.add(p.get());
}
int idx = order.lastIndexOf(parentName.get());
order.add(parentName.get());
logger.atWarning().log(
"Cycle detected in projects: %s", Joiner.on(" -> ").join(order.subList(idx, order.size())));
return false;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}