<commit_before>package org.jetbrains.sbt
package project

import java.io.File

import com.intellij.openapi.externalSystem.model.project._
import com.intellij.openapi.externalSystem.model.task.{ExternalSystemTaskId, ExternalSystemTaskNotificationEvent, ExternalSystemTaskNotificationListener}
import com.intellij.openapi.externalSystem.model.{DataNode, ExternalSystemException}
import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver
import com.intellij.openapi.module.StdModuleTypes
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.sbt.project.data._
import org.jetbrains.sbt.project.module.SbtModuleType
import org.jetbrains.sbt.project.settings._
import org.jetbrains.sbt.project.sources.SharedSourcesModuleType
import org.jetbrains.sbt.project.structure._
import org.jetbrains.sbt.resolvers.SbtResolver

/**
 * @author <NAME>
 */
class SbtProjectResolver extends ExternalSystemProjectResolver[SbtExecutionSettings] {

  private var runner: SbtRunner = null

  def resolveProjectInfo(id: ExternalSystemTaskId, projectPath: String, isPreview: Boolean, settings: SbtExecutionSettings, listener: ExternalSystemTaskNotificationListener): DataNode[ProjectData] = {
    val root = {
      val file = new File(projectPath)
      if (file.isDirectory) file.getPath else file.getParent
    }

    runner = new SbtRunner(settings.vmExecutable, settings.vmOptions, settings.environment,
                           settings.customLauncher, settings.customSbtStructureDir)

    var warnings = new StringBuilder()

    val xml = runner.read(new File(root), !isPreview, settings.resolveClassifiers, settings.resolveSbtClassifiers) { message =>
      if (message.startsWith("[error] ") || message.startsWith("[warn] ")) {
        warnings ++= message
      }

      listener.onStatusChange(new ExternalSystemTaskNotificationEvent(id, message.trim))
    } match {
      case Left(errors) => errors match {
        case _ : SbtRunner.ImportCancelledException => return null
        case _ => throw new ExternalSystemException(errors)
      }
      case Right(node) => node
    }

    if (warnings.nonEmpty) {
      listener.onTaskOutput(id, WarningMessage(warnings.toString), false)
    }

    val data = StructureParser.parse(xml, new File(System.getProperty("user.home")))

    convert(root, data, settings.jdk).toDataNode
  }

  private def convert(root: String, data: Structure, jdk: Option[String]): Node[ProjectData] = {
    val projects = data.projects
    val project = data.projects.headOption.getOrElse(throw new RuntimeException("No root project found"))
    val projectNode = new ProjectNode(project.name, root, root)

    val javacOptions = project.java.map(_.options).getOrElse(Seq.empty)
    val sbtVersion = data.sbtVersion
    val projectJdk =
      if (project.android.isDefined)
        Some(ScalaProjectData.Android(project.android.get.version))
      else
        jdk map ScalaProjectData.Jdk

    projectNode.add(new ScalaProjectNode(projectJdk, javacOptions, sbtVersion, FileUtil.toSystemIndependentName(root)))

    project.play2 map {
      case play2Data => projectNode.add(new Play2ProjectNode(play2Data.keys))
    }

    val libraryNodes = createLibraries(data, projects)
    projectNode.addAll(libraryNodes)

    val moduleFilesDirectory = new File(root + "/" + Sbt.ModulesDirectory)
    val moduleNodes = createModules(projects, libraryNodes, moduleFilesDirectory)
    projectNode.addAll(moduleNodes)

    createModuleDependencies(projects, moduleNodes)

    val projectToModuleNode = projects.zip(moduleNodes).toMap
    val sharedSourceModules = externalSourceRootGroupsIn(projects).map { group =>
      createSourceModuleNodesAndDependencies(group, projectToModuleNode, libraryNodes, moduleFilesDirectory)
    }
    projectNode.addAll(sharedSourceModules.toSeq)

    projectNode.addAll(projects.map(createBuildModule(_, moduleFilesDirectory, data.localCachePath)))
    projectNode
  }

  def createModuleDependencies(projects: Seq[Project], moduleNodes: Seq[ModuleNode]): Unit = {
    projects.zip(moduleNodes).foreach { case (moduleProject, moduleNode) =>
      moduleProject.dependencies.projects.foreach { dependencyId =>
        val dependency = moduleNodes.find(_.getId == dependencyId.project).getOrElse(
          throw new ExternalSystemException("Cannot find project dependency: " + dependencyId.project))
        val data = new ModuleDependencyNode(moduleNode, dependency)
        data.setScope(scopeFor(dependencyId.configurations))
        data.setExported(true)
        moduleNode.add(data)
      }
    }
  }

