All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fm.common.ClassUtil.scala Maven / Gradle / Ivy

/*
 * Copyright 2014 Frugal Mechanic (http://frugalmechanic.com)
 *
 * Licensed 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
 *
 *     http://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 fm.common

import fm.common.JavaConverters._
import java.lang.annotation.Annotation
import java.lang.reflect.{Method, Modifier}
import java.net.{JarURLConnection, URLConnection, URLDecoder}
import java.io.{File, InputStream}
import java.nio.file.Path
import java.util.jar.{JarEntry, JarFile}
import scala.reflect.{ClassTag, classTag}

/**
 * This contains utility methods for scanning Classes or Files on the classpath.
 *
 * Originally we used the classpath scanning functionality in the Spring Framework
 * and then later switched to the Reflections library (https://code.google.com/p/reflections/)
 * to avoid the dependency on Spring.  At some point we ran into issues with the Reflections
 * library not properly detecting classes so I ended up writing this as a replacement.
 */
object ClassUtil extends Logging {

  // Note: The classpath separator is *ALWAYS* a / and should not be File.separator
  // See:
  //   https://www.atlassian.com/blog/archives/how_to_use_file_separator_when
  //   http://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#getResource-java.lang.String-
  private def classpathSeparator: String = "/"

  def classForName(cls: String): Class[_] = classForName(cls, defaultClassLoader)
  def classForName(cls: String, classLoader: ClassLoader): Class[_] = Class.forName(cls, true, classLoader)

  def getClassForName(cls: String): Option[Class[_]] = getClassForName(cls, defaultClassLoader)

  def getClassForName(cls: String, classLoader: ClassLoader): Option[Class[_]] = {
    try {
      Option(Class.forName(cls, true, classLoader))
    } catch {
      case _: ClassNotFoundException => None
    }
  }

  def companionObject(cls: Class[_]): AnyRef = companionObjectAs[AnyRef](cls)

  def companionObjectAs[T <: AnyRef : ClassTag](cls: Class[_]): T = {
    companionObjectAs(cls, classTag[T].runtimeClass.asInstanceOf[Class[T]])
  }

  def companionObjectAs[T <: AnyRef](cls: Class[_], asCls: Class[T]): T = {
    val objectCls: Class[_] = companionObjectClass(cls)
    require(asCls.isAssignableFrom(objectCls), s"objectCls: $objectCls is not a asCls: $asCls")
    scalaObject(objectCls).asInstanceOf[T]
  }

  def getCompanionObject(cls: Class[_]): Option[AnyRef] = getCompanionObjectAs[AnyRef](cls)

  def getCompanionObjectAs[T <: AnyRef : ClassTag](cls: Class[_]): Option[T] = {
    getCompanionObjectAs(cls, classTag[T].runtimeClass.asInstanceOf[Class[T]])
  }

  def getCompanionObjectAs[T <: AnyRef](cls: Class[_], asCls: Class[T]): Option[T] = {
    val objectCls: Option[Class[_]] = getCompanionObjectClass(cls)

    if (objectCls.isEmpty || !asCls.isAssignableFrom(objectCls.get)) None
    else getScalaObjectAs(objectCls.get, asCls)
  }

  /**
   * Lookup the companion object class for a class
   */
  def companionObjectClass(cls: String): Class[_] = {
    companionObjectClass(cls, defaultClassLoader)
  }

  /**
   * Lookup the companion object class for a class
   */
  def companionObjectClass(cls: String, classLoader: ClassLoader): Class[_] = {
    getCompanionObjectClass(cls, classLoader).getOrElse{ throw new ClassNotFoundException(s"No companion object class for $cls") }
  }

  /**
   * Lookup the companion object class for a class
   */
  def companionObjectClass(cls: Class[_]): Class[_] = {
    getCompanionObjectClass(cls).getOrElse{ throw new ClassNotFoundException(s"No companion object class for $cls") }
  }

  /**
   * Lookup the companion object class for a class
   */
  def getCompanionObjectClass(cls: String): Option[Class[_]] = {
    getCompanionObjectClass(cls, defaultClassLoader)
  }

