blob: 7e090c549b29b6b7156c2808b0a29095ccc9ddbe [file] [log] [blame]
/*
* 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;
}
}