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

org.opalj.bi.reader.ClassFileReader.scala Maven / Gradle / Ivy

The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package bi
package reader

import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.ByteArrayInputStream
import java.io.DataInputStream
import java.io.BufferedInputStream
import java.io.IOException
import java.io.ByteArrayOutputStream
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Files
import java.util.zip.{ZipEntry, ZipFile}
import java.net.URL
import java.net.URI
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicInteger
import java.util.jar.JarInputStream
import java.util.jar.JarEntry

import scala.util.control.ControlThrowable
import scala.collection.JavaConverters._
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import scala.concurrent.Future
import scala.concurrent.ExecutionContext

import org.apache.commons.text.similarity.LevenshteinDistance

import org.opalj.log.OPALLogger.error
import org.opalj.log.OPALLogger.info
import org.opalj.control.fillArrayOfInt
import org.opalj.io.process

import org.opalj.concurrent.NumberOfThreadsForIOBoundTasks
import org.opalj.concurrent.BoundedExecutionContext
import org.opalj.concurrent.parForeachSeqElement
import org.opalj.bytecode.BytecodeProcessingFailedException
import org.opalj.collection.immutable.RefArray
import org.opalj.concurrent.Tasks

/**
 * Implements the template method to read in a Java class file. Additionally,
 * several convenience methods are defined to read in class files from various
 * sources (Streams, Files, JAR archives).
 *
 * This library supports class files from version 45 (Java 1.1) up to version 54 (Java 10).
 *
 * ==Notes for Implementors==
 * Reading of the class file's major structures: the constant pool, fields, methods
 * and the attributes is delegated to corresponding readers.
 * This enables a very high-level of adaptability.
 *
 * For further details see the JVM Specification: The ClassFile Structure.
 */
trait ClassFileReader extends ClassFileReaderConfiguration with Constant_PoolAbstractions {

    //
    // TYPE DEFINITIONS AND FACTORY METHODS
    //

    /**
     * The type of the object that represents a Java class file.
     */
    type ClassFile

    /**
     * The inherited interfaces.
     */
    final type Interfaces = Array[Constant_Pool_Index]

    /**
     * The type of the object that represents the fields of a class.
     */
    type Fields

    /**
     * The type of the object that represents all methods of a class.
     */
    type Methods

    /**
     * The type of the object that represents a class declaration's
     * attributes (e.g., the source file attribute.)
     */
    type Attributes

    // METHODS DELEGATING TO OTHER READERS
    //

    /**
     * Reads the constant pool using the given stream.
     *
     * When this method is called the given stream has to be positioned at the very
     * beginning of the constant pool. This method is called by the template method that
     * reads in a class file to delegate the reading of the constant pool. Only
     * information belonging to the constant pool are allowed to be read.
     *
     * The stream must not be closed after reading the constant pool.
     */
    protected def Constant_Pool(in: DataInputStream): Constant_Pool

    /**
     * Reads all field declarations using the given stream and constant pool.
     *
     * The given stream is positioned directly before a class file's "fields_count" field.
     * This method is called by the template method that reads in a class file to
     * delegate the reading of the declared fields.
     */
    protected def Fields(cp: Constant_Pool, in: DataInputStream): Fields

    /**
     * Reads all method declarations using the given stream and constant pool.
     *
     * The given stream is positioned directly before a class file's "methods_count" field.
     * This method is called by the
     * template method that reads in a class file to delegate the reading of the
     * declared method.
     */
    protected def Methods(cp: Constant_Pool, in: DataInputStream): Methods

