Skip to content

Commit

Permalink
Added util methods to create class/object by name + retrieve type tag…
Browse files Browse the repository at this point in the history
… by type name (#310)
  • Loading branch information
tovbinm committed May 9, 2019
1 parent 40e4929 commit b6bc25a
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ final class OpPipelineStageReader(val originalStage: OpPipelineStageBase)
// Get the model class

// Make the ctor function used for creating a model instance
def ctorArgs(argName: String, argSymbol: Symbol): Try[Any] = Try {
val ctorArgs: (String, Symbol) => Try[Any] = (argName, argSymbol) => Try {
val anyValue = ctorArgsMap.getOrElse(argName,
throw new RuntimeException(s"Ctor argument '$argName' was not found for model class '$modelClassName'")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import scala.reflect.runtime.universe._
import scala.reflect.runtime.{universe => runtimeUniverse}
import scala.util.{Failure, Success}


/**
* Various Reflection helpers
*/
Expand Down Expand Up @@ -78,6 +77,38 @@ object ReflectionUtils {
ctor.apply(args.map(_._2): _*).asInstanceOf[T]
}

/**
* Create a new of type T given it's class name
*
* @param className instance class
* @tparam T type T
* @return new instance of T
*/
def newInstance[T](className: String): T = newInstance[T](className, defaultClassLoader)

/**
* Create a new of type T given it's class name
*
* @param className instance class
* @param classLoader class loader to use
* @tparam T type T
* @return new instance of T
*/
def newInstance[T](className: String, classLoader: ClassLoader): T = try {
val klazz = ReflectionUtils.classForName(className, classLoader)
// Try to create an instance only if it has a single no-args ctor or fall back to object
val res = klazz.getConstructors.find(_.getParameterCount == 0) match {
case Some(c) => c.newInstance()
case _ => klazz.getField("MODULE$").get(klazz)
}
res.asInstanceOf[T]
} catch {
case e: Exception => throw new RuntimeException(
s"Failed to create an instance of class '$className'. " +
"Class has to either have a no-args ctor or be an object.", e
)
}

/**
* Copy any instance using reflection.
*
Expand Down Expand Up @@ -199,7 +230,6 @@ object ReflectionUtils {
*/
def classForName(name: String, classLoader: ClassLoader = defaultClassLoader): Class[_] = classLoader.loadClass(name)


/**
* Fully dealiased type name for [[Type]].
* This method performs a recursive dealising vs a regular type.dealias, which does on one level only.
Expand All @@ -222,19 +252,33 @@ object ReflectionUtils {
/**
* Create a TypeTag for Type
*
* @param rtm runtime mirror
* @param tpe type
* @param rtm runtime mirror
* @tparam T type T
* @return TypeTag[T]
*/
def typeTagForType[T](rtm: Mirror = runtimeMirror(), tpe: Type): TypeTag[T] = {
def typeTagForType[T](tpe: Type, rtm: Mirror = runtimeMirror()): TypeTag[T] = {
TypeTag(rtm, new api.TypeCreator {
def apply[U <: api.Universe with Singleton](m: api.Mirror[U]): U#Type =
if (m eq rtm) tpe.asInstanceOf[U#Type]
else throw new IllegalArgumentException(s"Type tag defined in $rtm cannot be migrated to other mirrors.")
})
}

/**
* Returns a Type Tag by Type name
*
* @param typeName type name
* @param rtm runtime mirror
* @tparam T type T
* @return TypeTag[T]
*/
def typeTagForTypeName[T](typeName: String, rtm: Mirror = runtimeMirror()): TypeTag[T] = {
val clazz = classForName(typeName)
val typee = rtm.classSymbol(clazz).toType
typeTagForType[T](typee, rtm)
}

/**
* Create a ClassTag for a WeakTypeTag
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ class TestClass[T]

class TestShouldFailClass(val x: Int, y: String)

class TestClassNoArgs() {
val x = 123
}

object TestObject {
val x = 456
}

class TestClassVar {
var myVar: Option[String] = None
Expand Down Expand Up @@ -103,6 +110,14 @@ class ReflectionUtilsTest extends FlatSpec with Matchers {
resttag.tpe =:= ttag.tpe shouldBe true
}

it should "return a TypeTag of a type name" in {
val ttag = typeTag[TestValClass]
val typeName = ttag.tpe.typeSymbol.fullName
val resttag = ReflectionUtils.typeTagForTypeName[TestValClass](typeName)
resttag shouldBe ttag
resttag.tpe =:= ttag.tpe shouldBe true
}

it should "return a dealiased TypeTag for an type alias" in {
val tTag = typeTag[scala.collection.immutable.List[String]]
val aliasTag = typeTag[ListStringAlias]
Expand Down Expand Up @@ -181,9 +196,24 @@ class ReflectionUtilsTest extends FlatSpec with Matchers {
instance.y shouldBe 2
}

it should "create a new instance by a class name" in {
val instance = ReflectionUtils.newInstance[TestClassNoArgs](classOf[TestClassNoArgs].getName)
instance.x shouldBe 123
}

it should "return object instance by its class name" in {
val instance = ReflectionUtils.newInstance[TestObject.type](TestObject.getClass.getName)
instance.x shouldBe 456
}

it should "fail to create a class if neither args ctor is not present nor it's an object" in {
a[RuntimeException] should be thrownBy
ReflectionUtils.newInstance[TestShouldFailClass](classOf[TestShouldFailClass].getName)
}

it should "fail to copy an instance that has a private ctor argument" in {
val orig = new TestShouldFailClass(x = 1, y = "private ctor arg")
an[RuntimeException] should be thrownBy ReflectionUtils.copy(orig)
a[RuntimeException] should be thrownBy ReflectionUtils.copy(orig)
}

it should "return a class for name" in {
Expand Down

0 comments on commit b6bc25a

Please sign in to comment.