| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 |
| * |
| * https://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 org.apache.ivy.core.sort; |
| |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.apache.ivy.Ivy; |
| import org.apache.ivy.core.module.descriptor.DependencyDescriptor; |
| import org.apache.ivy.core.module.descriptor.ModuleDescriptor; |
| import org.apache.ivy.core.module.id.ModuleRevisionId; |
| import org.apache.ivy.plugins.circular.CircularDependencyHelper; |
| import org.apache.ivy.plugins.circular.CircularDependencyStrategy; |
| import org.apache.ivy.plugins.version.VersionMatcher; |
| import org.apache.ivy.util.Message; |
| |
| /** |
| * Decorates a ModuleDescriptor with some attributes used during the sort. Thus every instance of a |
| * ModuleInSort can be used in only one ModuleDescriptorSorter at a time. |
| * <p> |
| * The added fields are: |
| * </p> |
| * <ul> |
| * <li><code>isSorted</code> : is <i>true</i> if and only if this module has already been added to |
| * the sorted list.</li> |
| * <li><code>loopElements</code> : When the module is the root of a loop (=the first element of a |
| * loop met during the sort), <code>loopElements</code> contains all ModuleInSort of the loop |
| * (excluding the root itself.</li> |
| * <li><code>isLoopIntermediateElement</code> : When a loop is detected, all modules included in |
| * the loop (except the root) have <code>isLoopIntermediateElement</code> set to true.</li> |
| * <li><code>caller</code> : During the sort, we traverse recursively the graph. When doing that, |
| * caller point to the parent element.</li> |
| * </ul> |
| */ |
| class ModuleInSort { |
| |
| private final ModuleDescriptor module; |
| |
| private boolean isSorted = false; |
| |
| private List<ModuleInSort> loopElements = new LinkedList<>(); |
| |
| private boolean isLoopIntermediateElement = false; |
| |
| private ModuleInSort caller; |
| |
| public ModuleInSort(ModuleDescriptor moduleToSort) { |
| module = moduleToSort; |
| } |
| |
| public boolean isInLoop() { |
| return isLoopIntermediateElement; |
| } |
| |
| /** This ModuleInSort has been placed on the sorted list */ |
| public boolean isSorted() { |
| if (isSorted) { |
| Message.debug("Module descriptor already sorted : " |
| + module.getModuleRevisionId().toString()); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * This ModuleInSort has already been analyzed. It is either already added to the sorted list, |
| * either it is included in a loop and will be added when the root of the loop will be added to |
| * the list. |
| */ |
| public boolean isProcessed() { |
| if (isSorted || isLoopIntermediateElement) { |
| Message.debug("Module descriptor is processed : " |
| + module.getModuleRevisionId().toString()); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public void setCaller(ModuleInSort caller) { |
| this.caller = caller; |
| } |
| |
| public void endOfCall() { |
| caller = null; |
| } |
| |
| /** |
| * Check if a adding this element as a dependency of caller will introduce a circular |
| * dependency. If it is, all the elements of the loop are flagged as 'loopIntermediateElement', |
| * and the loopElements of this module (which is the root of the loop) is updated. The |
| * depStrategy is invoked on order to report a correct circular loop message. |
| * |
| * @param futurCaller ModuleInSort |
| * @param depStrategy CircularDependencyStrategy |
| * @return true if a loop is detected. |
| */ |
| public boolean checkLoop(ModuleInSort futurCaller, CircularDependencyStrategy depStrategy) { |
| if (caller != null) { |
| List<ModuleRevisionId> elemOfLoop = new LinkedList<>(); |
| elemOfLoop.add(this.module.getModuleRevisionId()); |
| ModuleInSort stackEl = futurCaller; |
| while (stackEl != this) { |
| elemOfLoop.add(stackEl.module.getModuleRevisionId()); |
| stackEl.isLoopIntermediateElement = true; |
| loopElements.add(stackEl); |
| stackEl = stackEl.caller; |
| } |
| elemOfLoop.add(this.module.getModuleRevisionId()); |
| ModuleRevisionId[] mrids = elemOfLoop.toArray(new ModuleRevisionId[elemOfLoop.size()]); |
| depStrategy.handleCircularDependency(mrids); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Add this module to the sorted list except if this module is an intermediary element of a |
| * loop. If this module is the 'root' of a loop, then all elements of that loops are added |
| * before. |
| * |
| * @param sorted |
| * The list of sorted elements on which this module will be added |
| */ |
| public void addToSortedListIfRequired(List<ModuleDescriptor> sorted) { |
| if (!isLoopIntermediateElement) { |
| addToSortList(sorted); |
| } |
| } |
| |
| /** |
| * Add this module to the sorted list. If current is the 'root' of a loop, then all elements of |
| * that loops are added before. |
| */ |
| private void addToSortList(List<ModuleDescriptor> sortedList) { |
| for (ModuleInSort moduleInLoop : loopElements) { |
| moduleInLoop.addToSortList(sortedList); |
| } |
| if (!this.isSorted()) { |
| sortedList.add(module); |
| this.isSorted = true; |
| } |
| } |
| |
| public String toString() { |
| return module.getModuleRevisionId().toString(); |
| } |
| |
| public DependencyDescriptor[] getDependencies() { |
| return module.getDependencies(); |
| } |
| |
| /** Log a warning saying that a loop is detected */ |
| public static void logLoopWarning(List<ModuleDescriptor> loopElement) { |
| Message.warn("circular dependency detected during sort: " |
| + CircularDependencyHelper.formatMessageFromDescriptors(loopElement)); |
| } |
| |
| /** |
| * Return true if this module match the DependencyDescriptor with the given versionMatcher. If |
| * this module has no version defined, then true is always returned. |
| */ |
| public boolean match(DependencyDescriptor descriptor, VersionMatcher versionMatcher) { |
| ModuleDescriptor md = module; |
| return md.getResolvedModuleRevisionId().getRevision() == null |
| || md.getResolvedModuleRevisionId().getRevision().equals(Ivy.getWorkingRevision()) |
| || versionMatcher.accept(descriptor.getDependencyRevisionId(), md); |
| // Checking md.getResolvedModuleRevisionId().getRevision().equals(Ivy.getWorkingRevision() |
| // allow to consider any local non resolved ivy.xml |
| // as a valid module. |
| } |
| |
| public ModuleDescriptor getSortedModuleDescriptor() { |
| return module; |
| } |
| } |