content: /*
   *  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;
      }
  }
  