mirror of
https://github.com/sbt/sbt.git
synced 2026-09-05 01:03:36 +02:00
support lazy arguments in data type generator SafeLazy implementation that explicitly clears the reference to the thunk in API representation, drop synthetic modifier and merge deferred into abstract handle cyclic structures in API generation, display, comparison, persistence gzip compile cache file bump to 2.8.1.RC3, project definition cleanup fix main method detection to check for the right name properly view inherited definitions exclude constructors of ancestors
222 lines
9.8 KiB
Scala
222 lines
9.8 KiB
Scala
package sbt
|
|
|
|
import java.lang.reflect.{Array => _, _}
|
|
import java.lang.annotation.Annotation
|
|
import xsbti.api
|
|
import xsbti.SafeLazy
|
|
import SafeLazy.strict
|
|
|
|
object ClassToAPI
|
|
{
|
|
def apply(c: Seq[Class[_]]): api.Source =
|
|
{
|
|
val pkgs = packages(c).map(p => new api.Package(p))
|
|
val defs = c.filter(isTopLevel).flatMap(toDefinitions)
|
|
new api.Source(pkgs.toArray, defs.toArray)
|
|
}
|
|
|
|
def packages(c: Seq[Class[_]]): Set[String] =
|
|
c.flatMap(packageName).toSet
|
|
|
|
def isTopLevel(c: Class[_]): Boolean =
|
|
c.getEnclosingClass eq null
|
|
|
|
def toDefinitions(c: Class[_]): Seq[api.ClassLike] =
|
|
{
|
|
import api.DefinitionType.{ClassDef, Module, Trait}
|
|
val mods = modifiers(c.getModifiers)
|
|
val acc = access(c.getModifiers)
|
|
val annots = annotations(c.getAnnotations)
|
|
val name = c.getName
|
|
val tpe = if(Modifier.isInterface(c.getModifiers)) Trait else ClassDef
|
|
lazy val (static, instance) = structure(c)
|
|
val cls = new api.ClassLike(tpe, strict(Empty), lzy(instance), typeParameters(c.getTypeParameters), name, acc, mods, annots)
|
|
def makeStatic(s: api.Structure) =
|
|
new api.ClassLike(Module, strict(Empty), strict(s), Array(), name, acc, mods, annots)
|
|
cls :: static.map(makeStatic).toList
|
|
}
|
|
|
|
def structure(c: Class[_]): (Option[api.Structure], api.Structure) =
|
|
{
|
|
val methods = mergeMap(c, c.getMethods, c.getDeclaredMethods, methodToDef)
|
|
val fields = mergeMap(c, c.getFields, c.getDeclaredFields, fieldToDef)
|
|
val constructors = mergeMap(c, c.getConstructors, c.getDeclaredConstructors, constructorToDef)
|
|
val classes = merge[Class[_]](c, c.getClasses, c.getDeclaredClasses, toDefinitions, (_: 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))
|
|
def static = new api.Structure(emptyTpeArray, lzy(all.staticDeclared.toArray), lzy(all.staticInherited.toArray))
|
|
val staticStructure = if(all.staticDeclared.isEmpty && all.staticInherited.isEmpty) None else Some(static)
|
|
(staticStructure, instanceStructure)
|
|
}
|
|
def lzy[T <: AnyRef](t: => T): xsbti.api.Lazy[T] = xsbti.SafeLazy(t)
|
|
private val emptyTpeArray = lzy(Array[xsbti.api.Type]())
|
|
private val emptyDefArray = lzy(Array[xsbti.api.Definition]())
|
|
|
|
def parents(c: Class[_]): Seq[api.Type] =
|
|
types(c.getGenericSuperclass +: c.getGenericInterfaces)
|
|
def types(ts: Seq[Type]): Array[api.Type] = ts filter (_ ne null) map reference toArray;
|
|
def upperBounds(ts: Array[Type]): api.Type =
|
|
new api.Structure(lzy(types(ts)), emptyDefArray, emptyDefArray)
|
|
|
|
def fieldToDef(f: Field): api.FieldLike =
|
|
{
|
|
val name = f.getName
|
|
val accs = access(f.getModifiers)
|
|
val mods = modifiers(f.getModifiers)
|
|
val annots = annotations(f.getDeclaredAnnotations)
|
|
val tpe = reference(f.getGenericType)
|
|
if(mods.isFinal) new api.Val(tpe, name, accs, mods, annots) else new api.Var(tpe, name, accs, mods, annots)
|
|
}
|
|
|
|
def methodToDef(m: Method): api.Def =
|
|
defLike(m.getName, m.getModifiers, m.getDeclaredAnnotations, m.getTypeParameters, m.getParameterAnnotations, m.getGenericParameterTypes, Some(m.getGenericReturnType), m.getGenericExceptionTypes, m.isVarArgs)
|
|
|
|
def constructorToDef(c: Constructor[_]): api.Def =
|
|
defLike("<init>", c.getModifiers, c.getDeclaredAnnotations, c.getTypeParameters, c.getParameterAnnotations, c.getGenericParameterTypes, None, c.getGenericExceptionTypes, c.isVarArgs)
|
|
|
|
def defLike[T <: GenericDeclaration](name: String, mods: Int, annots: Array[Annotation], tps: Array[TypeVariable[T]], paramAnnots: Array[Array[Annotation]], paramTypes: Array[Type], retType: Option[Type], exceptions: Array[Type], varArgs: Boolean): api.Def =
|
|
{
|
|
val varArgPosition = if(varArgs) paramTypes.length - 1 else -1
|
|
val isVarArg = List.tabulate(paramTypes.length)(_ == varArgPosition)
|
|
val pa = (paramAnnots, paramTypes, isVarArg).zipped map { case (a,p,v) => parameter(a,p,v) }
|
|
val params = new api.ParameterList(pa, false)
|
|
val ret = retType match { case Some(rt) => reference(rt); case None => Empty }
|
|
new api.Def(Array(params), ret, typeParameters(tps), name, access(mods), modifiers(mods), annotations(annots) ++ exceptionAnnotations(exceptions))
|
|
}
|
|
|
|
def exceptionAnnotations(exceptions: Array[Type]): Array[api.Annotation] =
|
|
exceptions map { t => new api.Annotation(Throws, Array(new api.AnnotationArgument("value", t.toString))) }
|
|
|
|
def parameter(annots: Array[Annotation], parameter: Type, varArgs: Boolean): api.MethodParameter =
|
|
new api.MethodParameter("", annotated(reference(parameter),annots), false, if(varArgs) api.ParameterModifier.Repeated else api.ParameterModifier.Plain)
|
|
|
|
def annotated(t: api.SimpleType, annots: Array[Annotation]): api.Type =
|
|
if(annots.isEmpty) t else new api.Annotated(t, annotations(annots))
|
|
|
|
case class Defs(declared: Seq[api.Definition], inherited: Seq[api.Definition], staticDeclared: Seq[api.Definition], staticInherited: Seq[api.Definition])
|
|
{
|
|
def ++(o: Defs) = Defs(declared ++ o.declared, inherited ++ o.inherited, staticDeclared ++ o.staticDeclared, staticInherited ++ o.staticInherited)
|
|
}
|
|
def mergeMap[T <: Member](of: Class[_], self: Seq[T], public: Seq[T], f: T => api.Definition): Defs =
|
|
merge[T](of, self, public, x => f(x) :: Nil, splitStatic _, _.getDeclaringClass != of)
|
|
|
|
def merge[T](of: Class[_], self: Seq[T], public: Seq[T], f: T => Seq[api.Definition], splitStatic: Seq[T] => (Seq[T],Seq[T]), isInherited: T => Boolean): Defs =
|
|
{
|
|
val (selfStatic, selfInstance) = splitStatic(self)
|
|
val (inheritedStatic, inheritedInstance) = splitStatic(public filter isInherited)
|
|
Defs(selfInstance flatMap f, inheritedInstance flatMap f, selfStatic flatMap f, inheritedStatic flatMap f)
|
|
}
|
|
|
|
def splitStatic[T <: Member](defs: Seq[T]): (Seq[T], Seq[T]) =
|
|
defs partition isStatic
|
|
|
|
def isStatic(c: Class[_]): Boolean = Modifier.isStatic(c.getModifiers)
|
|
def isStatic(a: Member): Boolean = Modifier.isStatic(a.getModifiers)
|
|
|
|
def typeParameters[T <: GenericDeclaration](tps: Array[TypeVariable[T]]): Array[api.TypeParameter] =
|
|
tps map typeParameter
|
|
def typeParameter[T <: GenericDeclaration](tp: TypeVariable[T]): api.TypeParameter =
|
|
new api.TypeParameter(typeVariable(tp), Array(), Array(), api.Variance.Invariant, NothingRef, upperBounds(tp.getBounds))
|
|
|
|
// needs to be stable across compilations
|
|
// preferably, it would be a proper unique id based on de Bruijn index
|
|
def typeVariable[T <: GenericDeclaration](tv: TypeVariable[T]): Int =
|
|
reduceHash((name(tv.getGenericDeclaration) + " " + tv.getName).getBytes)
|
|
|
|
def reduceHash(in: Array[Byte]): Int =
|
|
(0 /: in)( (acc, b) => (acc * 43) ^ b)
|
|
|
|
def name(gd: GenericDeclaration): String =
|
|
gd match
|
|
{
|
|
case c: Class[_] => c.getName
|
|
case m: Method => m.getName
|
|
case c: Constructor[_] => c.getName
|
|
}
|
|
|
|
def modifiers(i: Int): api.Modifiers =
|
|
{
|
|
import Modifier.{isAbstract, isFinal}
|
|
new api.Modifiers( isAbstract(i), false, isFinal(i), false, false, false)
|
|
}
|
|
def access(i: Int): api.Access =
|
|
{
|
|
import Modifier.{isPublic, isPrivate, isProtected}
|
|
if(isPublic(i)) Public else if(isPrivate(i)) Private else if(isProtected(i)) Protected else error("Invalid modifiers " + i + " : no access flag set")
|
|
}
|
|
|
|
def annotations(a: Array[Annotation]): Array[api.Annotation] = a map annotation
|
|
def annotation(a: Annotation): api.Annotation =
|
|
new api.Annotation( reference(a.annotationType), Array(javaAnnotation(a.toString)))
|
|
|
|
// full information not available from reflection
|
|
def javaAnnotation(s: String): api.AnnotationArgument =
|
|
new api.AnnotationArgument("toString", s)
|
|
|
|
def array(tpe: api.Type): api.SimpleType = new api.Parameterized(ArrayRef, Array(tpe))
|
|
def reference(c: Class[_]): api.SimpleType =
|
|
if(c.isArray) array(reference(c.getComponentType)) else if(c.isPrimitive) primitive(c.getName) else reference(c.getName)
|
|
|
|
// does not handle primitives
|
|
def reference(s: String): api.SimpleType =
|
|
{
|
|
val (pkg, cls) = packageAndName(s)
|
|
pkg match
|
|
{
|
|
// translate all primitives?
|
|
case None => new api.Projection(EmptyPrefix, cls)
|
|
case Some(p) =>
|
|
new api.Projection(new api.Singleton(pathFromString(p)), cls)
|
|
}
|
|
}
|
|
def referenceP(t: ParameterizedType): api.Parameterized =
|
|
{
|
|
val args = t.getActualTypeArguments.map(reference)
|
|
val base = reference(t.getRawType)
|
|
new api.Parameterized(base, args.toArray[api.Type])
|
|
}
|
|
def reference(t: Type): api.SimpleType =
|
|
t match
|
|
{
|
|
case w: WildcardType => reference("_")
|
|
case tv: TypeVariable[_] => new api.ParameterRef(typeVariable(tv))
|
|
case pt: ParameterizedType => referenceP(pt)
|
|
case gat: GenericArrayType => array(reference(gat.getGenericComponentType))
|
|
case c: Class[_] => reference(c)
|
|
}
|
|
|
|
def pathFromString(s: String): api.Path =
|
|
new api.Path(s.split("\\.").map(new api.Id(_)) :+ ThisRef )
|
|
def packageName(c: Class[_]) = packageAndName(c)._1
|
|
def packageAndName(c: Class[_]): (Option[String], String) =
|
|
packageAndName(c.getName)
|
|
def packageAndName(name: String): (Option[String], String) =
|
|
{
|
|
val lastDot = name.lastIndexOf('.')
|
|
if(lastDot >= 0)
|
|
(Some(name.substring(0, lastDot)), name.substring(lastDot+1))
|
|
else
|
|
(None, name)
|
|
}
|
|
|
|
val Empty = new api.EmptyType
|
|
val EmptyPath = new api.Path(Array())
|
|
val EmptyPrefix = new api.Singleton(EmptyPath)
|
|
val ThisRef = new api.This
|
|
|
|
val Public = new api.Public
|
|
val Private = new api.Private(Unqualified)
|
|
val Protected = new api.Protected(Unqualified)
|
|
val Unqualified = new api.Unqualified
|
|
|
|
val ArrayRef = reference("scala.Array")
|
|
val Throws = reference("scala.throws")
|
|
val NothingRef = reference("scala.Nothing")
|
|
|
|
private[this] def PrimitiveNames = Seq("boolean", "byte", "char", "short", "int", "long", "float", "double")
|
|
private[this] def PrimitiveMap = PrimitiveNames.map( j => (j, j.capitalize)) :+ ("void" -> "Unit")
|
|
private[this] val PrimitiveRefs = PrimitiveMap.map { case (n, sn) => (n, reference("scala." + sn)) }.toMap
|
|
def primitive(name: String): api.SimpleType = PrimitiveRefs(name)
|
|
}
|