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.ArrayDeque;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Deque;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.ivy.core.module.descriptor.Artifact;
  import org.apache.ivy.core.module.descriptor.Configuration;
  import org.apache.ivy.core.module.descriptor.DefaultArtifact;
  import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
  import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
  import org.apache.ivy.core.module.id.ModuleId;
  import org.apache.ivy.core.module.id.ModuleRevisionId;
  
  public class IvyNodeCallers {
      public static class Caller {
          private ModuleDescriptor md;
  
          private ModuleRevisionId mrid;
  
          // callerConf -> dependencyConfs
          private Map<String, String[]> confs = new HashMap<>();
  
          private DependencyDescriptor dd;
  
          private boolean callerCanExclude;
  
          private boolean real = true;
  
          public Caller(ModuleDescriptor md, ModuleRevisionId mrid, DependencyDescriptor dd,
                  boolean callerCanExclude) {
              this.md = md;
              this.mrid = mrid;
              this.dd = dd;
              this.callerCanExclude = callerCanExclude;
          }
  
          public void addConfiguration(String callerConf, String[] dependencyConfs) {
              updateConfs(callerConf, dependencyConfs);
              Configuration conf = md.getConfiguration(callerConf);
              if (conf != null) {
                  String[] confExtends = conf.getExtends();
                  if (confExtends != null) {
                      for (String confExtend : confExtends) {
                          addConfiguration(confExtend, dependencyConfs);
                      }
                  }
              }
          }
  
          private void updateConfs(String callerConf, String[] dependencyConfs) {
              String[] prevDepConfs = confs.get(callerConf);
              if (prevDepConfs != null) {
                  Set<String> newDepConfs = new HashSet<>(Arrays.asList(prevDepConfs));
                  newDepConfs.addAll(Arrays.asList(dependencyConfs));
                  confs.put(callerConf, newDepConfs.toArray(new String[newDepConfs.size()]));
              } else {
                  confs.put(callerConf, dependencyConfs);
              }
          }
  
          public String[] getCallerConfigurations() {
              return confs.keySet().toArray(new String[confs.keySet().size()]);
          }
  
          public ModuleRevisionId getModuleRevisionId() {
              return mrid;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (!(obj instanceof Caller)) {
                  return false;
              }
              Caller other = (Caller) obj;
              return other.confs.equals(confs) && mrid.equals(other.mrid);
          }
  
          @Override
          public int hashCode() {
              // CheckStyle:MagicNumber| OFF
              int hash = 31;
              hash = hash * 13 + confs.hashCode();
              hash = hash * 13 + mrid.hashCode();
              // CheckStyle:MagicNumber| ON
              return hash;
          }
  
          @Override
          public String toString() {
              return mrid.toString();
          }
  
          @Deprecated
          public ModuleRevisionId getAskedDependencyId(ResolveData resolveData) {
              return getAskedDependencyId();
          }
  
          public ModuleRevisionId getAskedDependencyId() {
              return dd.getDependencyRevisionId();
          }
  
          public ModuleDescriptor getModuleDescriptor() {
              return md;
          }
  
          public boolean canExclude() {
              return callerCanExclude || md.canExclude() || dd.canExclude();
          }
  
          public DependencyDescriptor getDependencyDescriptor() {
              return dd;
          }
  
          public void setRealCaller(boolean b) {
              this.real = b;
          }
  
          public boolean isRealCaller() {
              return real;
          }
      }
  
      // key in second map is used to easily get a caller by its mrid
      private Map<String, Map<ModuleRevisionId, Caller>> callersByRootConf = new HashMap<>();
  
      // this map contains all the module ids calling this one (including transitively) as keys.
      // the mapped nodes (values) correspond to a direct caller from which the transitive caller
      // comes
      private Map<ModuleId, IvyNode> allCallers = new HashMap<>();
  
      private IvyNode node;
  
      public IvyNodeCallers(IvyNode node) {
          this.node = node;
      }
  
      /**
       * @param rootModuleConf ditto
       * @param callerNode IvyNode
       * @param callerConf ditto
       * @param requestedConf ditto
       * @param dependencyConfs
       *            '*' must have been resolved
       * @param dd
       *            the dependency revision id asked by the caller
       */
      public void addCaller(String rootModuleConf, IvyNode callerNode, String callerConf,
              String requestedConf, String[] dependencyConfs, DependencyDescriptor dd) {
          ModuleDescriptor md = callerNode.getDescriptor();
          ModuleRevisionId mrid = callerNode.getResolvedId();
          if (mrid.getModuleId().equals(node.getId().getModuleId())) {
              throw new IllegalArgumentException("a module is not authorized to depend on itself: "
                      + node.getId());
          }
          Map<ModuleRevisionId, Caller> callers = callersByRootConf.get(rootModuleConf);
          if (callers == null) {
              callers = new HashMap<>();
              callersByRootConf.put(rootModuleConf, callers);
          }
          Caller caller = callers.get(mrid);
          if (caller == null) {
              caller = new Caller(md, mrid, dd, callerNode.canExclude(rootModuleConf));
              callers.put(mrid, caller);
          }
          caller.addConfiguration(requestedConf, dependencyConfs);
  
          IvyNode parent = callerNode.getRealNode();
          for (ModuleId mid : parent.getAllCallersModuleIds()) {
              allCallers.put(mid, parent);
          }
          allCallers.put(mrid.getModuleId(), callerNode);
      }
  
      void removeCaller(String rootModuleConf, ModuleRevisionId callerMrid) {
          allCallers.remove(callerMrid.getModuleId());
          Map<ModuleRevisionId, Caller> callers = callersByRootConf.get(rootModuleConf);
          if (callers != null) {
              callers.remove(callerMrid);
          }
      }
  
      public Caller[] getCallers(String rootModuleConf) {
          Map<ModuleRevisionId, Caller> callers = callersByRootConf.get(rootModuleConf);
          if (callers == null) {
              return new Caller[0];
          }
          return callers.values().toArray(new Caller[callers.values().size()]);
      }
  
      private Set<Caller> getCallersByMrid(String rootModuleConf, ModuleRevisionId mrid) {
          Map<ModuleRevisionId, Caller> callers = callersByRootConf.get(rootModuleConf);
          if (callers == null) {
              return Collections.emptySet();
          }
  
          Set<Caller> mridCallers = new HashSet<>();
          for (Caller caller : callers.values()) {
              if (caller.getAskedDependencyId().equals(mrid)) {
                  mridCallers.add(caller);
              }
          }
          return mridCallers;
      }
  
      public Caller[] getAllCallers() {
          Set<Caller> all = new HashSet<>();
          for (Map<ModuleRevisionId, Caller> callers : callersByRootConf.values()) {
              all.addAll(callers.values());
          }
          return all.toArray(new Caller[all.size()]);
      }
  
      public Caller[] getAllRealCallers() {
          Set<Caller> all = new HashSet<>();
          for (Map<ModuleRevisionId, Caller> callers : callersByRootConf.values()) {
              for (Caller c : callers.values()) {
                  if (c.isRealCaller()) {
                      all.add(c);
                  }
              }
          }
          return all.toArray(new Caller[all.size()]);
      }
  
      public Collection<ModuleId> getAllCallersModuleIds() {
          return allCallers.keySet();
      }
  
      void updateFrom(IvyNodeCallers callers, String rootModuleConf, boolean real) {
          Map<ModuleRevisionId, Caller> nodecallers = callers.callersByRootConf.get(rootModuleConf);
          if (nodecallers != null) {
              Map<ModuleRevisionId, Caller> thiscallers = callersByRootConf.get(rootModuleConf);
              if (thiscallers == null) {
                  thiscallers = new HashMap<>();
                  callersByRootConf.put(rootModuleConf, thiscallers);
              }
              for (Caller caller : nodecallers.values()) {
                  if (!thiscallers.containsKey(caller.getModuleRevisionId())) {
                      if (!real) {
                          caller.setRealCaller(false);
                      }
                      thiscallers.put(caller.getModuleRevisionId(), caller);
                  }
              }
          }
      }
  
      public IvyNode getDirectCallerFor(ModuleId from) {
          return allCallers.get(from);
      }
  
      /**
       * Returns true if ALL callers exclude the given artifact in the given root module conf
       *
       * @param rootModuleConf ditto
       * @param artifact Artifact
       * @return boolean
       */
      boolean doesCallersExclude(String rootModuleConf, Artifact artifact) {
          return doesCallersExclude(rootModuleConf, artifact, new ArrayDeque<IvyNode>());
      }
  
      boolean doesCallersExclude(String rootModuleConf, Artifact artifact,
              Deque<IvyNode> callersStack) {
          /* The caller stack is, from bottom to top, the path from the
             artifact we're considering excluding up towards the
             root. */
          callersStack.push(node);
          try {
              Set<Caller> callers = getCallersByMrid(rootModuleConf, node.getId());
              if (callers.isEmpty()) {
                  return false;
              }
              boolean allInconclusive = true;
              String[] moduleConfs = new String[] {rootModuleConf};
              callers: for (Caller caller : callers) {
                  /* Each ancestor of this artifact (called "descendant", here, since it's
                     a descendant relative to this.node) might itself have been excluded by
                     an older ancestor (this.node); if it is, then it is as if artifact
                     itself were excluded in this path. */
                  for (IvyNode descendant : callersStack) {
                      if (node.directlyExcludes(node.getDescriptor(), moduleConfs,
                              caller.getDependencyDescriptor(),
                              DefaultArtifact.newIvyArtifact(descendant.getId(), null))) {
                          allInconclusive = false;
                          continue callers;
                      }
                  }
                  if (!caller.canExclude()) {
                      return false;
                  }
                  Boolean doesExclude = node.doesExclude(caller.getModuleDescriptor(), rootModuleConf,
                          caller.getCallerConfigurations(), caller.getDependencyDescriptor(),
                          artifact, callersStack);
                  if (doesExclude != null) {
                      if (!doesExclude) {
                          return false;
                      }
                      allInconclusive = false;
                  }
              }
              return !allInconclusive;
          } finally {
              callersStack.pop();
          }
      }
  
  }
  