Skip to content

Commit

Permalink
major rewrite to typeTags instead of the deprecated manifest. So wild…
Browse files Browse the repository at this point in the history
…card types are converted correctly
  • Loading branch information
Vrolijkx committed Apr 29, 2018
1 parent 63b1960 commit ac3437e
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 62 deletions.
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ useGpg := true

libraryDependencies ++= Seq(
"com.google.inject" % "guice" % "4.2.0",
"com.google.guava" % "guava" % "23.6-android"
"com.google.guava" % "guava" % "23.6-android",
"org.scala-lang" % "scala-reflect" % scalaVersion.value
)

libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test"
Expand Down
48 changes: 12 additions & 36 deletions src/main/scala/net/codingwell/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,30 @@
package net.codingwell

import java.lang.annotation.Annotation
import java.lang.reflect.Type

import com.google.inject.internal.Annotations
import com.google.inject.util.Types
import com.google.inject.util.Types.newParameterizedType
import com.google.inject.{Key, TypeLiteral}

import scala.language.higherKinds
import scala.reflect.ClassTag
import scala.reflect.{ClassTag, classTag}
import scala.reflect.runtime.universe.{TypeTag, runtimeMirror, typeOf}


package object scalaguice {
private val mirror = runtimeMirror(getClass.getClassLoader)

/**
* Create a com.google.inject.TypeLiteral from a [[scala.reflect.Manifest]].
* Subtypes of [[scala.AnyVal]] will be converted to their corresponding
* Java wrapper classes.
*/
def typeLiteral[T: Manifest]: TypeLiteral[T] = {
TypeLiteral.get(typeOf[T]).asInstanceOf[TypeLiteral[T]]
def typeLiteral[T: TypeTag]: TypeLiteral[T] = {
val javaType = TypeConversions.scalaTypeToJavaType(typeOf[T])
TypeLiteral.get(javaType).asInstanceOf[TypeLiteral[T]]
}

def cls[T: Manifest] = manifest[T].runtimeClass.asInstanceOf[Class[T]]

private def isArray[T](implicit m: Manifest[T]) = m.runtimeClass.isArray

private[scalaguice] def typeOf[T](implicit m: Manifest[T]): Type = {
def toWrapper(c: Type) = c match {
case java.lang.Byte.TYPE => classOf[java.lang.Byte]
case java.lang.Short.TYPE => classOf[java.lang.Short]
case java.lang.Character.TYPE => classOf[java.lang.Character]
case java.lang.Integer.TYPE => classOf[java.lang.Integer]
case java.lang.Long.TYPE => classOf[java.lang.Long]
case java.lang.Float.TYPE => classOf[java.lang.Float]
case java.lang.Double.TYPE => classOf[java.lang.Double]
case java.lang.Boolean.TYPE => classOf[java.lang.Boolean]
case java.lang.Void.TYPE => classOf[java.lang.Void]
case cls => cls
}

if (isArray[T]) return m.runtimeClass

import com.google.inject.util.Types
m.typeArguments match {
case Nil => toWrapper(m.runtimeClass)
case args => m.runtimeClass match {
case c: Class[_] if c.getEnclosingClass == null => Types.newParameterizedType(c, args.map(typeOf(_)): _*)
case c: Class[_] => Types.newParameterizedTypeWithOwner(c.getEnclosingClass, c, args.map(typeOf(_)): _*)
}
}
}
def cls[T: ClassTag] = classTag[T].runtimeClass.asInstanceOf[Class[T]]

/**
* Returns the name the set should use. This is based on the annotation.
Expand All @@ -89,7 +65,7 @@ package object scalaguice {

private[scalaguice] class WrapHelper[WType[_] : HKClassTag] {
def around[T](typ: TypeLiteral[T]): TypeLiteral[WType[T]] = {
val wType = Types.newParameterizedType(implicitly[HKClassTag[WType]].runtimeClass, typ.getType)
val wType = newParameterizedType(implicitly[HKClassTag[WType]].runtimeClass, typ.getType)
TypeLiteral.get(wType).asInstanceOf[TypeLiteral[WType[T]]]
}
}
Expand All @@ -101,7 +77,7 @@ package object scalaguice {

private[scalaguice] class WrapHelper2[WType[_, _] : HKClassTag2] {
def around[K, V](kTyp: TypeLiteral[K], vTyp: TypeLiteral[V]): TypeLiteral[WType[K, V]] = {
val wType = Types.newParameterizedType(
val wType = newParameterizedType(
implicitly[HKClassTag2[WType]].runtimeClass,
kTyp.getType,
vTyp.getType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import com.google.inject.Binder
import com.google.inject.binder._
import com.google.inject.name.Names
import java.lang.annotation.{Annotation => JAnnotation}
import scala.reflect.runtime.universe.TypeTag

import javax.inject.Provider

/**
Expand All @@ -44,15 +46,15 @@ import javax.inject.Provider
object BindingExtensions {

implicit class ScalaBinder(b: Binder) {
def bindType[T: Manifest] = b bind typeLiteral[T]
def bindType[T: TypeTag]: AnnotatedBindingBuilder[T] = b bind typeLiteral[T]
}

implicit class ScalaScopedBindingBuilder(b: ScopedBindingBuilder) {
def inType[TAnn <: JAnnotation : Manifest]() = b in cls[TAnn]
}

implicit class ScalaLinkedBindingBuilder[T](b: LinkedBindingBuilder[T]) {
def toType[TImpl <: T : Manifest] = b to typeLiteral[TImpl]
def toType[TImpl <: T : TypeTag] = b to typeLiteral[TImpl]

def toProviderType[TProvider <: Provider[_ <: T] : Manifest] = b toProvider cls[TProvider]
}
Expand Down
20 changes: 11 additions & 9 deletions src/main/scala/net/codingwell/scalaguice/ScalaMapBinder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import com.google.inject.multibindings.MapBinder
import com.google.inject.{Binder, Key, Module, Provider, TypeLiteral}
import net.codingwell.scalaguice.ScalaModule.ScalaLinkedBindingBuilder

import scala.reflect.runtime.universe.TypeTag
import scala.collection.{immutable => im}
import scala.reflect.ClassTag

/**
* Analog to Guice's MapBinder
Expand Down Expand Up @@ -69,23 +71,23 @@ object ScalaMapBinder {
* Returns a new mapbinder that collects entries of `K`/`V` in a
* [[scala.collection.immutable.Map]] that is itself bound with no binding annotation.
*/
def newMapBinder[K: Manifest, V: Manifest](binder: Binder): ScalaMapBinder[K, V] = {
def newMapBinder[K: TypeTag, V: TypeTag](binder: Binder): ScalaMapBinder[K, V] = {
newMapBinder(binder, typeLiteral[K], typeLiteral[V])
}

/**
* Returns a new mapbinder that collects entries of `K`/`V` in a
* [[scala.collection.immutable.Map]] that is itself bound with `annotation`.
*/
def newMapBinder[K: Manifest, V: Manifest](binder: Binder, annotation: Annotation): ScalaMapBinder[K, V] = {
def newMapBinder[K: TypeTag, V: TypeTag](binder: Binder, annotation: Annotation): ScalaMapBinder[K, V] = {
newMapBinder(binder, typeLiteral[K], typeLiteral[V], annotation)
}

/**
* Returns a new mapbinder that collects entries of `K`/`V` in a
* [[scala.collection.immutable.Map]] that is itself bound with `Ann`
*/
def newMapBinder[K: Manifest, V: Manifest, Ann <: Annotation : Manifest](binder: Binder): ScalaMapBinder[K, V] = {
def newMapBinder[K: TypeTag, V: TypeTag, Ann <: Annotation : ClassTag](binder: Binder): ScalaMapBinder[K, V] = {
newMapBinder(binder, typeLiteral[K], typeLiteral[V], cls[Ann])
}

Expand All @@ -104,9 +106,9 @@ object ScalaMapBinder {
/**
* Returns a new mapbinder that collects entries of `K`/`V` in a
* [[scala.collection.immutable.Map]] that is itself bound with no binding annotation. Note that
* `kTyp` and `vTyp` are ignored in favor of using the Manifest to capture type arguments.
* `kTyp` and `vTyp` are ignored in favor of using the TypeTag to capture type arguments.
*/
def newMapBinder[K: Manifest, V: Manifest](binder: Binder, kTyp: Class[K], vTyp: Class[V]): ScalaMapBinder[K, V] = {
def newMapBinder[K: TypeTag, V: TypeTag](binder: Binder, kTyp: Class[K], vTyp: Class[V]): ScalaMapBinder[K, V] = {
newMapBinder(binder, typeLiteral[K], typeLiteral[V])
}

Expand All @@ -125,9 +127,9 @@ object ScalaMapBinder {
/**
* Returns a new mapbinder that collects entries of `K`/`V` in a
* [[scala.collection.immutable.Map]] that is itself bound with `annotation`. Note that
* `kTyp` and `vTyp` are ignored in favor of using the Manifest to capture type arguments.
* `kTyp` and `vTyp` are ignored in favor of using the TypeTag to capture type arguments.
*/
def newMapBinder[K: Manifest, V: Manifest](binder: Binder, kTyp: Class[K], vTyp: Class[V],
def newMapBinder[K: TypeTag, V: TypeTag](binder: Binder, kTyp: Class[K], vTyp: Class[V],
annotation: Annotation): ScalaMapBinder[K, V] = {
newMapBinder(binder, typeLiteral[K], typeLiteral[V], annotation)
}
Expand All @@ -147,9 +149,9 @@ object ScalaMapBinder {
/**
* Returns a new mapbinder that collects entries of `K`/`V` in a
* [[scala.collection.immutable.Map]] that is itself bound with `annotationType`. Note that
* `kTyp` and `vTyp` are ignored in favor of using the Manifest to capture type arguments.
* `kTyp` and `vTyp` are ignored in favor of using the TypeTag to capture type arguments.
*/
def newMapBinder[K: Manifest, V: Manifest](binder: Binder, kTyp: Class[K], vTyp: Class[V],
def newMapBinder[K: TypeTag, V: TypeTag](binder: Binder, kTyp: Class[K], vTyp: Class[V],
annotationType: Class[_ <: Annotation]): ScalaMapBinder[K, V] = {
newMapBinder(binder, typeLiteral[K], typeLiteral[V], annotationType)
}
Expand Down
17 changes: 10 additions & 7 deletions src/main/scala/net/codingwell/scalaguice/ScalaOptionBinder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import com.google.inject.multibindings.OptionalBinder
import com.google.inject.{Binder, Key, Module, Provider, TypeLiteral}
import net.codingwell.scalaguice.ScalaModule.ScalaLinkedBindingBuilder

import scala.reflect.ClassTag
import scala.reflect.runtime.universe.TypeTag

/**
* Analog to Guice's OptionalBinder
*
Expand Down Expand Up @@ -53,31 +56,31 @@ object ScalaOptionBinder {
/**
* Returns a new optionbinder that binds an instance of `T` in a [[scala.Option]].
*/
def newOptionBinder[T: Manifest](binder: Binder): ScalaOptionBinder[T] = {
def newOptionBinder[T: TypeTag](binder: Binder): ScalaOptionBinder[T] = {
newOptionBinder(binder, typeLiteral[T])
}

/**
* Returns a new optionbinder that binds an instance of `T` in a [[scala.Option]] that is
* itself bound with a binding annotation `Ann`.
*/
def newOptionBinder[T: Manifest, Ann <: Annotation : Manifest](binder: Binder): ScalaOptionBinder[T] = {
def newOptionBinder[T: TypeTag, Ann <: Annotation : ClassTag](binder: Binder): ScalaOptionBinder[T] = {
newOptionBinder(binder, Key.get(typeLiteral[T], cls[Ann]))
}

/**
* Returns a new optionbinder that binds an instance of `T` in a [[scala.Option]] that is
* itself bound with a binding annotation.
*/
def newOptionBinder[T: Manifest](binder: Binder, annotation: Annotation): ScalaOptionBinder[T] = {
def newOptionBinder[T: TypeTag](binder: Binder, annotation: Annotation): ScalaOptionBinder[T] = {
newOptionBinder(binder, Key.get(typeLiteral[T], annotation))
}

/**
* Returns a new optionbinder that binds `typ` in a [[scala.Option]]. Note that
* `typ` is ignored in favor of using the Manifest to capture type arguments.
* `typ` is ignored in favor of using the TypeTag to capture type arguments.
*/
def newOptionBinder[T: Manifest](binder: Binder, typ: Class[T]): ScalaOptionBinder[T] = {
def newOptionBinder[T: TypeTag](binder: Binder, typ: Class[T]): ScalaOptionBinder[T] = {
newOptionBinder(binder, typeLiteral[T])
}

Expand Down Expand Up @@ -108,9 +111,9 @@ object ScalaOptionBinder {

/**
* Returns a new optionbinder that binds `typ` in a [[scala.Option]]. Note that
* `typ` is ignored in favor of using the Manifest to capture type arguments.
* `typ` is ignored in favor of using the TypeTag to capture type arguments.
*/
def newOptionalBinder[T: Manifest](binder: Binder, typ: Class[T]): ScalaOptionBinder[T] = {
def newOptionalBinder[T: TypeTag](binder: Binder, typ: Class[T]): ScalaOptionBinder[T] = {
newOptionBinder(binder, typ)
}

Expand Down
110 changes: 110 additions & 0 deletions src/main/scala/net/codingwell/scalaguice/TypeConversions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package net.codingwell.scalaguice

import java.lang.reflect.{Type => JavaType}

import com.google.inject.internal.MoreTypes.WildcardTypeImpl
import com.google.inject.util.Types._

import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe.{Type => ScalaType, WildcardType => ScalaWildCardType}

/**
* Copyright (C) 22/04/2018 - REstore NV
*/
private [scalaguice] object TypeConversions {
private val mirror = runtimeMirror(getClass.getClassLoader)
private val anyType = typeOf[Any]

object ArrayType {
private val arraySymbol = symbolOf[Array[_]]

def unapply(tpe: ScalaType): Option[ScalaType] = {
tpe match {
case TypeRef(pre, sym, args) if sym == arraySymbol => args.headOption
case _ => None
}

}
}

object ClassType {

def unapply(tpe: ScalaType): Option[(ClassSymbol, Seq[ScalaType])] = {
tpe match {
case TypeRef(pre, sym, args) if sym.isClass => Some((sym.asClass, args))
case _ => None
}

}
}

object WildcardType {
private val bottomType = typeOf[Nothing]
private val topType = typeOf[Any]
def unapply(tpe: ScalaType): Option[(List[ScalaType], List[ScalaType])] = {
tpe match {
case BoundedWildcardType(bounds) => unapply(bounds)
case TypeRef(pre, sym, args) if sym.isType && !sym.isSpecialized =>
unapply(sym.asType.info)
case TypeBounds(lo, hi) =>
val lowerBounds = if(lo =:= bottomType) Nil else List(lo)
Some((lowerBounds, List(hi)))
case _ => None
}

}

}

def scalaTypeToJavaType(scalaType: ScalaType): JavaType = {
scalaType.dealias match {
case `anyType` => classOf[java.lang.Object]
case ExistentialType(symbols, underlying) => scalaTypeToJavaType(underlying)
case ArrayType(argType) => arrayOf(scalaTypeToJavaType(argType))
case ClassType(symbol, args) => {
val rawType = mirror.runtimeClass(symbol)
val ownerType = findOwnerOf(symbol)
args.map(scalaTypeToJavaType) match {
case Nil => toWrapper(rawType)
case mappedArgs if ownerType.nonEmpty => newParameterizedTypeWithOwner(ownerType.get, rawType, mappedArgs:_*)
case mappedArgs => newParameterizedType(rawType, mappedArgs:_*)
}
}
case WildcardType(lowerBounds, upperBounds) => {
val mappedUpperBounds = upperBounds.map(scalaTypeToJavaType).toArray
val mappedLowerBounds = lowerBounds.map(scalaTypeToJavaType).toArray
new WildcardTypeImpl(mappedUpperBounds, mappedLowerBounds)
}
case _ => throw new UnsupportedOperationException(s"Could not convert scalaType $scalaType to a javaType")
}
}

private def findOwnerOf(symbol: universe.ClassSymbol): Option[JavaType] = {
val owner = symbol.owner

if (!owner.isPackage && (owner.isModuleClass && owner.owner.isPackage)) { //workaround for when owner is a top level object
// here we need to resolve class without the $ suffix for some reason
val clazz = mirror.staticClass(owner.asClass.fullName)
Some(mirror.runtimeClass(clazz))
} else if (!owner.isPackage && owner.isClass) {
Some(mirror.runtimeClass(owner.asClass))
} else {
None
}
}

private def toWrapper(c: JavaType) = c match {
case java.lang.Byte.TYPE => classOf[java.lang.Byte]
case java.lang.Short.TYPE => classOf[java.lang.Short]
case java.lang.Character.TYPE => classOf[java.lang.Character]
case java.lang.Integer.TYPE => classOf[java.lang.Integer]
case java.lang.Long.TYPE => classOf[java.lang.Long]
case java.lang.Float.TYPE => classOf[java.lang.Float]
case java.lang.Double.TYPE => classOf[java.lang.Double]
case java.lang.Boolean.TYPE => classOf[java.lang.Boolean]
case java.lang.Void.TYPE => classOf[java.lang.Void]
case cls => cls
}

}

0 comments on commit ac3437e

Please sign in to comment.