Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
sbt.classfile.Analyze.scala Maven / Gradle / Ivy
package sbt
package classfile
import scala.collection.mutable
import mutable.{ArrayBuffer , Buffer }
import scala.annotation.tailrec
import java.io.File
import java.lang.annotation.Annotation
import java.lang.reflect.Method
import java.lang.reflect.Modifier .{STATIC , PUBLIC , ABSTRACT }
import java.net.URL
private [sbt] object Analyze
{
def apply [T ](newClasses: Seq [File ], sources: Seq [File ], log: Logger )(analysis: xsbti.AnalysisCallback , loader: ClassLoader , readAPI: (File ,Seq [Class [_]]) => Set [String ])
{
val sourceMap = sources.toSet[File ].groupBy(_.getName)
def load (tpe: String , errMsg: => Option [String ]): Option [Class [_]] =
try { Some (Class .forName(tpe, false , loader)) }
catch { case e: Throwable => errMsg.foreach(msg => log.warn(msg + " : " +e.toString)); None }
val productToSource = new mutable.HashMap [File , File ]
val sourceToClassFiles = new mutable.HashMap [File , Buffer [ClassFile ]]
for (newClass <- newClasses;
classFile = Parser (newClass);
sourceFile <- classFile.sourceFile orElse guessSourceName(newClass.getName);
source <- guessSourcePath(sourceMap, classFile, log))
{
analysis.generatedClass(source, newClass, classFile.className)
productToSource(newClass) = source
sourceToClassFiles.getOrElseUpdate(source, new ArrayBuffer [ClassFile ]) += classFile
}
for ( (source, classFiles) <- sourceToClassFiles )
{
val publicInherited = readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some ("Error reading API from class file" ) )))
def processDependency (tpe: String , inherited: Boolean )
{
trapAndLog(log)
{
for (url <- Option (loader.getResource(tpe.replace('.', '/') + ClassExt )); file <- urlAsFile(url, log))
{
if (url.getProtocol == "jar" )
analysis.binaryDependency(file, tpe, source, inherited)
else
{
assume(url.getProtocol == "file" )
productToSource.get(file) match
{
case Some (dependsOn) => analysis.sourceDependency(dependsOn, source, inherited)
case None => analysis.binaryDependency(file, tpe, source, inherited)
}
}
}
}
}
def processDependencies (tpes: Iterable [String ], inherited: Boolean ): Unit = tpes.foreach(tpe => processDependency(tpe, inherited))
val notInherited = classFiles.flatMap(_.types).toSet -- publicInherited
processDependencies(notInherited, false )
processDependencies(publicInherited, true )
}
for ( source <- sources filterNot sourceToClassFiles.keySet ) {
analysis.api(source, new xsbti.api.SourceAPI (Array (), Array ()))
}
}
private [this ] def urlAsFile (url: URL , log: Logger ): Option [File ] =
try IO .urlAsFile(url)
catch { case e: Exception =>
log.warn("Could not convert URL '" + url.toExternalForm + "' to File: " + e.toString)
None
}
private def trapAndLog (log: Logger )(execute: => Unit )
{
try { execute }
catch { case e => log.trace(e); log.error(e.toString) }
}
private def guessSourceName (name: String ) = Some ( takeToDollar(trimClassExt(name)) )
private def takeToDollar (name: String ) =
{
val dollar = name.indexOf('$')
if (dollar < 0 ) name else name.substring(0 , dollar)
}
private final val ClassExt = ".class"
private def trimClassExt (name: String ) = if (name.endsWith(ClassExt )) name.substring(0 , name.length - ClassExt .length) else name
private def guessSourcePath (sourceNameMap: Map [String , Set [File ]], classFile: ClassFile , log: Logger ) =
{
val classNameParts = classFile.className.split("" "\." "" )
val pkg = classNameParts.init
val simpleClassName = classNameParts.last
val sourceFileName = classFile.sourceFile.getOrElse(simpleClassName.takeWhile(_ != '$').mkString("" , "" , ".java" ))
val candidates = findSource(sourceNameMap, pkg.toList, sourceFileName)
candidates match
{
case Nil => log.warn("Could not determine source for class " + classFile.className)
case head :: Nil => ()
case _ => log.warn("Multiple sources matched for class " + classFile.className + ": " + candidates.mkString(", " ))
}
candidates
}
private def findSource (sourceNameMap: Map [String , Iterable [File ]], pkg: List [String ], sourceFileName: String ): List [File ] =
refine( (sourceNameMap get sourceFileName).toList.flatten.map { x => (x,x.getParentFile) }, pkg.reverse)
@tailrec private def refine (sources: List [(File , File )], pkgRev: List [String ]): List [File ] =
{
def make = sources.map(_._1)
if (sources.isEmpty || sources.tail.isEmpty)
make
else
pkgRev match
{
case Nil => shortest(make)
case x :: xs =>
val retain = sources flatMap { case (src, pre) =>
if (pre != null && pre.getName == x)
(src, pre.getParentFile) :: Nil
else
Nil
}
refine(retain, xs)
}
}
private def shortest (files: List [File ]): List [File ] =
if (files.isEmpty) files
else
{
val fs = files.groupBy(distanceToRoot(0 ))
fs(fs.keys.min)
}
private def distanceToRoot (acc: Int )(file: File ): Int =
if (file == null ) acc else distanceToRoot(acc + 1 )(file.getParentFile)
private def isTopLevel (classFile: ClassFile ) = classFile.className.indexOf('$') < 0
private lazy val unit = classOf[Unit ]
private lazy val strArray = List (classOf[Array [String ]])
private def isMain (method: Method ): Boolean =
method.getName == "main" &&
isMain(method.getModifiers) &&
method.getReturnType == unit &&
method.getParameterTypes.toList == strArray
private def isMain (modifiers: Int ): Boolean = (modifiers & mainModifiers) == mainModifiers && (modifiers & notMainModifiers) == 0
private val mainModifiers = STATIC | PUBLIC
private val notMainModifiers = ABSTRACT
}