diff --git a/core/play-guice/src/test/scala/play/api/inject/ModulesSpec.scala b/core/play-guice/src/test/scala/play/api/inject/ModulesSpec.scala index f83b3e2b4e2..1ff1b7e3555 100644 --- a/core/play-guice/src/test/scala/play/api/inject/ModulesSpec.scala +++ b/core/play-guice/src/test/scala/play/api/inject/ModulesSpec.scala @@ -8,9 +8,8 @@ import com.google.inject.AbstractModule import com.typesafe.config.Config import org.specs2.matcher.BeEqualTypedValueCheck import org.specs2.mutable.Specification -import play.api.Configuration -import play.api.Environment -import play.{ Environment => JavaEnvironment } +import play.api.{Configuration, Environment, PlayException} +import play.{Environment => JavaEnvironment} class ModulesSpec extends Specification { @@ -63,6 +62,21 @@ class ModulesSpec extends Specification { } } + "list current constructor parameters when any match" in { + val env = Environment.simple() + val conf = Configuration( + "play.modules.enabled" -> Seq( + classOf[InvalidConfigModule].getName + ) + ) + + Modules.locate(env, conf) must throwA[PlayException].like { case e => + val firstConstructorParameters = "(com.typesafe.config.Config, java.lang.String, long)" + val secondConstructorParameters = "(com.typesafe.config.Config, java.lang.String)" + e.getMessage must contain(s"$firstConstructorParameters, $secondConstructorParameters") + } + } + } } @@ -78,3 +92,8 @@ class ScalaGuiceModule(val environment: Environment, val configuration: Configur class JavaGuiceConfigModule(val environment: JavaEnvironment, val config: Config) extends AbstractModule { override def configure(): Unit = () } + +class InvalidConfigModule(val config: Config, val ignored: String) extends AbstractModule { + def this(config: Config, ignored: String, useless: Long) = this(config, ignored) + override def configure(): Unit = () +} diff --git a/core/play/src/main/java/play/libs/reflect/ConstructorUtils.java b/core/play/src/main/java/play/libs/reflect/ConstructorUtils.java index ffd21bb2284..928779b33a9 100644 --- a/core/play/src/main/java/play/libs/reflect/ConstructorUtils.java +++ b/core/play/src/main/java/play/libs/reflect/ConstructorUtils.java @@ -18,6 +18,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; +import java.util.Arrays; /** Imported from apache.commons.lang3 3.6 */ public class ConstructorUtils { @@ -115,6 +116,23 @@ public static Constructor getMatchingAccessibleConstructor( return result; } + /** + * Finds the class names of all accessible constructors. + * + * @param the constructor type + * @return the constructors parameters, empty array if no matching accessible constructor found + */ + public static String[][] getConstructorParameters(final Class type) { + Constructor[] allConstructors = type.getDeclaredConstructors(); + return Arrays.stream(allConstructors) + .map( + cons -> + Arrays.stream(cons.getParameterTypes()) + .map(Class::getCanonicalName) + .toArray(String[]::new)) + .toArray(String[][]::new); + } + /** * Learn whether the specified class is generally accessible, i.e. is declared in an entirely * {@code public} manner. diff --git a/core/play/src/main/scala/play/api/inject/Module.scala b/core/play/src/main/scala/play/api/inject/Module.scala index 1324d8c94a7..70082bc6b1d 100644 --- a/core/play/src/main/scala/play/api/inject/Module.scala +++ b/core/play/src/main/scala/play/api/inject/Module.scala @@ -171,7 +171,13 @@ object Modules { tryConstruct() } .getOrElse { - throw new PlayException("No valid constructors", "Module [" + className + "] cannot be instantiated.") + val parameters: Array[Array[String]] = ConstructorUtils.getConstructorParameters(moduleClass) + throw new PlayException( + s""" + |No valid public constructors for ${moduleClass.getCanonicalName}. Expected one of: + ${parameters.map(_.mkString("(",", ", ")")).mkString(", ")} + """.stripMargin, + "Module [" + className + "] cannot be instantiated.") } } catch { case e: PlayException => throw e