  /**
   * Lookup the companion object class for a class
   */
  def getCompanionObjectClass(cls: String, classLoader: ClassLoader): Option[Class[_]] = {
    getClassForName(cls, classLoader).flatMap{ getCompanionObjectClass }
  }

  def getCompanionObjectClass(cls: Class[_]): Option[Class[_]] = {
    if (isScalaObject(cls)) Some(cls)
    else if (!cls.getName.endsWith("$")) getCompanionObjectClass(cls.getName+"$", cls.getClassLoader)
    else None
  }

  /**
   * Does this class represent a Scala object
   * @param cls The fully qualified name of the class to check (Note: should end with a '$' character)
   * @return
   */
  def isScalaObject(cls: String): Boolean = {
    isScalaObject(cls, defaultClassLoader)
  }

  /**
   * Does this class represent a Scala object
   * @param cls The fully qualified name of the class to check (Note: should end with a '$' character)
   * @return
   */
  def isScalaObject(cls: String, classLoader: ClassLoader): Boolean = {
    try {
      val c: Class[_] = classForName(cls, classLoader)
      isScalaObject(c)
    } catch {
      case _: ClassNotFoundException => false // Class does not exist
    }
  }

  /**
   * Is this the class for a Scala Object?
   */
  def isScalaObject(cls: Class[_]): Boolean = {
    try {
      cls.getField("MODULE$")
      true
    } catch {
      case _: NoSuchFieldException => false
    }
  }

  /**
   * Returns the Scala object instance for this class
   */
  def scalaObject(objectCls: Class[_]): AnyRef = scalaObjectAs[AnyRef](objectCls)

  /**
   * Returns the Scala object instance for this class
   */
  def scalaObjectAs[T <: AnyRef : ClassTag](objectCls: Class[_]): T = {
    scalaObjectAs(objectCls, classTag[T].runtimeClass.asInstanceOf[Class[T]])
  }

  /**
   * Returns the Scala object instance for this class
   */
  def scalaObjectAs[T <: AnyRef](objectCls: Class[_], asCls: Class[T]): T = {
    require(asCls.isAssignableFrom(objectCls), s"objectCls: $objectCls is not a asCls: $asCls")
    objectCls.getField("MODULE$").get(objectCls).asInstanceOf[T]
  }

  /**
   * Returns the Scala object instance for this class (if it is the class of a Scala object)
   */
  def getScalaObject(objectCls: Class[_]): Option[AnyRef] = getScalaObjectAs[AnyRef](objectCls)

  /**
   * Returns the Scala object instance for this class (if it is the class of a Scala object)
   */
  def getScalaObjectAs[T <: AnyRef : ClassTag](objectCls: Class[_]): Option[T] = {
    getScalaObjectAs(objectCls, classTag[T].runtimeClass.asInstanceOf[Class[T]])
  }

  /**
   * Returns the Scala object instance for this class (if it is the class of a Scala object)
   */
  def getScalaObjectAs[T <: AnyRef](objectCls: Class[_], asCls: Class[T]): Option[T] = {
    try {
      val res: AnyRef = objectCls.getField("MODULE$").get(objectCls)
      if (res.isNotNull && asCls.isAssignableFrom(res.getClass)) Some(res.asInstanceOf[T]) else None
    } catch {
      case _: NoSuchFieldException => None   // No MODULE$ field.  Not a Scala object?
      case _: ClassNotFoundException => None // Object does not exist
      case _: ClassCastException => None     // Object is not an instance of T
    }
  }

  /**
   * Can an instance of this class be created using a zero-args constructor?
   */
  def canCreateInstanceOf(cls: Class[_]): Boolean = {
    try {
      cls.getDeclaredConstructor()
      true
    } catch {
      case _: NoSuchMethodException => false
    }
  }

  /**
   * Can an instance of this class be created using a zero-args constructor?
   */
  def canCreateInstanceOfOrIsObject(cls: Class[_]): Boolean = {
    if (isScalaObject(cls)) true
    else canCreateInstanceOf(cls)
  }

  /**
   * Creates a new instance of a class using a 0-args constructor or returns the Scala object instance of this class
   */
  def newInstanceOrObject[T <: AnyRef](cls: Class[T]): T = {
    if (isScalaObject(cls)) scalaObjectAs(cls, cls)
    else cls.getDeclaredConstructor().newInstance()
  }

  /**
   * Creates a new instance of a class using a 0-args constructor or returns the Scala object instance of this class
   */
  def newInstanceOrObjectAs[T <: AnyRef : ClassTag](cls: Class[_]): T = {
    newInstanceOrObjectAs(cls, classTag[T].runtimeClass.asInstanceOf[Class[T]])
  }

  /**
   * Creates a new instance of a class using a 0-args constructor or returns the Scala object instance of this class
   */
  def newInstanceOrObjectAs[T <: AnyRef](cls: Class[_], asCls: Class[T]): T = {
    require(asCls.isAssignableFrom(cls), s"cls: $cls is not a asCls: $asCls")

    if (isScalaObject(cls)) scalaObjectAs(cls, asCls)
    else cls.getDeclaredConstructor().newInstance().asInstanceOf[T]
  }

  /**
   * Creates a new instance of a class using a 0-args constructor or returns the Scala object instance of this class
   */
  def getNewInstanceOrObject[T <: AnyRef](cls: Class[T]): Option[T] = {
    getNewInstanceOrObjectAs(cls, cls)
  }

  def getNewInstanceOrObjectAs[T <: AnyRef : ClassTag](cls: Class[_]): Option[T] = {
    getNewInstanceOrObjectAs(cls, classTag[T].runtimeClass.asInstanceOf[Class[T]])
  }

  /**
   * Creates a new instance of a class using a 0-args constructor or returns the Scala object instance of this class
   * @param cls The class to create an instance of (or to get the Object instance for)
   * @param asCls The return type
   * @tparam T
   * @return
   */
  def getNewInstanceOrObjectAs[T <: AnyRef](cls: Class[_], asCls: Class[T]): Option[T] = {
    if (isScalaObject(cls)) {
      getScalaObjectAs(cls, asCls)
    } else {
      try {
        if (!asCls.isAssignableFrom(cls)) None
        else Option(cls.getDeclaredConstructor().newInstance().asInstanceOf[T])
      } catch {
        case _: IllegalAccessException => None // Private Constructor
        case _: InstantiationException => None // No 0-args Constructor
        case _: NoSuchMethodException => None  // No 0-args Constructor
      }
    }
  }

  /**
   * Check if a class is loaded
   */
  def isClassLoaded(cls: String): Boolean = isClassLoaded(cls, defaultClassLoader)

  /**
   * Check if a class is loaded
   */
  def isClassLoaded(cls: String, classLoader: ClassLoader): Boolean = findLoadedClass(cls, classLoader).isDefined

  def findLoadedClass(cls: String): Option[Class[_]] = findLoadedClass(cls, defaultClassLoader)

  def findLoadedClass(cls: String, classLoader: ClassLoader): Option[Class[_]] = {
    val findLoadedClass: Method = classOf[ClassLoader].getDeclaredMethod("findLoadedClass", classOf[String])
    findLoadedClass.setAccessible(true)
    val res: Object = findLoadedClass.invoke(classLoader, cls)
    if (null == res) None else Some(res.asInstanceOf[Class[_]])
  }

  /**
   * Check if a class exists.
   */
  def classExists(cls: String): Boolean = classExists(cls, defaultClassLoader)

  /**
   * Check if a class exists.
   */
  def classExists(cls: String, classLoader: ClassLoader): Boolean = try {
    classLoader.loadClass(cls)
    true
  } catch {
    case _: ClassNotFoundException => false
  }

  /** Check if a file exists on the classpath */
  def classpathFileExists(file: String): Boolean = classpathFileExists(file, defaultClassLoader)

  /** Check if a file exists on the classpath */
  def classpathFileExists(file: String, classLoader: ClassLoader): Boolean = {
    classpathFileExists(new File(file), classLoader)
  }

  /** Check if a file exists on the classpath */
  def classpathFileExists(file: File): Boolean = classpathFileExists(file, defaultClassLoader)

  /** Check if a file exists on the classpath */
  def classpathFileExists(file: File, classLoader: ClassLoader): Boolean = {
    withClasspathURL(file, classLoader){ (url: URL) =>
      if (url.isFile) url.toFile.isFile()
      else withURLInputStream(url){ (is: InputStream) =>
        // This should work for a file
        try { is.read(); true } catch { case ex: NullPointerException => false }
      }
    }.getOrElse(false)
  }