    /**
     * Reads all attributes using the given stream and constant pool.
     *
     * The given stream is positioned directly before a class file's "attributes_count"
     * field.
     * This method is called by the template method that reads in a class file to
     * delegate the reading of the attributes.
     *
     * '''From the Specification'''
     *
     * The attributes [...] appearing in the attributes table of a ClassFile
     * structure are the InnerClasses, EnclosingMethod, Synthetic, Signature,
     * SourceFile, SourceDebugExtension, Deprecated, RuntimeVisibleAnnotations,
     * RuntimeInvisibleAnnotations, BootstrapMethods, RuntimeVisibleTypeAnnotations,
     * and RuntimeInvisibleTypeAnnotations attributes.
     */
    protected def Attributes(
        cp:                  Constant_Pool,
        ap:                  AttributeParent,
        ap_name_index:       Constant_Pool_Index,
        ap_descriptor_index: Constant_Pool_Index,
        in:                  DataInputStream
    ): Attributes

    /**
     * Factory method to create the `ClassFile` object that represents the class
     * file as a whole, plus any `ClassFile`s that have been synthesized in the process
     * of parsing it.
     *
     * The result will always contain at least one `ClassFile` object, namely the one that
     * is created from this method's parameters. Regardless of how many `ClassFile`s the
     * result contains, the `ClassFile` created from this method's parameters will always
     * be the result's first element.
     */
    protected def ClassFile(
        cp:            Constant_Pool,
        minor_version: Int,
        major_version: Int,
        access_flags:  Int,
        this_class:    Constant_Pool_Index,
        super_class:   Constant_Pool_Index,
        interfaces:    Interfaces,
        fields:        Fields,
        methods:       Methods,
        attributes:    Attributes
    ): ClassFile

    //
    // IMPLEMENTATION
    //

    import ClassFileReader.ExceptionHandler

    final val defaultExceptionHandler: ExceptionHandler = (source, t) ⇒ {
        error("class file reader", s"processing $source failed", t)
    }

    private[this] var classFilePostProcessors = RefArray.empty[List[ClassFile] ⇒ List[ClassFile]]

    /**
     * Register a class file post processor. A class file post processor
     * can transform the completely read and reified class file. Post processors
     * can only be registered before the usage of a class file reader. '''Registering
     * new `ClassFilePostProcessors` while processing class files is not supported
     * and the behavior is undefined'''.
     *
     * @note `PostProcessors` will be executed in last-in-first-out order.
     */
    def registerClassFilePostProcessor(p: List[ClassFile] ⇒ List[ClassFile]): Unit = {
        classFilePostProcessors :+= p
    }

