From a867d8e87c158582b965776beacad6111967a5b7 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Wed, 1 May 2013 17:54:10 -0400 Subject: [PATCH] extract public inherited dependencies from Java class files --- .../api/src/main/scala/sbt/ClassToAPI.scala | 48 ++++++++++++++----- .../sbt/compiler/AggressiveCompile.scala | 6 ++- .../main/scala/sbt/classfile/Analyze.scala | 18 ++++--- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/compile/api/src/main/scala/sbt/ClassToAPI.scala b/compile/api/src/main/scala/sbt/ClassToAPI.scala index 5af7b3d50..bdab5e1e5 100644 --- a/compile/api/src/main/scala/sbt/ClassToAPI.scala +++ b/compile/api/src/main/scala/sbt/ClassToAPI.scala @@ -10,11 +10,18 @@ import collection.mutable 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 defs = c.filter(isTopLevel).flatMap(toDefinitions(new mutable.HashMap)) - new api.SourceAPI(pkgs.toArray, defs.toArray) + val cmap = emptyClassMap + 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. @@ -35,9 +42,13 @@ object ClassToAPI def isTopLevel(c: Class[_]): Boolean = 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] = - cmap.getOrElseUpdate(c.getName, toDefinitions0(c, cmap)) + cmap.memo.getOrElseUpdate(c.getName, toDefinitions0(c, cmap)) def toDefinitions0(c: Class[_], cmap: ClassMap): Seq[api.ClassLike] = { import api.DefinitionType.{ClassDef, Module, Trait} @@ -48,13 +59,14 @@ object ClassToAPI val name = c.getName val tpe = if(Modifier.isInterface(c.getModifiers)) Trait else ClassDef 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 stat = new api.ClassLike(Module, strict(Empty), lzy(static), emptyStringArray, emptyTypeParameterArray, 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, cmap), emptyStringArray, emptyTypeParameterArray, name, acc, mods, annots) val defs = cls :: stat :: Nil - cmap(c.getName) = defs + cmap.memo(c.getName) = 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) = { 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 classes = merge[Class[_]](c, c.getDeclaredClasses, c.getClasses, toDefinitions(cmap), (_: Seq[Class[_]]).partition(isStatic), _.getEnclosingClass != c) val all = (methods ++ fields ++ constructors ++ classes) - val parentTypes = parents(c) - val instanceStructure = new api.Structure(lzy(parentTypes.toArray), lzy(all.declared.toArray), lzy(all.inherited.toArray)) - val staticStructure = new api.Structure(lzyEmptyTpeArray, lzy(all.staticDeclared.toArray), lzy(all.staticInherited.toArray)) + val parentJavaTypes = allSuperTypes(c) + if(!Modifier.isPrivate(c.getModifiers)) + 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) } + 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) + 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 emptyTypeArray = new Array[xsbti.api.Type](0) private val emptyAnnotationArray = new Array[xsbti.api.Annotation](0) private val emptyTypeParameterArray = new Array[xsbti.api.TypeParameter](0) private val emptySimpleTypeArray = new Array[xsbti.api.SimpleType](0) - private val lzyEmptyTpeArray = lzy(emptyTypeArray) - private val lzyEmptyDefArray = lzy(new Array[xsbti.api.Definition](0)) + private val lzyEmptyTpeArray = lzyS(emptyTypeArray) + private val lzyEmptyDefArray = lzyS(new Array[xsbti.api.Definition](0)) private def allSuperTypes(t: Type): Seq[Type] = { @@ -101,6 +122,7 @@ object ClassToAPI accumulate(t).filterNot(_ == null).distinct } + @deprecated("No longer used", "0.13.0") 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 upperBounds(ts: Array[Type]): api.Type = diff --git a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala index b33db6d84..2ff410e4d 100644 --- a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala +++ b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala @@ -126,7 +126,11 @@ class AggressiveCompile(cacheFile: File) 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) { for ((classesFinder, oldClasses, srcs) <- memo) { diff --git a/util/classfile/src/main/scala/sbt/classfile/Analyze.scala b/util/classfile/src/main/scala/sbt/classfile/Analyze.scala index 843f129c5..117b718ae 100644 --- a/util/classfile/src/main/scala/sbt/classfile/Analyze.scala +++ b/util/classfile/src/main/scala/sbt/classfile/Analyze.scala @@ -15,7 +15,7 @@ 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[_]]) => 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) @@ -42,29 +42,33 @@ private[sbt] object Analyze // get class to class dependencies and map back to source to class dependencies 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) { for (url <- Option(loader.getResource(tpe.replace('.', '/') + ClassExt)); file <- urlAsFile(url, log)) { if(url.getProtocol == "jar") - analysis.binaryDependency(file, tpe, source, /*inherited = */ false) // TODO: properly handle inherited + analysis.binaryDependency(file, tpe, source, inherited) else { assume(url.getProtocol == "file") productToSource.get(file) match { - case Some(dependsOn) => analysis.sourceDependency(dependsOn, source, /*inherited = */ false) - case None => analysis.binaryDependency(file, tpe, source, /*inherited = */ false) + 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)) - classFiles.flatMap(_.types).toSet.foreach(processDependency) - readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") ))) + val notInherited = classFiles.flatMap(_.types).toSet -- publicInherited + processDependencies(notInherited, false) + processDependencies(publicInherited, true) analysis.endSource(source) }