  /** Check if a directory exists on the classpath */
  def classpathDirExists(file: String): Boolean = classpathDirExists(file, defaultClassLoader)

  /** Check if a directory exists on the classpath */
  def classpathDirExists(file: String, classLoader: ClassLoader): Boolean = {
    classpathDirExists(new File(file), classLoader)
  }

  /** Check if a directory exists on the classpath */
  def classpathDirExists(file: File): Boolean = classpathDirExists(file, defaultClassLoader)

  /** Check if a directory exists on the classpath */
  def classpathDirExists(file: File, classLoader: ClassLoader): Boolean = {
    withClasspathURL(file, classLoader){ (url: URL) =>
      if (url.isFile) url.toFile.isDirectory()
      else withURLInputStream(url){ (is: InputStream) =>
        // Not sure if there is a better way to do this -- A NullPointerException is thrown for a directory
        try { is.read(); false } catch { case ex: Exception => true }
      }
    }.getOrElse(false)
  }

  /** Lookup the lastModified timestamp for a resource on the classpath */
  def classpathLastModified(file: String): Long = classpathLastModified(file, defaultClassLoader)

  /** Lookup the lastModified timestamp for a resource on the classpath */
  def classpathLastModified(file: String, classLoader: ClassLoader): Long = {
    classpathLastModified(new File(file), classLoader)
  }

  /** Lookup the lastModified timestamp for a resource on the classpath */
  def classpathLastModified(file: File): Long = classpathLastModified(file, defaultClassLoader)

  /** Lookup the lastModified timestamp for a resource on the classpath */
  def classpathLastModified(file: File, classLoader: ClassLoader): Long = {
    withClasspathURLConnection(file, classLoader){ (conn: URLConnection) =>
      conn match {
        case j: JarURLConnection if null != j.getJarEntry => j.getJarEntry.getLastModifiedTime.toMillis
        case _ => conn.getLastModified
      }
    }.getOrElse(0L) // This default matches File.lastModified()
  }

  /** Lookup the legnth for a resource on the classpath */
  def classpathContentLength(file: String): Long = classpathContentLength(file, defaultClassLoader)

  /** Lookup the legnth for a resource on the classpath */
  def classpathContentLength(file: String, classLoader: ClassLoader): Long = {
    classpathContentLength(new File(file), classLoader)
  }

  /** Lookup the legnth for a resource on the classpath */
  def classpathContentLength(file: File): Long = classpathContentLength(file, defaultClassLoader)

  /** Lookup the legnth for a resource on the classpath */
  def classpathContentLength(file: File, classLoader: ClassLoader): Long = {
    withClasspathURLConnection(file, classLoader){ _.getContentLengthLong() }.getOrElse(0L) // This default matches File.length()
  }

  /** A helper for the above methods */
  private def withClasspathURL[T](file: File, classLoader: ClassLoader)(f: URL => T): Option[T] = {
    val path: String = file.toResourcePath.stripLeading(classpathSeparator)
    val urls: Vector[URL] = classLoader.getResources(path).asScala.toVector

    urls.headOption.map{ (url: URL) => f(url) }
  }

  /** A helper for the above methods */
  private def withClasspathURLConnection[T](file: File, classLoader: ClassLoader)(f: URLConnection => T): Option[T] = {
    withClasspathURL(file, classLoader){ (url: URL) =>
      val conn: URLConnection = url.openConnection()
      f(conn)
    }
  }

  /** A helper for the above methods */
  private def withURLInputStream[T](url: URL)(f: InputStream => T): T = {
    val is: InputStream = url.openStream()
    try {
      f(is)
    } finally {
      // close() can throw exceptions if the file doesn't exist
      try{ is.close() } catch { case _: Exception => }
    }
  }

  /**
   * Check if a class exists.  If it does not then a ClassNotFoundException is thrown.
   */
  def requireClass(cls: String, msg: => String): Unit = requireClass(cls, msg, defaultClassLoader)

  /**
   * Check if a class exists.  If it does not then a ClassNotFoundException is thrown.
   */
  def requireClass(cls: String, msg: => String, classLoader: ClassLoader): Unit = {
    if (!classExists(cls, classLoader)) throw new ClassNotFoundException(s"Missing Class: $cls - $msg")
  }

  /**
   * Find all classes annotated with a Java Annotation.
   *
   * Note: This loads ALL classes under the basePackage!
   */
  def findAnnotatedClasses[T <: Annotation](basePackage: String, annotationClass: Class[T]): Set[Class[_]] = {
    findAnnotatedClasses(basePackage, annotationClass, defaultClassLoader)
  }

  /**
   * Find all classes annotated with a Java Annotation.
   *
   * Note: This loads ALL classes under the basePackage!
   */
  def findAnnotatedClasses[T <: Annotation](basePackage: String, annotationClass: Class[T], classLoader: ClassLoader): Set[Class[_]] = {
    findClassNames(basePackage, classLoader)/*.filterNot { _.contains("$") }*/.map{ classLoader.loadClass }.filter { (c: Class[_]) =>
      c.getAnnotation(annotationClass) != null
    }
  }

  /**
   * Finds all Scala Objects that extends a trait/interface/class.
   *
   * Note: This loads ALL classes under the basePackage and uses Class.isAssignableFrom for checking.
   */
  def findImplementingObjects[T <: AnyRef](basePackage: String, clazz: Class[T]): Set[T] = {
    findImplementingObjects(basePackage, clazz, defaultClassLoader)

  }

  /**
   * Finds all Scala Objects that extends a trait/interface/class.
   *
   * Note: This loads ALL classes under the basePackage and uses Class.isAssignableFrom for checking.
   */
  def findImplementingObjects[T <: AnyRef](basePackage: String, clazz: Class[T], classLoader: ClassLoader): Set[T] = {
    findImplementingClasses(basePackage, clazz, classLoader).filter{ isScalaObject }.map{ scalaObjectAs(_, clazz) }
  }

  /**
   * Find all concrete classes that extend a trait/interface/class.
   *
   * Note: This loads ALL classes under the basePackage and uses Class.isAssignableFrom for checking.
   */
  def findImplementingClasses[T](basePackage: String, clazz: Class[T]): Set[Class[_ <: T]] = {
    findImplementingClasses[T](basePackage, clazz, defaultClassLoader)
  }

  /**
   * Find all concrete classes that extend a trait/interface/class.
   *
   * Note: This loads ALL classes under the basePackage and uses Class.isAssignableFrom for checking.
   */
  def findImplementingClasses[T](basePackage: String, clazz: Class[T], classLoader: ClassLoader): Set[Class[_ <: T]] = {
    findClassNames(basePackage, classLoader)/*.filterNot{ _.contains("$") }*/.map{ classLoader.loadClass }.filter { (c: Class[_]) =>
      clazz.isAssignableFrom(c)
    }.filterNot{ (c: Class[_]) =>
      val mods: Int = c.getModifiers()
      Modifier.isAbstract(mods) || Modifier.isInterface(mods)
    }.map{ _.asInstanceOf[Class[_ <: T]] }
  }

  /**
   * Find all class names under the base package (includes anonymous/inner/objects etc...)
   */
  def findClassNames(basePackage: String): Set[String] = findClassNames(basePackage, defaultClassLoader)

  /**
   * Find all class names under the base package (includes anonymous/inner/objects etc...)
   */
  def findClassNames(basePackage: String, classLoader: ClassLoader): Set[String] = {
    findClasspathFiles(basePackage, classLoader).filter{ (f: File) =>
      f.getName.endsWith(".class")
    }.map{ (f: File) =>
      val name: String = f.toString()
      name.substring(0, name.length - ".class".length).replace(File.separator, ".")
    }
  }

  /**
   * Similar to File.listFiles() (i.e. a non-recursive findClassPathFiles)
   */
  def listClasspathFiles(basePackage: String): Set[File] = listClasspathFiles(basePackage, defaultClassLoader)

