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.resolve;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Date;
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.apache.ivy.core.event.EventManager;
  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.core.report.ConfigurationResolveReport;
  import org.apache.ivy.util.Message;
  
  public class ResolveData {
      private ResolveEngine engine;
  
      // shared map of all visit data
      private Map<ModuleRevisionId, VisitData> visitData;
  
      private ConfigurationResolveReport report;
  
      private ResolveOptions options;
  
      private VisitNode currentVisitNode = null;
  
      private ResolvedModuleRevision currentResolvedModuleRevision;
  
      public ResolveData(ResolveData data, boolean validate) {
          this(data.engine, new ResolveOptions(data.options).setValidate(validate), data.report,
                  data.visitData);
          setCurrentVisitNode(data.currentVisitNode);
          setCurrentResolvedModuleRevision(data.currentResolvedModuleRevision);
      }
  
      public ResolveData(ResolveEngine engine, ResolveOptions options) {
          this(engine, options, null, new LinkedHashMap<ModuleRevisionId, VisitData>());
      }
  
      public ResolveData(ResolveEngine engine, ResolveOptions options,
              ConfigurationResolveReport report) {
          this(engine, options, report, new LinkedHashMap<ModuleRevisionId, VisitData>());
      }
  
      public ResolveData(ResolveEngine engine, ResolveOptions options,
              ConfigurationResolveReport report, Map<ModuleRevisionId, VisitData> visitData) {
          this.engine = engine;
          this.report = report;
          this.visitData = visitData;
          this.options = options;
      }
  
      public ConfigurationResolveReport getReport() {
          return report;
      }
  
      public IvyNode getNode(ModuleRevisionId mrid) {
          VisitData visitData = getVisitData(mrid);
          return visitData == null ? null : visitData.getNode();
      }
  
      public Collection<IvyNode> getNodes() {
          Collection<IvyNode> nodes = new ArrayList<>();
          for (VisitData vdata : visitData.values()) {
              nodes.add(vdata.getNode());
          }
          return nodes;
      }
  
      public Collection<ModuleRevisionId> getNodeIds() {
          return visitData.keySet();
      }
  
      public VisitData getVisitData(ModuleRevisionId mrid) {
          VisitData result = visitData.get(mrid);
  
          if (result == null) {
              // search again, now ignore the missing extra attributes
              for (Map.Entry<ModuleRevisionId, VisitData> entry : visitData.entrySet()) {
                  ModuleRevisionId current = entry.getKey();
  
                  if (isSubMap(mrid.getAttributes(), current.getAttributes())) {
                      result = entry.getValue();
                      break;
                  }
              }
          }
  
          return result;
      }
  
      /**
       * Checks whether one map is a sub-map of the other.
       */
      private static <K, V> boolean isSubMap(Map<K, V> map1, Map<K, V> map2) {
          int map1Size = map1.size();
          int map2Size = map2.size();
  
          if (map1Size == map2Size) {
              return map1.equals(map2);
          }
  
          Map<K, V> smallest = map1Size < map2Size ? map1 : map2;
          Map<K, V> largest = map1Size < map2Size ? map2 : map1;
  
          for (Map.Entry<K, V> entry : smallest.entrySet()) {
  
              if (!largest.containsKey(entry.getKey())) {
                  return false;
              }
  
              Object map1Value = smallest.get(entry.getKey());
              Object map2Value = largest.get(entry.getKey());
              if (!isEqual(map1Value, map2Value)) {
                  return false;
              }
          }
  
          return true;
      }
  
      private static boolean isEqual(Object obj1, Object obj2) {
          return obj1 == obj2 || obj1 != null && obj2 != null && obj1.equals(obj2);
      }
  
      /**
       * Returns the VisitNode currently visited, or <code>null</code> if there is no node currently
       * visited in this context.
       *
       * @return the VisitNode currently visited
       */
      public VisitNode getCurrentVisitNode() {
          return currentVisitNode;
      }
  
      /**
       * Sets the currently visited node. WARNING: This should only be called by Ivy core
       * ResolveEngine!
       *
       * @param currentVisitNode VisitNode
       */
      void setCurrentVisitNode(VisitNode currentVisitNode) {
          this.currentVisitNode = currentVisitNode;
      }
  
      public void register(VisitNode node) {
          register(node.getId(), node);
      }
  
      public void register(ModuleRevisionId mrid, VisitNode node) {
          VisitData visitData = getVisitData(mrid);
          if (visitData == null) {
              visitData = new VisitData(node.getNode());
              visitData.addVisitNode(node);
              this.visitData.put(mrid, visitData);
          } else {
              visitData.setNode(node.getNode());
              visitData.addVisitNode(node);
          }
      }
  
      /**
       * Updates the visit data currently associated with the given mrid with the given node and the
       * visit nodes of the old visitData for the given rootModuleConf
       *
       * @param mrid
       *            the module revision id for which the update should be done
       * @param node
       *            the IvyNode to associate with the visit data to update
       * @param rootModuleConf
       *            the root module configuration in which the update is made
       */
      void replaceNode(ModuleRevisionId mrid, IvyNode node, String rootModuleConf) {
          VisitData visitData = getVisitData(mrid);
          if (visitData == null) {
              throw new IllegalArgumentException("impossible to replace node for id " + mrid
                      + ". No registered node found.");
          }
          VisitData keptVisitData = getVisitData(node.getId());
          if (keptVisitData == null) {
              throw new IllegalArgumentException("impossible to replace node with " + node
                      + ". No registered node found for " + node.getId() + ".");
          }
          // replace visit data in Map (discards old one)
          this.visitData.put(mrid, keptVisitData);
          // update visit data with discarded visit nodes
          keptVisitData.addVisitNodes(rootModuleConf, visitData.getVisitNodes(rootModuleConf));
  
          report.updateDependency(mrid, node);
      }
  
      public void setReport(ConfigurationResolveReport report) {
          this.report = report;
      }
  
      public Date getDate() {
          return options.getDate();
      }
  
      public boolean isValidate() {
          return options.isValidate();
      }
  
      public boolean isTransitive() {
          return options.isTransitive();
      }
  
      public ResolveOptions getOptions() {
          return options;
      }
  
      public ResolveEngineSettings getSettings() {
          return engine.getSettings();
      }
  
      public EventManager getEventManager() {
          return engine.getEventManager();
      }
  
      public ResolveEngine getEngine() {
          return engine;
      }
  
      void blacklist(IvyNode node) {
          Iterator<Map.Entry<ModuleRevisionId, VisitData>> iter = visitData.entrySet().iterator();
          while (iter.hasNext()) {
              Map.Entry<ModuleRevisionId, VisitData> entry = iter.next();
              if (entry.getValue().getNode() == node && !node.getResolvedId().equals(entry.getKey())) {
                  // this visit data was associated with the blacklisted node,
                  // we discard this association
                  iter.remove();
              }
          }
      }
  
      public boolean isBlacklisted(String rootModuleConf, ModuleRevisionId mrid) {
          IvyNode node = getNode(mrid);
  
          /*
          if (node == null) {
              // search again, now ignore the extra attributes
              // TODO: maybe we should search the node that has at least the same attributes as mrid
              for (Map.Entry<ModuleRevisionId, VisitData> entry : visitData.entrySet()) {
                  ModuleRevisionId current = entry.getKey();
                  if (current.getModuleId().equals(mrid.getModuleId())
                          && current.getRevision().equals(mrid.getRevision())) {
                      VisitData data = entry.getValue();
                      node = data.getNode();
                      break;
                  }
              }
          }
          */
  
          return node != null && node.isBlacklisted(rootModuleConf);
      }
  
      public DependencyDescriptor mediate(DependencyDescriptor dd) {
          DependencyDescriptor originalDD = dd;
          dd = getEngine().mediate(dd, getOptions());
  
          VisitNode current = getCurrentVisitNode();
          if (current != null) {
              // mediating dd through dependers stack
              List<VisitNode> dependers = new ArrayList<>(current.getPath());
              // the returned path contains the currently visited node, we are only interested in
              // the dependers, so we remove the currently visited node from the end
              dependers.remove(dependers.size() - 1);
              // we want to apply mediation going up in the dependers stack, not the opposite
              Collections.reverse(dependers);
              for (VisitNode n : dependers) {
                  ModuleDescriptor md = n.getDescriptor();
                  if (md != null) {
                      dd = md.mediate(dd);
                  }
              }
          }
  
          if (originalDD != dd) {
              Message.verbose("dependency descriptor has been mediated: " + originalDD + " => " + dd);
          }
  
          return dd;
      }
  
      /**
       * Sets the last {@link ResolvedModuleRevision} which has been currently resolved.
       * <p>
       * This can be used especially in dependency resolvers, to know if another dependency resolver
       * has already resolved the requested dependency, to take a decision if the resolver should try
       * to resolve it by itself or not. Indeed, the dependency resolver is responsible for taking
       * this decision, even when included in a chain. The chain responsibility is only to set this
       * current resolved module revision to enable the resolver to take the decision.
       * </p>
       *
       * @param mr
       *            the last {@link ResolvedModuleRevision} which has been currently resolved.
       */
      public void setCurrentResolvedModuleRevision(ResolvedModuleRevision mr) {
          this.currentResolvedModuleRevision = mr;
      }
  
      /**
       * Returns the last {@link ResolvedModuleRevision} which has been currently resolved.
       * <p>
       * It can be <code>null</code>.
       * </p>
       *
       * @return the last {@link ResolvedModuleRevision} which has been currently resolved.
       */
      public ResolvedModuleRevision getCurrentResolvedModuleRevision() {
          return currentResolvedModuleRevision;
      }
  }
  