  def createSourceModuleNodesAndDependencies(rootGroup: RootGroup,
                                             projectToModuleNode: Map[Project, ModuleNode],
                                             libraryNodes: Seq[LibraryNode],
                                             moduleFilesDirectory: File): ModuleNode = {
    val sourceModuleNode = {
      val node = createSourceModule(rootGroup, moduleFilesDirectory)
      val commonDependencies = commonDependenciesIn(rootGroup.projects)
      node.addAll(createLibraryDependencies(commonDependencies)(node, libraryNodes.map(_.data)))
      node
    }

    rootGroup.projects.map(projectToModuleNode).foreach { ownerModule =>
      ownerModule.add(new ModuleDependencyNode(ownerModule, sourceModuleNode))
    }

    sourceModuleNode
  }

  def createSourceModule(group: RootGroup, moduleFilesDirectory: File): ModuleNode = {
    val moduleNode = new ModuleNode(SharedSourcesModuleType.instance.getId,
      group.name, group.name, moduleFilesDirectory.path, group.base.canonicalPath)

    val contentRootNode = {
      val node = new ContentRootNode(group.base.path)

      group.roots.foreach { root =>
        val sourceType = (root.scope, root.kind) match {
          case (Root.Scope.Compile, Root.Kind.Sources) => ExternalSystemSourceType.SOURCE
          case (Root.Scope.Compile, Root.Kind.Resources) => ExternalSystemSourceType.RESOURCE
          case (Root.Scope.Test, Root.Kind.Sources) => ExternalSystemSourceType.TEST
          case (Root.Scope.Test, Root.Kind.Resources) => ExternalSystemSourceType.TEST_RESOURCE
        }
        node.storePath(sourceType, root.directory.path)
      }

      node
    }

    moduleNode.add(contentRootNode)

    moduleNode
  }

  def commonDependenciesIn(projects: Seq[Project]): Seq[ModuleDependency] = {
    projects.flatMap(_.dependencies.modules).filter { dependency =>
      projects.forall(_.dependencies.modules.contains(dependency))
    }
  }

  def createModules(projects: Seq[Project], libraryNodes: Seq[LibraryNode], moduleFilesDirectory: File): Seq[ModuleNode] = {
    val unmanagedSourcesAndDocsLibrary = libraryNodes.map(_.data).find(_.getExternalName == Sbt.UnmanagedSourcesAndDocsName)
    projects.map { project =>
      val moduleNode = createModule(project, moduleFilesDirectory)
      moduleNode.add(createContentRoot(project))
      moduleNode.addAll(createLibraryDependencies(project.dependencies.modules)(moduleNode, libraryNodes.map(_.data)))
      moduleNode.addAll(project.scala.map(createScalaSdk(project, _)).toSeq)
      moduleNode.addAll(project.android.map(createFacet(project, _)).toSeq)<commit_msg>SBT: add unique rather than common dependencies to shared source modules<commit_after>package org.jetbrains.sbt
package project

import java.io.File

import com.intellij.openapi.externalSystem.model.project._
import com.intellij.openapi.externalSystem.model.task.{ExternalSystemTaskId, ExternalSystemTaskNotificationEvent, ExternalSystemTaskNotificationListener}
import com.intellij.openapi.externalSystem.model.{DataNode, ExternalSystemException}
import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver
import com.intellij.openapi.module.StdModuleTypes
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.sbt.project.data._
import org.jetbrains.sbt.project.module.SbtModuleType
import org.jetbrains.sbt.project.settings._
import org.jetbrains.sbt.project.sources.SharedSourcesModuleType
import org.jetbrains.sbt.project.structure._
import org.jetbrains.sbt.resolvers.SbtResolver

/**
 * @author <NAME>
 */
class SbtProjectResolver extends ExternalSystemProjectResolver[SbtExecutionSettings] {

  private var runner: SbtRunner = null

  def resolveProjectInfo(id: ExternalSystemTaskId, projectPath: String, isPreview: Boolean, settings: SbtExecutionSettings, listener: ExternalSystemTaskNotificationListener): DataNode[ProjectData] = {
    val root = {
      val file = new File(projectPath)
      if (file.isDirectory) file.getPath else file.getParent
    }

    runner = new SbtRunner(settings.vmExecutable, settings.vmOptions, settings.environment,
                           settings.customLauncher, settings.customSbtStructureDir)

    var warnings = new StringBuilder()

    val xml = runner.read(new File(root), !isPreview, settings.resolveClassifiers, settings.resolveSbtClassifiers) { message =>
      if (message.startsWith("[error] ") || message.startsWith("[warn] ")) {
        warnings ++= message
      }

      listener.onStatusChange(new ExternalSystemTaskNotificationEvent(id, message.trim))
    } match {
      case Left(errors) => errors match {
        case _ : SbtRunner.ImportCancelledException => return null
        case _ => throw new ExternalSystemException(errors)
      }
      case Right(node) => node
    }

    if (warnings.nonEmpty) {
      listener.onTaskOutput(id, WarningMessage(warnings.toString), false)
    }

    val data = StructureParser.parse(xml, new File(System.getProperty("user.home")))

    convert(root, data, settings.jdk).toDataNode
  }

  private def convert(root: String, data: Structure, jdk: Option[String]): Node[ProjectData] = {
    val projects = data.projects
    val project = data.projects.headOption.getOrElse(throw new RuntimeException("No root project found"))
    val projectNode = new ProjectNode(project.name, root, root)

    val javacOptions = project.java.map(_.options).getOrElse(Seq.empty)
    val sbtVersion = data.sbtVersion
    val projectJdk =
      if (project.android.isDefined)
        Some(ScalaProjectData.Android(project.android.get.version))
      else
        jdk map ScalaProjectData.Jdk

    projectNode.add(new ScalaProjectNode(projectJdk, javacOptions, sbtVersion, FileUtil.toSystemIndependentName(root)))

    project.play2 map {
      case play2Data => projectNode.add(new Play2ProjectNode(play2Data.keys))
    }

    val libraryNodes = createLibraries(data, projects)
    projectNode.addAll(libraryNodes)

    val moduleFilesDirectory = new File(root + "/" + Sbt.ModulesDirectory)
    val moduleNodes = createModules(projects, libraryNodes, moduleFilesDirectory)
    projectNode.addAll(moduleNodes)

    createModuleDependencies(projects, moduleNodes)

    val projectToModuleNode = projects.zip(moduleNodes).toMap
    val sharedSourceModules = externalSourceRootGroupsIn(projects).map { group =>
      createSourceModuleNodesAndDependencies(group, projectToModuleNode, libraryNodes, moduleFilesDirectory)
    }
    projectNode.addAll(sharedSourceModules.toSeq)

    projectNode.addAll(projects.map(createBuildModule(_, moduleFilesDirectory, data.localCachePath)))
    projectNode
  }

  def createModuleDependencies(projects: Seq[Project], moduleNodes: Seq[ModuleNode]): Unit = {
    projects.zip(moduleNodes).foreach { case (moduleProject, moduleNode) =>
      moduleProject.dependencies.projects.foreach { dependencyId =>
        val dependency = moduleNodes.find(_.getId == dependencyId.project).getOrElse(
          throw new ExternalSystemException("Cannot find project dependency: " + dependencyId.project))
        val data = new ModuleDependencyNode(moduleNode, dependency)
        data.setScope(scopeFor(dependencyId.configurations))
        data.setExported(true)
        moduleNode.add(data)
      }
    }
  }

  def createSourceModuleNodesAndDependencies(rootGroup: RootGroup,
                                             projectToModuleNode: Map[Project, ModuleNode],
                                             libraryNodes: Seq[LibraryNode],
                                             moduleFilesDirectory: File): ModuleNode = {
    val sourceModuleNode = {
      val node = createSourceModule(rootGroup, moduleFilesDirectory)
      val uniqueDependencies = rootGroup.projects.flatMap(_.dependencies.modules).distinct
      node.addAll(createLibraryDependencies(uniqueDependencies)(node, libraryNodes.map(_.data)))
      node
    }

    rootGroup.projects.map(projectToModuleNode).foreach { ownerModule =>
      ownerModule.add(new ModuleDependencyNode(ownerModule, sourceModuleNode))
    }

    sourceModuleNode
  }

  def createSourceModule(group: RootGroup, moduleFilesDirectory: File): ModuleNode = {
    val moduleNode = new ModuleNode(SharedSourcesModuleType.instance.getId,
      group.name, group.name, moduleFilesDirectory.path, group.base.canonicalPath)

    val contentRootNode = {
      val node = new ContentRootNode(group.base.path)

      group.roots.foreach { root =>
        val sourceType = (root.scope, root.kind) match {
          case (Root.Scope.Compile, Root.Kind.Sources) => ExternalSystemSourceType.SOURCE
          case (Root.Scope.Compile, Root.Kind.Resources) => ExternalSystemSourceType.RESOURCE
          case (Root.Scope.Test, Root.Kind.Sources) => ExternalSystemSourceType.TEST
          case (Root.Scope.Test, Root.Kind.Resources) => ExternalSystemSourceType.TEST_RESOURCE
        }
        node.storePath(sourceType, root.directory.path)
      }

      node
    }

    moduleNode.add(contentRootNode)

    moduleNode
  }

  def createModules(projects: Seq[Project], libraryNodes: Seq[LibraryNode], moduleFilesDirectory: File): Seq[ModuleNode] = {
    val unmanagedSourcesAndDocsLibrary = libraryNodes.map(_.data).find(_.getExternalName == Sbt.UnmanagedSourcesAndDocsName)
    projects.map { project =>
      val moduleNode = createModule(project, moduleFilesDirectory)
      moduleNode.add(createContentRoot(project))
      moduleNode.addAll(createLibraryDependencies(project.dependencies.modules)(moduleNode, libraryNodes.map(_.data)))
      moduleNode.addAll(project.scala.map(createScalaSdk(project, _)).toSeq)
      moduleNode.addAll(project.android.map(createFacet(project, _)).toSeq)
{'fraction_non_alphanumeric': 0.09497361843932241, 'fraction_numerical': 0.0005554012774229381, 'mean_word_length': 4.042002100105005, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 38, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4260187', 'n_tokens_mistral': 4010, 'n_tokens_neox': 3872, 'n_words': 704}