  /**
   * Similar to File.listFiles() (i.e. a non-recursive findClassPathFiles)
   */
  def listClasspathFiles(basePackage: String, classLoader: ClassLoader): Set[File] = {
    val packageDirPath: Path = new File(getPackageDirPath(basePackage)).toPath

    // An empty Path("") will still return 1 for packageDirPath.getNameCount(), which will lead to an exception
    // You can technically have a directory named " ", so using .isEmpty and not .isNullOrBlank
    val subPathLength: Int = if (packageDirPath.toString.isEmpty) 1 else packageDirPath.getNameCount() + 1

    findClasspathFiles(basePackage, classLoader).map{ _.toPath.subpath(0, subPathLength).toFile }
  }

  /**
   * Recursively Find files on the classpath given a base package.
   */
  def findClasspathFiles(basePackage: String): Set[File] = findClasspathFiles(basePackage, defaultClassLoader)

  /**
   * Recursively Find files on the classpath given a base package.
   */
  def findClasspathFiles(basePackage: String, classLoader: ClassLoader): Set[File] = {
    val packageDirPath: String = getPackageDirPath(basePackage)
    val urls: Set[URL] = classLoader.getResources(packageDirPath).asScala.toSet

    urls.flatMap { (url: URL) =>
      val filePath: String = URLDecoder.decode(url.getFile(), "UTF-8")

      url.getProtocol() match {
        case "jar" =>
          val jarFile: String = filePath.substring("file:".length, filePath.indexOf("!"))

          // Note: This might not match the original packageDirPath due to being a multi-release JAR file
          // See: https://docs.oracle.com/javase/9/docs/specs/jar/jar.html
          val jarPrefix: String = filePath.substring(filePath.indexOf("!") + 1)

          // Original code. This broke unit tests for findClasspathFiles("") when upgrading to Logback 1.3.5 with this error message:
          // java.lang.IllegalArgumentException: requirement failed: Expected jarPrefix (/META-INF/versions/9/) to equal package prefix (/). url: jar:file:/Users/tim/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.3.5/logback-classic-1.3.5.jar!/META-INF/versions/9/
          // This is due to it being a multi-release JAR file: https://docs.oracle.com/javase/9/docs/specs/jar/jar.html
          //require(jarPrefix == classpathSeparator+packageDirPath, s"Expected jarPrefix ($jarPrefix) to equal package prefix ($classpathSeparator$packageDirPath). url: $url")
          //scanJar(packageDirPath+classpathSeparator, new File(jarFile))

          scanJar(jarPrefix.stripLeading(classpathSeparator).requireTrailing(classpathSeparator), new File(jarFile))

        case "file" =>
          val packageDir: File = new File(filePath)
          if (packageDir.isDirectory) recursiveListFiles(packageDir).map{ (f: File) => packageDir.toPath.relativize(f.toPath).toFile }.map{ (f: File) => new File(packageDirPath, f.toString) } else Nil

        case _ =>
          logger.warn("Unknown classpath entry: "+url)
          Nil
      }
    }
  }

  private def recursiveListFiles(dir: File): Set[File] = {
    require(dir.isDirectory, s"Expected file to be a directory: $dir")
    dir.listFiles.flatMap { (f: File) =>
      if (f.isFile) List(f)
      else if (f.isDirectory) recursiveListFiles(f)
      else Nil
    }.toSet
  }

  private def scanJar(prefix: String, jarFile: File): Set[File] = {
    require(jarFile.isFile, s"Missing jar file: $jarFile")
    if (prefix != "") {
      require(!prefix.startsWith(classpathSeparator), s"Prefix should not starts with $classpathSeparator")
      require(prefix.endsWith(classpathSeparator), s"Non-Empty prefix should end with $classpathSeparator")
    }

    val builder = Set.newBuilder[File]

    Resource.using(new JarFile(jarFile)){ (jar: JarFile) =>
      jar.entries().asScala.foreach { (entry: JarEntry) =>
        val name: String = entry.getName
        if (!entry.isDirectory && name.startsWith(prefix)) builder += new File(name)
      }
    }

    builder.result()
  }

  private def getPackageDirPath(basePackage: String): String = {
    basePackage.stripLeading(classpathSeparator).replace(".", classpathSeparator)
  }

  private def defaultClassLoader: ClassLoader = {
    val cl: ClassLoader = Thread.currentThread.getContextClassLoader
    if (null != cl) cl else getClass().getClassLoader()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy