<commit_before>package xitrum.routing

import java.io.File
import scala.collection.mutable.{ArrayBuffer, Map => MMap}
import scala.reflect.runtime.universe
import scala.util.control.NonFatal

import sclasner.{FileEntry, Scanner}

import xitrum.{Action, Config, Log, SockJsAction}
import xitrum.annotation._
import xitrum.sockjs.SockJsPrefix

case class DiscoveredAcc(
  xitrumVersion:             String,
  normalRoutes:              SerializableRouteCollection,
  sockJsWithoutPrefixRoutes: SerializableRouteCollection,
  sockJsMap:                 Map[String, SockJsClassAndOptions],
  swaggerMap:                Map[Class[_ <: Action], Swagger]
)

/** Scan all classes to collect routes from actions. */
object RouteCollector {
  import ActionAnnotations._

  def deserializeCacheFileOrRecollect(cachedFileName: String, cl: ClassLoader): DiscoveredAcc = {
    var acc = DiscoveredAcc(
      "<Invalid Xitrum version>",
      new SerializableRouteCollection,
      new SerializableRouteCollection,
      Map.empty[String, SockJsClassAndOptions],
      Map.empty[Class[_ <: Action], Swagger]
    )

    // Only serialize/deserialize ActionTreeBuilder, which represents
    // traits/classes extending Action, and their relationship. The relationship
    // is for the (Swagger) annotation inheritance feature. The traits/classes
    // are then loaded to get annotations.
    val xitrumVersion     = xitrum.version.toString
    val actionTreeBuilder = Scanner.foldLeft(cachedFileName, new ActionTreeBuilder(xitrumVersion), discovered(cl))

    if (actionTreeBuilder.xitrumVersion != xitrumVersion) {
      // The caller should see that the Xitrum version is invalid and act properly
      acc
    } else {
      val ka = actionTreeBuilder.getConcreteActionsAndAnnotations(cl)
      ka.foreach { case (klass, annotations) =>
        acc = processAnnotations(acc, klass, annotations)
      }
      DiscoveredAcc(xitrumVersion, acc.normalRoutes, acc.sockJsWithoutPrefixRoutes, acc.sockJsMap, acc.swaggerMap)
    }
  }

  //----------------------------------------------------------------------------

  private def discovered(cl: ClassLoader)(acc: ActionTreeBuilder, entry: FileEntry): ActionTreeBuilder = {
    // At ActionTreeBuilder, we can't use ASM or Javassist to get annotations
    // (because they don't understand Scala annotations), we have to actually
    // classes anyway, here we guess class name from .class file name and
    // load the class.

    if (!entry.relPath.endsWith(".class")) return acc

    // Optimize: Ignore standard Java, standard Scala, Netty classes etc.; these can be thousands
    val relPath = if (File.separatorChar != '/') entry.relPath.replace(File.separatorChar, '/') else entry.relPath
    if (IgnoredPackages.isIgnored(relPath)) return acc

    try {
      val withoutExt      = relPath.substring(0, relPath.length - ".class".length)
      val className       = withoutExt.replace('/', '.')
      val klass           = cl.loadClass(className)
      val superclass      = klass.getSuperclass.asInstanceOf[Class[_]]
      val superclassNameo = if (superclass == null) None else Some(superclass.getName)
      acc.addBranches(klass.getName, superclassNameo, klass.getInterfaces.map(_.getName))
    } catch {
      // Probably java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet
      case e: java.lang.Error =>
        acc

      // Probably the .class file name -> class name guess was wrong
      case NonFatal(e) =>
        acc
    }
  }

  private def processAnnotations(
      acc:         DiscoveredAcc,
      klass:       Class[_ <: Action],<commit_msg>Improve comment for RouteCollector<commit_after>package xitrum.routing

import java.io.File
import scala.collection.mutable.{ArrayBuffer, Map => MMap}
import scala.reflect.runtime.universe
import scala.util.control.NonFatal

import sclasner.{FileEntry, Scanner}

import xitrum.{Action, Config, Log, SockJsAction}
import xitrum.annotation._
import xitrum.sockjs.SockJsPrefix

case class DiscoveredAcc(
  xitrumVersion:             String,
  normalRoutes:              SerializableRouteCollection,
  sockJsWithoutPrefixRoutes: SerializableRouteCollection,
  sockJsMap:                 Map[String, SockJsClassAndOptions],
  swaggerMap:                Map[Class[_ <: Action], Swagger]
)

/** Scan all classes to collect routes from actions. */
object RouteCollector {
  import ActionAnnotations._

  def deserializeCacheFileOrRecollect(cachedFileName: String, cl: ClassLoader): DiscoveredAcc = {
    var acc = DiscoveredAcc(
      "<Invalid Xitrum version>",
      new SerializableRouteCollection,
      new SerializableRouteCollection,
      Map.empty[String, SockJsClassAndOptions],
      Map.empty[Class[_ <: Action], Swagger]
    )

    // Only serialize/deserialize ActionTreeBuilder, which represents
    // traits/classes extending Action, and their relationship. The relationship
    // is for the (Swagger) annotation inheritance feature. The traits/classes
    // are then loaded to get annotations.
    val xitrumVersion     = xitrum.version.toString
    val actionTreeBuilder = Scanner.foldLeft(cachedFileName, new ActionTreeBuilder(xitrumVersion), discovered(cl))

    if (actionTreeBuilder.xitrumVersion != xitrumVersion) {
      // The caller should see that the Xitrum version is invalid and act properly
      acc
    } else {
      val ka = actionTreeBuilder.getConcreteActionsAndAnnotations(cl)
      ka.foreach { case (klass, annotations) =>
        acc = processAnnotations(acc, klass, annotations)
      }
      DiscoveredAcc(xitrumVersion, acc.normalRoutes, acc.sockJsWithoutPrefixRoutes, acc.sockJsMap, acc.swaggerMap)
    }
  }

  //----------------------------------------------------------------------------

  private def discovered(cl: ClassLoader)(acc: ActionTreeBuilder, entry: FileEntry): ActionTreeBuilder = {
    // At ActionTreeBuilder, we can't use ASM or Javassist to get annotations,
    // because they don't understand Scala annotations. We have to actually load
    // the class and use Scala reflection to get annotations. Here we guess class
    // name from .class file name and load the class using the provided ClassLoader.

    if (!entry.relPath.endsWith(".class")) return acc

    // Optimize: Ignore standard Java, standard Scala, Netty classes etc.; these can be thousands
    val relPath = if (File.separatorChar != '/') entry.relPath.replace(File.separatorChar, '/') else entry.relPath
    if (IgnoredPackages.isIgnored(relPath)) return acc

    try {
      val withoutExt      = relPath.substring(0, relPath.length - ".class".length)
      val className       = withoutExt.replace('/', '.')
      val klass           = cl.loadClass(className)
      val superclass      = klass.getSuperclass.asInstanceOf[Class[_]]
      val superclassNameo = if (superclass == null) None else Some(superclass.getName)
      acc.addBranches(klass.getName, superclassNameo, klass.getInterfaces.map(_.getName))
    } catch {
      // Probably java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet
      case e: java.lang.Error =>
        acc

      // Probably the .class file name -> class name guess was wrong
      case NonFatal(e) =>
        acc
    }
  }

  private def processAnnotations(
      acc:         DiscoveredAcc,
      klass:       Class[_ <: Action],
{'fraction_non_alphanumeric': 0.1089244225775591, 'fraction_numerical': 0.00027333606669400026, 'mean_word_length': 3.020879120879121, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4623589', 'n_tokens_mistral': 2011, 'n_tokens_neox': 1980, 'n_words': 556}