|  | // 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.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; | 
|  | } | 
|  |  | 
|  | 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(); | 
|  | } | 
|  | } |