    /**
     * Template method that reads a Java class file from the given input stream.
     *
     * All other methods to read a class file use this method to eventually parse a
     * class file.
     *
     * ==Class File Structure==
     * Parses a class file according to the specification:
     * 
     * ClassFile {
     *    u4 magic;
     *    u2 minor_version;
     *    u2 major_version;
     *    u2 constant_pool_count;
     *    cp_info constant_pool[constant_pool_count-1];
     *    u2 access_flags;
     *    u2 this_class;
     *    u2 super_class;
     *    u2 interfaces_count;
     *    u2 interfaces[interfaces_count];
     *    u2 fields_count;
     *    field_info fields[fields_count];
     *    u2 methods_count;
     *    method_info methods[methods_count];
     *    u2 attributes_count;
     *    attribute_info attributes[attributes_count];
     * }
     * 
* * @param in The `DataInputStream` from which the class file will be read. The * stream is not closed by this method. * '''It is highly recommended that the stream is buffered; otherwise the * performance will be terrible!''' */ def ClassFile(in: DataInputStream): List[ClassFile] = { // magic val readMagic = in.readInt if (ClassFileMagic != readMagic) throw BytecodeProcessingFailedException("the file does not start with 0xCAFEBABE") val minor_version = in.readUnsignedShort val major_version = in.readUnsignedShort def unsupportedVersion = s"unsupported class file version: $major_version.$minor_version"+ " (Supported: 45(Java 1.1) <= version <= "+ s"$LatestSupportedJavaMajorVersion(${jdkVersion(LatestSupportedJavaMajorVersion)}))" // let's make sure that we support this class file's version if (major_version < 45) // at least JDK 1.1 or we back out throw BytecodeProcessingFailedException(unsupportedVersion) if (major_version > LatestSupportedJavaMajorVersion || ( major_version == LatestSupportedJavaMajorVersion && minor_version > LatestSupportedJavaVersion.minor )) { // Just log an error message for newer version, we might still be able to handle the // class if it doesn't use any features introduced in an unsupported version error("class file reader", unsupportedVersion) } val cp = Constant_Pool(in) val access_flags = in.readUnsignedShort val this_class = in.readUnsignedShort val super_class = in.readUnsignedShort val interfaces = { val interfaces_count = in.readUnsignedShort fillArrayOfInt(interfaces_count) { in.readUnsignedShort } } val fields = Fields(cp, in) val methods = Methods(cp, in) val attributes = Attributes(cp, AttributesParent.ClassFile, this_class, -1, in) var classFile = ClassFile( cp, minor_version, major_version, access_flags, this_class, super_class, interfaces, fields, methods, attributes ) // Perform transformations that are specific to this class file. // (Used, e.g., to finally resolve the invokedynamic instructions.) classFile = applyDeferredActions(cp, classFile) // Perform general transformations on class files. classFilePostProcessors.foldLeft(List(classFile)) { (classFiles, postProcessor) ⇒ postProcessor(classFiles) } } // // CONVENIENCE METHODS TO LOAD CLASS FILES FROM VARIOUS SOURCES // /** * Reads in a class file. * * @param create A function that creates a new `InputStream` and * which must not return `null`. If you already do have an open input stream * which should not be closed after reading the class file use * `...ClassFileReader.ClassFile(java.io.DataInputStream) : ClassFile` instead. * The (newly created) `InputStream` returned by calling `create` is closed by * this method. * The created input stream will automatically be wrapped by OPAL to enable * efficient reading of the class file. */ def ClassFile(create: () ⇒ InputStream): List[ClassFile] = { process(create()) { case null ⇒ throw new IllegalArgumentException("the created stream is null") case dis: DataInputStream ⇒ ClassFile(dis) case bis: BufferedInputStream ⇒ ClassFile(new DataInputStream(bis)) case bas: ByteArrayInputStream ⇒ ClassFile(new DataInputStream(bas)) case is ⇒ ClassFile(new DataInputStream(new BufferedInputStream(is))) } } def isClassFileRepository(filename: String, containerName: Option[String]): Boolean = { if (containerName.isDefined) { // We don't want to extract inner jars,... from jmods (the default jmods contain // jars which contain class files also found in the jmods.) val containerNameLength = containerName.get.length if (containerNameLength > 5 && containerName.get.endsWith(".jmod")) { return false; } } val filenameLength = filename.length filenameLength > 4 && { val ending = filename.substring(filenameLength - 4, filenameLength).toLowerCase (ending == "jmod" && filename.charAt(filenameLength - 5) == '.') || ending == ".jar" || ending == ".zip" || ending == ".war" || ending == ".ear" } } protected[this] def ClassFile(jarFile: ZipFile, jarEntry: ZipEntry): List[ClassFile] = { process(jarFile.getInputStream(jarEntry)) { in ⇒ ClassFile(new DataInputStream(new BufferedInputStream(in))) } } /** * Reads in a single class file from a Jar file. * * @param jarFile An existing ZIP/JAR file that contains class files. * @param jarFileEntryName The name of a class file stored in the specified ZIP/JAR file. */ @throws[java.io.IOException]("if the file is empty or the entry cannot be found") def ClassFile(jarFile: File, jarFileEntryName: String): List[ClassFile] = { if (jarFile.length() == 0) throw new IOException(s"the file $jarFile is empty"); val levenshteinDistance = new LevenshteinDistance() process(new ZipFile(jarFile)) { zf ⇒ val jarEntry = zf.getEntry(jarFileEntryName) if (jarEntry == null) { var names: List[(Int, String)] = Nil val zfEntries = zf.entries() while (zfEntries.hasMoreElements) { val zfEntry = zfEntries.nextElement() val zfEntryName = zfEntry.getName val distance = levenshteinDistance(zfEntryName, jarFileEntryName).intValue() names = (distance, zfEntryName) :: names } val mostRelatedNames = names.sortWith((l, r) ⇒ l._1 < r._1).map(_._2).take(15) val ending = if (mostRelatedNames.length > 15) ", ...)" else ")" val messageHeader = s"the file $jarFile does not contain $jarFileEntryName" val message = mostRelatedNames.mkString(s"$messageHeader (similar: ", ", ", ending) throw new IOException(message) } ClassFile(zf, jarEntry) } } /** * Reads in a single class file from a Jar file. * * @param jarFileName the name of an existing ZIP/JAR file that contains class files. * @param jarFileEntryName the name of a class file stored in the specified ZIP/JAR file. */ @throws[java.io.IOException]("if the file is empty or the entry cannot be found") def ClassFile(jarFileName: String, jarFileEntryName: String): List[ClassFile] = { ClassFile(new File(jarFileName), jarFileEntryName) } /** * Reads in parallel all class files stored in the given jar/zip file. * * @param jarFile Some valid (non-empty) jar File. * @return The loaded class files. */ def ClassFiles( jarFile: ZipFile, exceptionHandler: ExceptionHandler ): List[(ClassFile, URL)] = { val Lock = new Object var classFiles: List[(ClassFile, URL)] = Nil def addClassFile(cf: ClassFile, url: URL): Unit = { Lock.synchronized { classFiles ::= ((cf, url)) } } ClassFiles(jarFile, addClassFile, exceptionHandler) classFiles } /** * Reads the class files from the given JarInputStream */ // The following solution is inspired by Ben Hermann's solution found at: // https://github.com/delphi-hub/delphi-crawler/blob/feature/streamworkaround/src/main/scala/de/upb/cs/swt/delphi/crawler/tools/JarStreamReader.scala // and // https://github.com/delphi-hub/delphi-crawler/blob/develop/src/main/scala/de/upb/cs/swt/delphi/crawler/tools/ClassStreamReader.scala def ClassFiles(in: ⇒ JarInputStream): List[(ClassFile, String)] = process(in) { in ⇒ var je: JarEntry = in.getNextJarEntry() var futures: List[Future[List[(ClassFile, String)]]] = Nil while (je != null) { val entryName = je.getName if (entryName.endsWith(".class") || entryName.endsWith(".jar")) { val entryBytes = { val baos = new ByteArrayOutputStream() val buffer = new Array[Byte](32 * 1024) Stream.continually(in.read(buffer)).takeWhile(_ > 0).foreach { bytesRead ⇒ baos.write(buffer, 0, bytesRead) baos.flush() } baos.toByteArray } futures ::= Future[List[(ClassFile, String)]] { if (entryName.endsWith(".class")) { val cfs = ClassFile(new DataInputStream(new ByteArrayInputStream(entryBytes))) cfs map { cf ⇒ (cf, entryName) } } else { // ends with ".jar" info("class file reader", s"reading inner jar $entryName") ClassFiles(new JarInputStream(new ByteArrayInputStream(entryBytes))) } }( // we can't use the OPALExecutionContext here, because the number of // threads is bounded and (depending on the nesting level, we may need // more threads..) ExecutionContext.global ) } je = in.getNextJarEntry() } futures.flatMap(f ⇒ Await.result(f, Duration.Inf)) } /** * Reads '''in parallel''' all class files stored in the given jar file. For each * successfully read class file the function `classFileHandler` is called. * * @param zipFile A valid zip file that contains `.class` files and other * `.jar` files; other files are ignored. Inner jar files are also unzipped. * @param classFileHandler A function that is called for each class file in * the given jar file. * Given that the jarFile is read in parallel '''this function has to be * thread safe'''. * @param exceptionHandler The exception handler that is called when the reading * of a class file fails. '''This function has to be thread safe'''. */ def ClassFiles( zipFile: ZipFile, classFileHandler: (ClassFile, URL) ⇒ Unit, exceptionHandler: ExceptionHandler ): Unit = { val zipFileURL = new File(zipFile.getName).toURI.toURL.toExternalForm val jarFileName = s"jar:$zipFileURL!/" ClassFiles(jarFileName, zipFile, classFileHandler, exceptionHandler) } private def ClassFiles( jarFileURL: String, // the complete path to the given jar file. jarFile: ZipFile, classFileHandler: (ClassFile, URL) ⇒ Unit, exceptionHandler: ExceptionHandler ): Unit = { import scala.collection.JavaConverters._ // First let's collect all inner Jar Entries, then do the processing. // Otherwise - if the OPALExecutionContextTaskSupport uses a fixed // sized thread pool - we may run out of threads... to process anything. val innerJarEntries = new ConcurrentLinkedQueue[ZipEntry] val jarEntries = jarFile.entries.asScala.toArray val nextEntryIndex = new AtomicInteger(jarEntries.length - 1) val parallelismLevel = NumberOfThreadsForIOBoundTasks val futures: Array[Future[Unit]] = new Array(parallelismLevel) var futureIndex = 0 while (futureIndex < parallelismLevel) { futures(futureIndex) = Future[Unit] { var index = -1 while ({ index = nextEntryIndex.getAndDecrement; index } >= 0) { val jarEntry = jarEntries(index) if (!jarEntry.isDirectory && jarEntry.getSize > 0) { val jarEntryName = jarEntry.getName if (jarEntryName.endsWith(".class")) { try { val url = new URL(jarFileURL + jarEntry.getName) val classFiles = ClassFile(jarFile, jarEntry) classFiles foreach (classFile ⇒ classFileHandler(classFile, url)) } catch { case ct: ControlThrowable ⇒ throw ct case t: Throwable ⇒ exceptionHandler(jarEntryName, t) } } else if (isClassFileRepository(jarEntryName, Some(jarFile.getName))) { innerJarEntries.add(jarEntry) } } } }(org.opalj.concurrent.OPALHTBoundedExecutionContext) futureIndex += 1 } while ({ futureIndex -= 1; futureIndex } >= 0) { Await.ready(futures(futureIndex), Duration.Inf) } innerJarEntries.iterator().forEachRemaining { jarEntry ⇒ // TODO make this commons.vfs compatible... // jar:jar/...!/...!... val nextJarFileURL = s"${jarFileURL}jar:${jarEntry.getName}!/" try { val jarData = new Array[Byte](jarEntry.getSize.toInt) val din = new DataInputStream(jarFile.getInputStream(jarEntry)) din.readFully(jarData) din.close() ClassFiles(nextJarFileURL, jarData, classFileHandler, exceptionHandler) } catch { case ct: ControlThrowable ⇒ throw ct case t: Throwable ⇒ exceptionHandler(nextJarFileURL, t) } } } /** * Loads class files from an in-memory representation of a jar file given in form * of a byte array. * This is done by writing the jar file data to a temporary file and then loading * the class files from it as done with any other jar file. */ private def ClassFiles( jarFileURL: String, jarData: Array[Byte], classFileHandler: (ClassFile, URL) ⇒ Unit, exceptionHandler: ExceptionHandler ): Unit = { val pathToEntry = jarFileURL.substring(0, jarFileURL.length - 3) val entry = pathToEntry.substring(pathToEntry.lastIndexOf('/') + 1) try { val jarFile = File.createTempFile(entry, ".zip") process { new java.io.FileOutputStream(jarFile) } { fout ⇒ fout.write(jarData) } ClassFiles(jarFileURL, new ZipFile(jarFile), classFileHandler, exceptionHandler) jarFile.delete() } catch { case ct: ControlThrowable ⇒ throw ct case t: Throwable ⇒ exceptionHandler(pathToEntry, t) } } private[this] def processJar( file: File, exceptionHandler: ExceptionHandler = defaultExceptionHandler ): List[(ClassFile, URL)] = { try { process(new ZipFile(file)) { zf ⇒ ClassFiles(zf, exceptionHandler) } } catch { case e: Exception ⇒ { exceptionHandler(file, e); Nil } } } private[this] def processClassFile( file: File, exceptionHandler: ExceptionHandler = defaultExceptionHandler ): List[(ClassFile, URL)] = { try { process( new DataInputStream(new BufferedInputStream(new FileInputStream(file))) ) { in ⇒ ClassFile(in).map(classFile ⇒ (classFile, file.toURI.toURL)) } } catch { case e: Exception ⇒ { exceptionHandler(file, e); Nil } } } /** * Loads class files from the given file location. * - If the file denotes a single ".class" file this class file is loaded. * - If the file object denotes a ".jar|.war|.ear|.zip" file, all class files in the * jar file will be loaded. * - If the file object specifies a directory object, all ".class" files * in the directory and in all subdirectories are loaded as well as all * class files stored in ".jar" files in one of the directories. This class loads * all class files in parallel. However, this does not effect analyses working on the * resulting `List`. */ def ClassFiles( file: File, exceptionHandler: ExceptionHandler = defaultExceptionHandler ): List[(ClassFile, URL)] = { if (!file.exists()) { Nil } else if (file.isFile) { val filename = file.getName if (file.length() == 0) Nil else if (isClassFileRepository(filename, None)) processJar(file) else if (filename.endsWith(".class")) processClassFile(file) else Nil } else if (file.isDirectory) { val jarFiles = ArrayBuffer.empty[File] val classFiles = ArrayBuffer.empty[File] def collectFiles(files: Array[File]): Unit = { if (files eq null) return ; files foreach { file ⇒ val filename = file.getName if (file.isFile) { if (file.length() == 0) Nil else if (isClassFileRepository(filename, None)) jarFiles += file else if (filename.endsWith(".class")) classFiles += file } else if (file.isDirectory) { collectFiles(file.listFiles()) } else { info( "class file reader", s"ignored: $file it is neither a file nor a directory" ) } } } // 1. get the list of all files in the directory as well as all subdirectories collectFiles(file.listFiles()) // 2. get all class files var allClassFiles = List.empty[(ClassFile, URL)] // 2.1 load - in parallel - all ".class" files if (classFiles.nonEmpty) { val theClassFiles = new ConcurrentLinkedQueue[(ClassFile, URL)] parForeachSeqElement(classFiles, NumberOfThreadsForIOBoundTasks) { classFile ⇒ theClassFiles.addAll(processClassFile(classFile, exceptionHandler).asJava) } allClassFiles ++= theClassFiles.asScala } // 2.2 load - one after the other - all ".jar" files (processing jar files // is already parallelized.) jarFiles.foreach { jarFile ⇒ allClassFiles ++= processJar(jarFile) } // 3. return all loaded class files allClassFiles } else { throw new UnknownError(s"$file is neither a file nor a directory") } } def AllClassFiles( files: Traversable[File], exceptionHandler: ExceptionHandler = defaultExceptionHandler ): Traversable[(ClassFile, URL)] = { files.flatMap(file ⇒ ClassFiles(file, exceptionHandler)) } /** Returns the class files of the current Java Runtime Image grouped by module. */ def JRTClassFiles: Iterable[(String, List[(ClassFile, URL)])] = { def traverseModule(module: Path): List[(ClassFile, URL)] = { var allClassFiles = List.empty[(ClassFile, URL)] def traversePath(p: Path): Unit = { if (Files.isDirectory(p)) { try { for (subPath ← Files.newDirectoryStream(p, "*").asScala) { traversePath(subPath) } } catch { case e: Exception ⇒ { error( "class file reader", "failed processing Java 9+ Runtime Image (jrt:/)", e ) } } } else if (p.getFileName.toString.endsWith(".class")) { val cf = ClassFile(() ⇒ Files.newInputStream(p)) allClassFiles = cf.map(c ⇒ (c, p.toUri.toURL)) ++: allClassFiles } } traversePath(module) allClassFiles } val allModulesPath = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("/modules") for { modulePath ← Files.newDirectoryStream(allModulesPath, "*").asScala if Files.isDirectory(modulePath) } yield { (modulePath.getFileName.toString, traverseModule(modulePath)) } } /** * Goes over all files in parallel and calls back the given function which has to be thread-safe! */ def processClassFiles( files: Traversable[File], progressReporter: File ⇒ Unit, classFileProcessor: ((ClassFile, URL)) ⇒ Unit, exceptionHandler: ExceptionHandler = defaultExceptionHandler ): Unit = { val ts = Tasks[File] { (tasks: Tasks[File], file: File) ⇒ if (file.isFile && file.length() > 0) { val filename = file.getName if (isClassFileRepository(filename, None)) { if (!filename.endsWith("-javadoc.jar") && !filename.endsWith("-sources.jar")) { progressReporter(file) processJar(file, exceptionHandler).foreach(classFileProcessor) } } else if (filename.endsWith(".class")) { progressReporter(file) processClassFile(file, exceptionHandler).foreach(classFileProcessor) } } else if (file.isDirectory) { progressReporter(file) file.listFiles().foreach(tasks.submit) } }( // We need a fresh/privately owned execution context with a fixed number of threads // to avoid that – if the processor also uses the fixed size pool – // we potentially run out of threads! BoundedExecutionContext( "ClassFileReader.processClassFiles", NumberOfThreadsForIOBoundTasks ) ) files.foreach(ts.submit) ts.join() } /** * Searches for the first class file that is accepted by the filter. If no class file * can be found that is accepted by the filter the set of all class names is returned. * * @param files Some file. If the file names a .jar file the .jar file is opened and * searched for a corresponding class file. If the file identifies a "directory" * then, all files in that directory are processed. */ def findClassFile( files: Traversable[File], progressReporter: File ⇒ Unit, classFileFilter: ClassFile ⇒ Boolean, className: ClassFile ⇒ String, exceptionHandler: ExceptionHandler = defaultExceptionHandler ): Either[(ClassFile, URL), Set[String]] = { var classNames = Set.empty[String] files.filter(_.exists()) foreach { file ⇒ if (file.isFile && file.length() > 0) { val filename = file.getName ( if (isClassFileRepository(filename, None)) { if (!filename.endsWith("-javadoc.jar") && !filename.endsWith("-sources.jar")) { progressReporter(file) processJar(file, exceptionHandler) } else { Nil } } else if (filename.endsWith(".class")) { progressReporter(file) processClassFile(file, exceptionHandler) } else { Nil } ) filter { cfSource ⇒ val (cf, _) = cfSource classNames += className(cf) classFileFilter(cf) } foreach { e ⇒ return Left(e); } } else if (file.isDirectory) { file.listFiles { (dir: File, name: String) ⇒ dir.isDirectory || isClassFileRepository(file.toString, None) } foreach { f ⇒ findClassFile( List(f), progressReporter, classFileFilter, className, exceptionHandler ) match { case Left(cf) ⇒ return Left(cf); case Right(moreClassNames) ⇒ classNames ++= moreClassNames /*nothing else to do... let's continue*/ } } } } Right(classNames) } } /** * Helper methods related to reading class files. */ object ClassFileReader { type ExceptionHandler = (AnyRef, Throwable) ⇒ Unit final val SuppressExceptionHandler: ExceptionHandler = (_, _) ⇒ {} }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy