org.specs2.reflect.Classes.scala Maven / Gradle / Ivy
The newest version!
package org.specs2
package reflect
import scala.reflect.ClassTag
import ClassName._
import control._
import scala.util.control.NonFatal
import scalaz._, Scalaz._
import java.lang.reflect.Constructor
import eff._
/**
* This trait provides functions to instantiate classes
*/
trait Classes {
/**
* Try to create an instance of a given class by using whatever constructor is available
* and trying to instantiate the first parameter recursively if there is a parameter for that constructor.
*
* This is useful to instantiate nested classes which are referencing their outer class in their constructor
*/
def createInstance[T <: AnyRef](className: String, loader: ClassLoader, defaultInstances: List[AnyRef] = Nil)(implicit m: ClassTag[T]): Action[T] =
loadClass(className, loader) >>= { klass: Class[T] =>
createInstanceFromClass(klass, loader, defaultInstances)
}
def createInstanceFromClass[T <: AnyRef](klass: Class[T], loader: ClassLoader, defaultInstances: List[AnyRef] = Nil)(implicit m: ClassTag[T]): Action[T] =
findInstance[T](klass, loader, defaultInstances,
klass.getDeclaredConstructors.toList.filter(_.getParameterTypes.size <= 1).sortBy(_.getParameterTypes.size))
/** try to create an instance but return an exception if this is not possible */
def createInstanceEither[T <: AnyRef](className: String, loader: ClassLoader, defaultInstances: List[AnyRef] = Nil)(implicit m: ClassTag[T]): Action[Throwable \/ T] =
loadClassEither(className, loader) >>= { tc: Throwable \/ Class[T] =>
tc match {
case -\/(t) => Actions.ok(-\/(t))
case \/-(klass) =>
findInstance[T](klass, loader, defaultInstances,
klass.getDeclaredConstructors.toList.filter(_.getParameterTypes.size <= 1).sortBy(_.getParameterTypes.size)).map(\/-(_))
}
}
private def findInstance[T <: AnyRef : ClassTag](klass: Class[T], loader: ClassLoader, defaultInstances: List[AnyRef], cs: List[Constructor[_]], error: Option[ErrorEffect.Error] = None): Action[T] =
cs match {
case Nil => error.map(Actions.fromError[T]).getOrElse(Actions.fail[T]("Can't find a constructor for class "+klass.getName))
case c :: rest =>
runAction(createInstanceForConstructor[T](klass, c, loader, defaultInstances)).
fold(e => findInstance[T](klass, loader, defaultInstances, rest, Some(e)),
a => Actions.safe[T](a))
}
/**
* Given a class, a zero or one-parameter constructor, return an instance of that class
*/
private def createInstanceForConstructor[T <: AnyRef : ClassTag](klass: Class[_], constructor: Constructor[_],
loader: ClassLoader, defaultInstances: List[AnyRef] = Nil): Action[T] = {
constructor.setAccessible(true)
if (constructor.getParameterTypes.isEmpty)
newInstance(klass, constructor.newInstance())
else if (constructor.getParameterTypes.size == 1) {
defaultInstances.find(i => constructor.getParameterTypes.apply(0) isAssignableFrom i.getClass) match {
case None =>
// if the specification has a constructor with one parameter, it is either because
// it is a nested class
// or it might have a parameter that has a 0 args constructor
val constructorParameter =
createInstance(constructor.getParameterTypes.toSeq(0).getName, loader, defaultInstances).
orElse(createInstance[T](getOuterClassName(klass), loader, defaultInstances))
constructorParameter.flatMap(p => newInstance(klass, constructor.newInstance(p)))
case Some(instance) =>
newInstance(klass, constructor.newInstance(instance))
}
} else Actions.fail[T]("Can't find a suitable constructor for class "+klass.getName)
}
/** create a new instance for a given class and return a proper error if this fails */
private def newInstance[T](klass: Class[_], instance: =>Any): Action[T] =
try Actions.ok(instance.asInstanceOf[T])
catch { case NonFatal(t) =>
Actions.exception(UserException("cannot create an instance for class " + klass.getName, t))
}
/**
* Load a class, given the class name
*/
def loadClassEither[T <: AnyRef](className: String, loader: ClassLoader): Action[Throwable \/ Class[T]] = Actions.safe {
try \/-(loader.loadClass(className).asInstanceOf[Class[T]])
catch { case NonFatal(t) => -\/(t) }
}
def loadClass[T <: AnyRef](className: String, loader: ClassLoader): Action[Class[T]] =
loadClassEither(className, loader).flatMap((tc: Throwable \/ Class[T]) => tc.fold(Actions.exception, Actions.ok))
/** @return true if a class can be loaded */
def existsClass(className: String, loader: ClassLoader): Action[Boolean] = Actions.safe {
try { loader.loadClass(className); true }
catch { case NonFatal(t) => false }
}
}
/**
* This object provides simple functions to instantiate classes.
*/
object Classes extends Classes