mirror of https://github.com/sbt/sbt.git
extract public inherited dependencies from Java class files
This commit is contained in:
parent
435bd1d587
commit
a867d8e87c
|
|
@ -10,11 +10,18 @@ import collection.mutable
|
||||||
|
|
||||||
object ClassToAPI
|
object ClassToAPI
|
||||||
{
|
{
|
||||||
def apply(c: Seq[Class[_]]): api.SourceAPI =
|
def apply(c: Seq[Class[_]]): api.SourceAPI = process(c)._1
|
||||||
|
|
||||||
|
// (api, public inherited classes)
|
||||||
|
def process(c: Seq[Class[_]]): (api.SourceAPI, Set[Class[_]]) =
|
||||||
{
|
{
|
||||||
val pkgs = packages(c).map(p => new api.Package(p))
|
val pkgs = packages(c).map(p => new api.Package(p))
|
||||||
val defs = c.filter(isTopLevel).flatMap(toDefinitions(new mutable.HashMap))
|
val cmap = emptyClassMap
|
||||||
new api.SourceAPI(pkgs.toArray, defs.toArray)
|
val defs = c.filter(isTopLevel).flatMap(toDefinitions(cmap))
|
||||||
|
val source = new api.SourceAPI(pkgs.toArray, defs.toArray)
|
||||||
|
cmap.lz.foreach(_.get()) // force thunks to ensure all inherited dependencies are recorded
|
||||||
|
cmap.clear()
|
||||||
|
(source, cmap.inherited.toSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoiding implicit allocation.
|
// Avoiding implicit allocation.
|
||||||
|
|
@ -35,9 +42,13 @@ object ClassToAPI
|
||||||
def isTopLevel(c: Class[_]): Boolean =
|
def isTopLevel(c: Class[_]): Boolean =
|
||||||
c.getEnclosingClass eq null
|
c.getEnclosingClass eq null
|
||||||
|
|
||||||
type ClassMap = mutable.Map[String, Seq[api.ClassLike]]
|
final class ClassMap private[sbt](private[sbt] val memo: mutable.Map[String, Seq[api.ClassLike]], private[sbt] val inherited: mutable.Set[Class[_]], private[sbt] val lz: mutable.Buffer[xsbti.api.Lazy[_]]) {
|
||||||
|
def clear() { memo.clear(); inherited.clear(); lz.clear() }
|
||||||
|
}
|
||||||
|
def emptyClassMap: ClassMap = new ClassMap(new mutable.HashMap, new mutable.HashSet, new mutable.ListBuffer)
|
||||||
|
|
||||||
def toDefinitions(cmap: ClassMap)(c: Class[_]): Seq[api.ClassLike] =
|
def toDefinitions(cmap: ClassMap)(c: Class[_]): Seq[api.ClassLike] =
|
||||||
cmap.getOrElseUpdate(c.getName, toDefinitions0(c, cmap))
|
cmap.memo.getOrElseUpdate(c.getName, toDefinitions0(c, cmap))
|
||||||
def toDefinitions0(c: Class[_], cmap: ClassMap): Seq[api.ClassLike] =
|
def toDefinitions0(c: Class[_], cmap: ClassMap): Seq[api.ClassLike] =
|
||||||
{
|
{
|
||||||
import api.DefinitionType.{ClassDef, Module, Trait}
|
import api.DefinitionType.{ClassDef, Module, Trait}
|
||||||
|
|
@ -48,13 +59,14 @@ object ClassToAPI
|
||||||
val name = c.getName
|
val name = c.getName
|
||||||
val tpe = if(Modifier.isInterface(c.getModifiers)) Trait else ClassDef
|
val tpe = if(Modifier.isInterface(c.getModifiers)) Trait else ClassDef
|
||||||
lazy val (static, instance) = structure(c, enclPkg, cmap)
|
lazy val (static, instance) = structure(c, enclPkg, cmap)
|
||||||
val cls = new api.ClassLike(tpe, strict(Empty), lzy(instance), emptyStringArray, typeParameters(c.getTypeParameters), name, acc, mods, annots)
|
val cls = new api.ClassLike(tpe, strict(Empty), lzy(instance, cmap), emptyStringArray, typeParameters(c.getTypeParameters), name, acc, mods, annots)
|
||||||
val stat = new api.ClassLike(Module, strict(Empty), lzy(static), emptyStringArray, emptyTypeParameterArray, name, acc, mods, annots)
|
val stat = new api.ClassLike(Module, strict(Empty), lzy(static, cmap), emptyStringArray, emptyTypeParameterArray, name, acc, mods, annots)
|
||||||
val defs = cls :: stat :: Nil
|
val defs = cls :: stat :: Nil
|
||||||
cmap(c.getName) = defs
|
cmap.memo(c.getName) = defs
|
||||||
defs
|
defs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the (static structure, instance structure, inherited classes) for `c`. */
|
||||||
def structure(c: Class[_], enclPkg: Option[String], cmap: ClassMap): (api.Structure, api.Structure) =
|
def structure(c: Class[_], enclPkg: Option[String], cmap: ClassMap): (api.Structure, api.Structure) =
|
||||||
{
|
{
|
||||||
val methods = mergeMap(c, c.getDeclaredMethods, c.getMethods, methodToDef(enclPkg))
|
val methods = mergeMap(c, c.getDeclaredMethods, c.getMethods, methodToDef(enclPkg))
|
||||||
|
|
@ -62,20 +74,29 @@ object ClassToAPI
|
||||||
val constructors = mergeMap(c, c.getDeclaredConstructors, c.getConstructors, constructorToDef(enclPkg))
|
val constructors = mergeMap(c, c.getDeclaredConstructors, c.getConstructors, constructorToDef(enclPkg))
|
||||||
val classes = merge[Class[_]](c, c.getDeclaredClasses, c.getClasses, toDefinitions(cmap), (_: Seq[Class[_]]).partition(isStatic), _.getEnclosingClass != c)
|
val classes = merge[Class[_]](c, c.getDeclaredClasses, c.getClasses, toDefinitions(cmap), (_: Seq[Class[_]]).partition(isStatic), _.getEnclosingClass != c)
|
||||||
val all = (methods ++ fields ++ constructors ++ classes)
|
val all = (methods ++ fields ++ constructors ++ classes)
|
||||||
val parentTypes = parents(c)
|
val parentJavaTypes = allSuperTypes(c)
|
||||||
val instanceStructure = new api.Structure(lzy(parentTypes.toArray), lzy(all.declared.toArray), lzy(all.inherited.toArray))
|
if(!Modifier.isPrivate(c.getModifiers))
|
||||||
val staticStructure = new api.Structure(lzyEmptyTpeArray, lzy(all.staticDeclared.toArray), lzy(all.staticInherited.toArray))
|
cmap.inherited ++= parentJavaTypes.collect { case c: Class[_] => c }
|
||||||
|
val parentTypes = types(parentJavaTypes)
|
||||||
|
val instanceStructure = new api.Structure(lzyS(parentTypes.toArray), lzyS(all.declared.toArray), lzyS(all.inherited.toArray))
|
||||||
|
val staticStructure = new api.Structure(lzyEmptyTpeArray, lzyS(all.staticDeclared.toArray), lzyS(all.staticInherited.toArray))
|
||||||
(staticStructure, instanceStructure)
|
(staticStructure, instanceStructure)
|
||||||
}
|
}
|
||||||
|
private[this] def lzyS[T <: AnyRef](t: T): xsbti.api.Lazy[T] = lzy(t)
|
||||||
def lzy[T <: AnyRef](t: => T): xsbti.api.Lazy[T] = xsbti.SafeLazy(t)
|
def lzy[T <: AnyRef](t: => T): xsbti.api.Lazy[T] = xsbti.SafeLazy(t)
|
||||||
|
private[this] def lzy[T <: AnyRef](t: => T, cmap: ClassMap): xsbti.api.Lazy[T] = {
|
||||||
|
val s = lzy(t)
|
||||||
|
cmap.lz += s
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
private val emptyStringArray = new Array[String](0)
|
private val emptyStringArray = new Array[String](0)
|
||||||
private val emptyTypeArray = new Array[xsbti.api.Type](0)
|
private val emptyTypeArray = new Array[xsbti.api.Type](0)
|
||||||
private val emptyAnnotationArray = new Array[xsbti.api.Annotation](0)
|
private val emptyAnnotationArray = new Array[xsbti.api.Annotation](0)
|
||||||
private val emptyTypeParameterArray = new Array[xsbti.api.TypeParameter](0)
|
private val emptyTypeParameterArray = new Array[xsbti.api.TypeParameter](0)
|
||||||
private val emptySimpleTypeArray = new Array[xsbti.api.SimpleType](0)
|
private val emptySimpleTypeArray = new Array[xsbti.api.SimpleType](0)
|
||||||
private val lzyEmptyTpeArray = lzy(emptyTypeArray)
|
private val lzyEmptyTpeArray = lzyS(emptyTypeArray)
|
||||||
private val lzyEmptyDefArray = lzy(new Array[xsbti.api.Definition](0))
|
private val lzyEmptyDefArray = lzyS(new Array[xsbti.api.Definition](0))
|
||||||
|
|
||||||
private def allSuperTypes(t: Type): Seq[Type] =
|
private def allSuperTypes(t: Type): Seq[Type] =
|
||||||
{
|
{
|
||||||
|
|
@ -101,6 +122,7 @@ object ClassToAPI
|
||||||
accumulate(t).filterNot(_ == null).distinct
|
accumulate(t).filterNot(_ == null).distinct
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@deprecated("No longer used", "0.13.0")
|
||||||
def parents(c: Class[_]): Seq[api.Type] = types(allSuperTypes(c))
|
def parents(c: Class[_]): Seq[api.Type] = types(allSuperTypes(c))
|
||||||
def types(ts: Seq[Type]): Array[api.Type] = ts filter (_ ne null) map reference toArray;
|
def types(ts: Seq[Type]): Array[api.Type] = ts filter (_ ne null) map reference toArray;
|
||||||
def upperBounds(ts: Array[Type]): api.Type =
|
def upperBounds(ts: Array[Type]): api.Type =
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,11 @@ class AggressiveCompile(cacheFile: File)
|
||||||
javac.compile(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, log)
|
javac.compile(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
def readAPI(source: File, classes: Seq[Class[_]]) { callback.api(source, ClassToAPI(classes)) }
|
def readAPI(source: File, classes: Seq[Class[_]]): Set[String] = {
|
||||||
|
val (api, inherits) = ClassToAPI.process(classes)
|
||||||
|
callback.api(source, api)
|
||||||
|
inherits.map(_.getName)
|
||||||
|
}
|
||||||
|
|
||||||
timed("Java analysis", log) {
|
timed("Java analysis", log) {
|
||||||
for ((classesFinder, oldClasses, srcs) <- memo) {
|
for ((classesFinder, oldClasses, srcs) <- memo) {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import java.net.URL
|
||||||
|
|
||||||
private[sbt] object Analyze
|
private[sbt] object Analyze
|
||||||
{
|
{
|
||||||
def apply[T](newClasses: Seq[File], sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit)
|
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)
|
val sourceMap = sources.toSet[File].groupBy(_.getName)
|
||||||
|
|
||||||
|
|
@ -42,29 +42,33 @@ private[sbt] object Analyze
|
||||||
// get class to class dependencies and map back to source to class dependencies
|
// get class to class dependencies and map back to source to class dependencies
|
||||||
for( (source, classFiles) <- sourceToClassFiles )
|
for( (source, classFiles) <- sourceToClassFiles )
|
||||||
{
|
{
|
||||||
def processDependency(tpe: String)
|
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)
|
trapAndLog(log)
|
||||||
{
|
{
|
||||||
for (url <- Option(loader.getResource(tpe.replace('.', '/') + ClassExt)); file <- urlAsFile(url, log))
|
for (url <- Option(loader.getResource(tpe.replace('.', '/') + ClassExt)); file <- urlAsFile(url, log))
|
||||||
{
|
{
|
||||||
if(url.getProtocol == "jar")
|
if(url.getProtocol == "jar")
|
||||||
analysis.binaryDependency(file, tpe, source, /*inherited = */ false) // TODO: properly handle inherited
|
analysis.binaryDependency(file, tpe, source, inherited)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
assume(url.getProtocol == "file")
|
assume(url.getProtocol == "file")
|
||||||
productToSource.get(file) match
|
productToSource.get(file) match
|
||||||
{
|
{
|
||||||
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source, /*inherited = */ false)
|
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source, inherited)
|
||||||
case None => analysis.binaryDependency(file, tpe, source, /*inherited = */ false)
|
case None => analysis.binaryDependency(file, tpe, source, inherited)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
def processDependencies(tpes: Iterable[String], inherited: Boolean): Unit = tpes.foreach(tpe => processDependency(tpe, inherited))
|
||||||
|
|
||||||
classFiles.flatMap(_.types).toSet.foreach(processDependency)
|
val notInherited = classFiles.flatMap(_.types).toSet -- publicInherited
|
||||||
readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") )))
|
processDependencies(notInherited, false)
|
||||||
|
processDependencies(publicInherited, true)
|
||||||
analysis.endSource(source)
|
analysis.endSource(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue