content: /*
   * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package openjdk.tools.sjavac.comp.dependencies;
  
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
  import jdkx.tools.JavaFileManager.Location;
  import jdkx.tools.JavaFileObject;
  import jdkx.tools.StandardLocation;
  
  import openjdk.source.util.TaskEvent;
  import openjdk.source.util.TaskListener;
  import openjdk.tools.javac.code.Kinds.Kind;
  import openjdk.tools.javac.code.Symbol;
  import openjdk.tools.javac.code.Symbol.ClassSymbol;
  import openjdk.tools.javac.code.Symbol.TypeSymbol;
  import openjdk.tools.javac.code.Type;
  import openjdk.tools.javac.util.Context;
  import openjdk.tools.javac.util.DefinedBy;
  import openjdk.tools.javac.util.DefinedBy.Api;
  import openjdk.tools.javac.util.Dependencies.GraphDependencies;
  import openjdk.tools.javac.util.Dependencies.GraphDependencies.CompletionNode;
  import openjdk.tools.javac.util.GraphUtils.Node;
  import openjdk.tools.sjavac.Util;
  import openjdk.tools.sjavac.comp.JavaFileObjectWithLocation;
  import openjdk.tools.sjavac.comp.PubAPIs;
  
  
  public class NewDependencyCollector implements TaskListener {
  
      private final Context context;
      private final Collection<JavaFileObject> explicitJFOs;
  
      private Map<String, Map<String, Set<String>>> deps;
      private Map<String, Map<String, Set<String>>> cpDeps;
  
      public NewDependencyCollector(Context context,
                                    Collection<JavaFileObject> explicitJFOs) {
          this.context = context;
          this.explicitJFOs = explicitJFOs;
      }
  
      @Override
      @DefinedBy(Api.COMPILER_TREE)
      public void finished(TaskEvent e) {
          if (e.getKind() == TaskEvent.Kind.COMPILATION) {
              collectPubApisOfDependencies(context, explicitJFOs);
              deps = getDependencies(context, explicitJFOs, false);
              cpDeps = getDependencies(context, explicitJFOs, true);
          }
      }
  
      public Map<String, Map<String, Set<String>>> getDependencies(boolean cp) {
          return cp ? cpDeps : deps;
      }
  
      private Set<CompletionNode> getDependencyNodes(Context context,
                                                     Collection<JavaFileObject> explicitJFOs,
                                                     boolean explicits) {
          GraphDependencies deps = (GraphDependencies) GraphDependencies.instance(context);
  
          return deps.getNodes()
                     .stream()
                     .map(n -> (CompletionNode) n)
                     .filter(n -> n.getClassSymbol().fullname != null)
                     .filter(n -> explicits == explicitJFOs.contains(n.getClassSymbol().classfile))
                     .collect(Collectors.toSet());
      }
  
      private void collectPubApisOfDependencies(Context context,
                                                Collection<JavaFileObject> explicitJFOs) {
          PubAPIs pubApis = PubAPIs.instance(context);
          for (CompletionNode cDepNode : getDependencyNodes(context, explicitJFOs, false)) {
              ClassSymbol cs = cDepNode.getClassSymbol().outermostClass();
              Location loc = getLocationOf(cs);
              // We're completely ignorant of PLATFORM_CLASS_PATH classes
              if (loc == StandardLocation.CLASS_PATH || loc == StandardLocation.SOURCE_PATH)
                  pubApis.visitPubapi(cs);
          }
      }
  
      private Location getLocationOf(ClassSymbol cs) {
          JavaFileObject jfo = cs.outermostClass().classfile;
          if (jfo instanceof JavaFileObjectWithLocation) {
              return ((JavaFileObjectWithLocation<?>) jfo).getLocation();
          }
  
          // jfo is most likely on PLATFORM_CLASS_PATH.
          // See notes in SmartFileManager::locWrap
  
          return null;
      }
  
      // :Package -> fully qualified class name [from] -> set of fully qualified class names [to]
      private Map<String, Map<String, Set<String>>> getDependencies(Context context,
                                                                    Collection<JavaFileObject> explicitJFOs,
                                                                    boolean cp) {
          Map<String, Map<String, Set<String>>> result = new HashMap<>();
  
          for (CompletionNode cnode : getDependencyNodes(context, explicitJFOs, true)) {
  
              String fqDep = cnode.getClassSymbol().outermostClass().flatname.toString();
              String depPkg = Util.pkgNameOfClassName(fqDep);
  
              Map<String, Set<String>> depsForThisClass = result.get(depPkg);
              if (depsForThisClass == null) {
                  result.put(depPkg, depsForThisClass = new HashMap<>());
              }
  
              Set<String> fqDeps = depsForThisClass.get(fqDep);
              if (fqDeps == null) {
                  depsForThisClass.put(fqDep, fqDeps = new HashSet<>());
              }
  
              for (Node<?,?> depNode : getAllDependencies(cnode)) {
                  CompletionNode cDepNode = (CompletionNode) depNode;
                  // Symbol is not regarded to depend on itself.
                  if (cDepNode == cnode) {
                      continue;
                  }
                  // Skip anonymous classes
                  if (cDepNode.getClassSymbol().fullname == null) {
                      continue;
                  }
                  if (isSymbolRelevant(cp, cDepNode.getClassSymbol())) {
                      fqDeps.add(cDepNode.getClassSymbol().outermostClass().flatname.toString());
                  }
              }
  
              // The completion dependency graph is not transitively closed for inheritance relations.
              // For sjavac's purposes however, a class depends on it's super super type, so below we
              // make sure that we include super types.
              for (ClassSymbol cs : allSupertypes(cnode.getClassSymbol())) {
                  if (isSymbolRelevant(cp, cs)) {
                      fqDeps.add(cs.outermostClass().flatname.toString());
                  }
              }
  
          }
          return result;
      }
  
      public boolean isSymbolRelevant(boolean cp, ClassSymbol cs) {
          Location csLoc = getLocationOf(cs);
          Location relevantLocation = cp ? StandardLocation.CLASS_PATH : StandardLocation.SOURCE_PATH;
          return csLoc == relevantLocation;
      }
  
      private Set<ClassSymbol> allSupertypes(TypeSymbol t) {
          if (t == null || !(t instanceof ClassSymbol)) {
              return Collections.emptySet();
          }
          Set<ClassSymbol> result = new HashSet<>();
          ClassSymbol cs = (ClassSymbol) t;
          result.add(cs);
          result.addAll(allSupertypes(cs.getSuperclass().tsym));
          for (Type it : cs.getInterfaces()) {
              result.addAll(allSupertypes(it.tsym));
          }
          return result;
      }
  
      private Collection<? extends Node<?, ?>> getAllDependencies(CompletionNode cnode) {
          return Stream.of(cnode.getSupportedDependencyKinds())
                       .flatMap(dk -> cnode.getDependenciesByKind(dk).stream())
                       .collect(Collectors.toSet());
      }
  }
  