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.report;
  
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.ivy.core.cache.ResolutionCacheManager;
  import org.apache.ivy.core.module.descriptor.Artifact;
  import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
  import org.apache.ivy.core.module.id.ModuleId;
  import org.apache.ivy.core.module.id.ModuleRevisionId;
  import org.apache.ivy.core.resolve.IvyNode;
  import org.apache.ivy.core.resolve.ResolveEngine;
  import org.apache.ivy.core.resolve.ResolveOptions;
  import org.apache.ivy.core.sort.SortOptions;
  import org.apache.ivy.plugins.report.XmlReportParser;
  import org.apache.ivy.util.Message;
  
  /**
   * Represents a whole resolution report for a module but for a specific configuration
   */
  public class ConfigurationResolveReport {
  
      private final ModuleDescriptor md;
  
      private final String conf;
  
      private final Date date;
  
      private final ResolveOptions options;
  
      private Map<IvyNode, List<ArtifactDownloadReport>> dependencyReports = new LinkedHashMap<>();
  
      private Map<ModuleRevisionId, IvyNode> dependencies = new LinkedHashMap<>();
  
      private final ResolveEngine resolveEngine;
  
      private Map<ModuleId, Collection<IvyNode>> modulesIdsMap = new LinkedHashMap<>();
  
      private List<ModuleId> modulesIds;
  
      private Boolean hasChanged = null;
  
      public ConfigurationResolveReport(ResolveEngine resolveEngine, ModuleDescriptor md,
              String conf, Date date, ResolveOptions options) {
          this.resolveEngine = resolveEngine;
          this.md = md;
          this.conf = conf;
          this.date = date;
          this.options = options;
      }
  
      /**
       * Check if the set of dependencies has changed since the previous execution of a resolution.
       * <p>
       * This function use the report file found in the cache. So the function must be called before
       * the new report is serialized there.
       * </p>
       * <p>
       * This function also use the internal dependencies that must already be filled. This function
       * might be 'heavy' because it may have to parse the previous report.
       * </p>
       */
      public void checkIfChanged() {
          ResolutionCacheManager cache = resolveEngine.getSettings().getResolutionCacheManager();
          String resolveId = options.getResolveId();
          File previousReportFile = cache.getConfigurationResolveReportInCache(resolveId, conf);
          if (previousReportFile.exists()) {
              try {
                  XmlReportParser parser = new XmlReportParser();
                  parser.parse(previousReportFile);
                  Set<ModuleRevisionId> previousDepSet = new HashSet<>(
                          Arrays.asList(parser.getDependencyRevisionIds()));
                  hasChanged = !previousDepSet.equals(getModuleRevisionIds());
              } catch (Exception e) {
                  Message.warn("Error while parsing configuration resolve report "
                          + previousReportFile.getAbsolutePath(), e);
                  hasChanged = Boolean.TRUE;
              }
          } else {
              hasChanged = Boolean.TRUE;
          }
      }
  
      /**
       * @pre checkIfChanged has been called.
       * @return boolean
       */
      public boolean hasChanged() {
          return hasChanged;
      }
  
      /**
       * Returns all non evicted and non error dependency mrids The returned set is ordered so that a
       * dependency will always be found before their own dependencies
       *
       * @return all non evicted and non error dependency mrids
       */
      public Set<ModuleRevisionId> getModuleRevisionIds() {
          Set<ModuleRevisionId> mrids = new LinkedHashSet<>();
          for (IvyNode node : getDependencies()) {
              if (!node.isEvicted(getConfiguration()) && !node.hasProblem()) {
                  mrids.add(node.getResolvedId());
              }
          }
          return mrids;
      }
  
      public void addDependency(IvyNode node) {
          dependencies.put(node.getId(), node);
          dependencies.put(node.getResolvedId(), node);
          dependencyReports.put(node, Collections.<ArtifactDownloadReport> emptyList());
      }
  
      public void updateDependency(ModuleRevisionId mrid, IvyNode node) {
          dependencies.put(mrid, node);
      }
  
      public void addDependency(IvyNode node, DownloadReport report) {
          dependencies.put(node.getId(), node);
          dependencies.put(node.getResolvedId(), node);
          List<ArtifactDownloadReport> adrs = new ArrayList<>();
          for (Artifact artifact : node.getArtifacts(conf)) {
              ArtifactDownloadReport artifactReport = report.getArtifactReport(artifact);
              if (artifactReport != null) {
                  adrs.add(artifactReport);
              } else {
                  Message.debug("no report found for " + artifact);
              }
          }
          dependencyReports.put(node, adrs);
      }
  
      public String getConfiguration() {
          return conf;
      }
  
      public Date getDate() {
          return date;
      }
  
      public ModuleDescriptor getModuleDescriptor() {
          return md;
      }
  
      public ResolveOptions getResolveOptions()  {
          return options;
      }
  
      public IvyNode[] getUnresolvedDependencies() {
          List<IvyNode> unresolved = new ArrayList<>();
          for (IvyNode node : getDependencies()) {
              if (node.hasProblem()) {
                  unresolved.add(node);
              }
          }
          return unresolved.toArray(new IvyNode[unresolved.size()]);
      }
  
      private Collection<IvyNode> getDependencies() {
          return new LinkedHashSet<>(dependencies.values());
      }
  
      public IvyNode[] getEvictedNodes() {
          List<IvyNode> evicted = new ArrayList<>();
          for (IvyNode node : getDependencies()) {
              if (node.isEvicted(conf)) {
                  evicted.add(node);
              }
          }
          return evicted.toArray(new IvyNode[evicted.size()]);
      }
  
      private Set<ModuleRevisionId> getEvictedMrids() {
          Set<ModuleRevisionId> evicted = new LinkedHashSet<>();
          for (IvyNode node : getEvictedNodes()) {
              evicted.add(node.getId());
          }
          return evicted;
      }
  
      public IvyNode[] getDownloadedNodes() {
          List<IvyNode> downloaded = new ArrayList<>();
          for (IvyNode node : getDependencies()) {
              if (node.isDownloaded() && node.getRealNode() == node) {
                  downloaded.add(node);
              }
          }
          return downloaded.toArray(new IvyNode[downloaded.size()]);
      }
  
      public IvyNode[] getSearchedNodes() {
          List<IvyNode> downloaded = new ArrayList<>();
          for (IvyNode node : getDependencies()) {
              if (node.isSearched() && node.getRealNode() == node) {
                  downloaded.add(node);
              }
          }
          return downloaded.toArray(new IvyNode[downloaded.size()]);
      }
  
      public ArtifactDownloadReport[] getDownloadReports(ModuleRevisionId mrid) {
          Collection<ArtifactDownloadReport> col = dependencyReports.get(getDependency(mrid));
          if (col == null) {
              return new ArtifactDownloadReport[0];
          }
          return col.toArray(new ArtifactDownloadReport[col.size()]);
      }
  
      public IvyNode getDependency(ModuleRevisionId mrid) {
          return dependencies.get(mrid);
      }
  
      /**
       * gives all the modules ids concerned by this report, from the most dependent to the least one
       *
       * @return a list of ModuleId
       */
      public List<ModuleId> getModuleIds() {
          if (modulesIds == null) {
              List<IvyNode> sortedDependencies = resolveEngine.getSortEngine().sortNodes(
                  getDependencies(), SortOptions.SILENT);
              Collections.reverse(sortedDependencies);
              for (IvyNode dependency : sortedDependencies) {
                  ModuleId mid = dependency.getResolvedId().getModuleId();
                  Collection<IvyNode> deps = modulesIdsMap.get(mid);
                  if (deps == null) {
                      deps = new LinkedHashSet<>();
                      modulesIdsMap.put(mid, deps);
                  }
                  deps.add(dependency);
              }
              modulesIds = new ArrayList<>(modulesIdsMap.keySet());
          }
          return Collections.unmodifiableList(modulesIds);
      }
  
      public Collection<IvyNode> getNodes(ModuleId mid) {
          if (modulesIds == null) {
              getModuleIds();
          }
          return modulesIdsMap.get(mid);
      }
  
      public ResolveEngine getResolveEngine() {
          return resolveEngine;
      }
  
      public int getArtifactsNumber() {
          int total = 0;
          for (Collection<ArtifactDownloadReport> reports : dependencyReports.values()) {
              total += reports == null ? 0 : reports.size();
          }
          return total;
      }
  
      /**
       * Get every report on the download requests.
       *
       * @return the list of reports, never <code>null</code>
       */
      public ArtifactDownloadReport[] getAllArtifactsReports() {
          return getArtifactsReports(null, true);
      }
  
      /**
       * Get the report on the download requests. The list of download report can be restricted to a
       * specific download status, and also remove the download report for the evicted modules.
       *
       * @param downloadStatus
       *            the status of download to retrieve. Set it to <code>null</code> for no restriction
       *            on the download status
       * @param withEvicted
       *            set it to <code>true</code> if the report for the evicted modules have to be
       *            retrieved.
       * @return the list of reports, never <code>null</code>
       * @see ArtifactDownloadReport
       */
      public ArtifactDownloadReport[] getArtifactsReports(DownloadStatus downloadStatus,
              boolean withEvicted) {
          Collection<ArtifactDownloadReport> all = new LinkedHashSet<>();
          Collection<ModuleRevisionId> evictedMrids = null;
          if (!withEvicted) {
              evictedMrids = getEvictedMrids();
          }
          for (Collection<ArtifactDownloadReport> reports : dependencyReports.values()) {
              for (ArtifactDownloadReport report : reports) {
                  if (downloadStatus != null && report.getDownloadStatus() != downloadStatus) {
                      continue;
                  }
                  if (withEvicted
                          || !evictedMrids.contains(report.getArtifact().getModuleRevisionId())) {
                      all.add(report);
                  }
              }
          }
          return all.toArray(new ArtifactDownloadReport[all.size()]);
      }
  
      /**
       * Get the report on the successful download requests with the evicted modules
       *
       * @return the list of reports, never <code>null</code>
       */
      public ArtifactDownloadReport[] getDownloadedArtifactsReports() {
          return getArtifactsReports(DownloadStatus.SUCCESSFUL, true);
      }
  
      /**
       * Get the report on the failed download requests with the evicted modules
       *
       * @return the list of reports, never <code>null</code>
       */
      public ArtifactDownloadReport[] getFailedArtifactsReports() {
          ArtifactDownloadReport[] allFailedReports = getArtifactsReports(DownloadStatus.FAILED, true);
          return filterOutMergedArtifacts(allFailedReports);
      }
  
      public boolean hasError() {
          return getUnresolvedDependencies().length > 0 || getFailedArtifactsReports().length > 0;
      }
  
      public int getNodesNumber() {
          return getDependencies().size();
      }
  
      public static ArtifactDownloadReport[] filterOutMergedArtifacts(
              ArtifactDownloadReport[] allFailedReports) {
          Collection<ArtifactDownloadReport> adrs = new ArrayList<>(
                  Arrays.asList(allFailedReports));
          Iterator<ArtifactDownloadReport> iterator = adrs.iterator();
          while (iterator.hasNext()) {
              ArtifactDownloadReport adr = iterator.next();
  
              if (adr.getArtifact().getExtraAttribute("ivy:merged") != null) {
                  iterator.remove();
              }
          }
          return adrs.toArray(new ArtifactDownloadReport[adrs.size()]);
      }
  
  }
  