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.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
  import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
  import org.apache.ivy.core.module.descriptor.IncludeRule;
  import org.apache.ivy.core.module.descriptor.WorkspaceModuleDescriptor;
  
  /**
   * Class collecting usage data for an IvyNode.
   * <p>
   * Usage data contains the configurations required by callers for each root module configuration,
   * the configurations required by caller node and caller configuration, dependency artifacts
   * descriptors declared by callers, include rules declared by callers, and blacklisted data by root
   * module conf.
   * </p>
   */
  public class IvyNodeUsage {
  
      private static final class NodeConf {
          private IvyNode node;
  
          private String conf;
  
          public NodeConf(IvyNode node, String conf) {
              if (node == null) {
                  throw new NullPointerException("node must not null");
              }
              if (conf == null) {
                  throw new NullPointerException("conf must not null");
              }
              this.node = node;
              this.conf = conf;
          }
  
          public final String getConf() {
              return conf;
          }
  
          public final IvyNode getNode() {
              return node;
          }
  
          @Override
          public boolean equals(Object obj) {
              return obj instanceof NodeConf && getNode().equals(((NodeConf) obj).getNode()) && getConf().equals(((NodeConf) obj).getConf());
          }
  
          @Override
          public int hashCode() {
              // CheckStyle:MagicNumber| OFF
              int hash = 33;
              hash += getNode().hashCode() * 17;
              hash += getConf().hashCode() * 17;
              // CheckStyle:MagicNumber| OFF
              return hash;
          }
  
          @Override
          public String toString() {
              return "NodeConf(" + conf + ")";
          }
      }
  
      private static final class Depender {
          private DependencyDescriptor dd;
  
          private String dependerConf;
  
          public Depender(DependencyDescriptor dd, String dependerConf) {
              this.dd = dd;
              this.dependerConf = dependerConf;
          }
  
          @Override
          public String toString() {
              return dd + " [" + dependerConf + "]";
          }
  
          @Override
          public boolean equals(Object obj) {
              if (!(obj instanceof Depender)) {
                  return false;
              }
              Depender other = (Depender) obj;
              return other.dd == dd && other.dependerConf.equals(dependerConf);
          }
  
          @Override
          public int hashCode() {
              int hash = 33;
              hash += dd.hashCode() * 13;
              hash += dependerConf.hashCode() * 13;
              return hash;
          }
      }
  
      private IvyNode node;
  
      // used to know which configurations of the dependency are required
      // for each root module configuration
      // rootConfName -> confNames
      private Map<String, Set<String>> rootModuleConfs = new HashMap<>();
  
      private Map<NodeConf, Set<String>> requiredConfs = new HashMap<>();
  
      private Map<String, Set<Depender>> dependers = new HashMap<>();
  
      // rootModuleConf -> black list
      private Map<String, IvyNodeBlacklist> blacklisted = new HashMap<>();
  
      public IvyNodeUsage(IvyNode node) {
          this.node = node;
      }
  
      protected Collection<String> getRequiredConfigurations(IvyNode in, String inConf) {
          return requiredConfs.get(new NodeConf(in, inConf));
      }
  
      protected void setRequiredConfs(IvyNode parent, String parentConf, Collection<String> confs) {
          requiredConfs.put(new NodeConf(parent, parentConf), new HashSet<>(confs));
      }
  
      /**
       * Returns the configurations of the dependency required in a given root module configuration.
       *
       * @param rootModuleConf ditto
       * @return Set&lt;String&gt;
       */
      protected Set<String> getConfigurations(String rootModuleConf) {
          return rootModuleConfs.get(rootModuleConf);
      }
  
      protected Set<String> addAndGetConfigurations(String rootModuleConf) {
          Set<String> depConfs = rootModuleConfs.get(rootModuleConf);
          if (depConfs == null) {
              depConfs = new HashSet<>();
              rootModuleConfs.put(rootModuleConf, depConfs);
          }
          return depConfs;
      }
  
      protected Set<String> getRootModuleConfigurations() {
          return rootModuleConfs.keySet();
      }
  
      public void updateDataFrom(Collection<IvyNodeUsage> usages, String rootModuleConf) {
          for (IvyNodeUsage usage : usages) {
              updateDataFrom(usage, rootModuleConf);
          }
      }
  
      private void updateDataFrom(IvyNodeUsage usage, String rootModuleConf) {
          // update requiredConfs
          updateMapOfSet(usage.requiredConfs, requiredConfs);
  
          // update rootModuleConfs
          updateMapOfSetForKey(usage.rootModuleConfs, rootModuleConfs, rootModuleConf);
  
          // update dependencyArtifacts
          updateMapOfSetForKey(usage.dependers, dependers, rootModuleConf);
      }
  
      private <K, V> void updateMapOfSet(Map<K, Set<V>> from, Map<K, Set<V>> to) {
          for (K key : from.keySet()) {
              updateMapOfSetForKey(from, to, key);
          }
      }
  
      private <K, V> void updateMapOfSetForKey(Map<K, Set<V>> from, Map<K, Set<V>> to, K key) {
          Set<V> set = from.get(key);
          if (set != null) {
              Set<V> toupdate = to.get(key);
              if (toupdate != null) {
                  toupdate.addAll(set);
              } else {
                  to.put(key, new HashSet<>(set));
              }
          }
      }
  
      private <K, V> void addObjectsForConf(K rootModuleConf, V objectToAdd, Map<K, Set<V>> map) {
          Set<V> set = map.get(rootModuleConf);
          if (set == null) {
              set = new HashSet<>();
              map.put(rootModuleConf, set);
          }
          set.add(objectToAdd);
      }
  
      public void addUsage(String rootModuleConf, DependencyDescriptor dd, String parentConf) {
          addObjectsForConf(rootModuleConf, new Depender(dd, parentConf), dependers);
      }
  
      protected Set<DependencyArtifactDescriptor> getDependencyArtifactsSet(String rootModuleConf) {
          if (node.getDescriptor() instanceof WorkspaceModuleDescriptor) {
              // for a module in the "workspace" artifacts will be actually declared by the resolver
              return null;
          }
          Collection<Depender> dependersInConf = dependers.get(rootModuleConf);
          if (dependersInConf == null) {
              return null;
          }
          Set<DependencyArtifactDescriptor> dependencyArtifacts = new HashSet<>();
          for (Depender depender : dependersInConf) {
              DependencyArtifactDescriptor[] dads = depender.dd
                      .getDependencyArtifacts(depender.dependerConf);
              dependencyArtifacts.addAll(Arrays.asList(dads));
          }
          return dependencyArtifacts;
      }
  
      protected Set<IncludeRule> getDependencyIncludesSet(final String rootModuleConf) {
          final Collection<Depender> dependersInConf = dependers.get(rootModuleConf);
          if (dependersInConf == null) {
              return null;
          }
          Set<IncludeRule> dependencyIncludes = new HashSet<>();
          for (Depender depender : dependersInConf) {
              IncludeRule[] rules = depender.dd.getIncludeRules(depender.dependerConf);
              if (rules == null || rules.length == 0) {
                  // no include rule in at least one depender -> we must include everything,
                  // and so return no include rule at all
                  return null;
              }
              dependencyIncludes.addAll(Arrays.asList(rules));
          }
          return dependencyIncludes;
      }
  
      protected void removeRootModuleConf(String rootModuleConf) {
          rootModuleConfs.remove(rootModuleConf);
      }
  
      protected void blacklist(IvyNodeBlacklist bdata) {
          blacklisted.put(bdata.getRootModuleConf(), bdata);
      }
  
      /**
       * Indicates if this node has been blacklisted in the given root module conf.
       * <p>
       * A blacklisted node should be considered as if it doesn't even exist on the repository.
       * </p>
       *
       * @param rootModuleConf
       *            the root module conf for which we'd like to know if the node is blacklisted
       *
       * @return true if this node is blacklisted int he given root module conf, false otherwise
       * @see #blacklist(IvyNodeBlacklist)
       */
      protected boolean isBlacklisted(String rootModuleConf) {
          return blacklisted.containsKey(rootModuleConf);
      }
  
      /**
       * Returns the blacklist data of this node in the given root module conf, or <code>null</code>
       * if this node is not blacklisted in this root module conf.
       *
       * @param rootModuleConf
       *            the root module configuration to consider
       * @return the blacklist data if any
       */
      protected IvyNodeBlacklist getBlacklistData(String rootModuleConf) {
          return blacklisted.get(rootModuleConf);
      }
  
      protected IvyNode getNode() {
          return node;
      }
  
      /**
       * Indicates if at least one depender has a transitive dependency descriptor for the given root
       * module conf.
       *
       * @param rootModuleConf
       *            the root module conf to consider
       * @return <code>true</code> if at least one depender has a transitive dependency descriptor for
       *         the given root module conf, <code>false</code> otherwise.
       */
      public boolean hasTransitiveDepender(String rootModuleConf) {
          Set<Depender> dependersSet = dependers.get(rootModuleConf);
          if (dependersSet == null) {
              return false;
          }
          for (Depender depender : dependersSet) {
              if (depender.dd.isTransitive()) {
                  return true;
              }
          }
          return false;
      }
  
  }
  