├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.sbt
├── js
└── src
│ └── main
│ └── scala
│ └── org
│ └── portablescala
│ └── reflect
│ ├── Reflect.scala
│ ├── annotation
│ └── package.scala
│ └── package.scala
├── jvm
└── src
│ └── main
│ ├── java
│ └── org
│ │ └── portablescala
│ │ └── reflect
│ │ └── annotation
│ │ └── EnableReflectiveInstantiation.java
│ └── scala
│ └── org
│ └── portablescala
│ └── reflect
│ ├── InstantiatableClass.scala
│ ├── InvokableConstructor.scala
│ ├── LoadableModuleClass.scala
│ ├── Reflect.scala
│ └── internal
│ └── Macros.scala
├── native
└── src
│ └── main
│ └── scala
│ └── org
│ └── portablescala
│ └── reflect
│ ├── Reflect.scala
│ ├── annotation
│ └── package.scala
│ └── package.scala
├── project
├── build.properties
└── plugins.sbt
└── shared
└── src
└── test
└── scala
└── org
└── portablescala
└── reflect
└── ReflectTest.scala
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request:
7 | branches:
8 | - master
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | scalaversion: ["2.11.12", "2.12.13", "2.13.4"]
16 | platform: ["JVM", "JS", "JS-0.6.x", "Native"]
17 | include:
18 | # Earliest Scala versions supported by Scala Native 0.5.x
19 | - scalaversion: "2.12.14"
20 | platform: "Native-0.5.x"
21 | - scalaversion: "2.13.8"
22 | platform: "Native-0.5.x"
23 | env:
24 | SCALAJS_VERSION: "${{ matrix.platform == 'JS-0.6.x' && '0.6.33' || '' }}"
25 | SCALANATIVE_VERSION: "${{ matrix.platform == 'Native-0.5.x' && '0.5.2' || '' }}"
26 | PROJECT_NAME: "portable-scala-reflect${{ matrix.platform == 'JS-0.6.x' && 'JS' || matrix.platform == 'Native-0.5.x' && 'Native' || matrix.platform }}"
27 | steps:
28 | - uses: actions/checkout@v2
29 | - uses: actions/setup-node@v4
30 | with:
31 | node-version: '16.x'
32 | - uses: olafurpg/setup-scala@v10
33 | with:
34 | java-version: "adopt@1.8"
35 | - uses: coursier/cache-action@v5
36 | - name: Unit tests
37 | run: sbt "++${{ matrix.scalaversion }}!" "${{ env.PROJECT_NAME }}/test"
38 | - name: Test generate documentation
39 | run: sbt "++${{ matrix.scalaversion }}!" "${{ env.PROJECT_NAME }}/doc"
40 | - name: MiMa
41 | run: sbt "++${{ matrix.scalaversion }}!" "${{ env.PROJECT_NAME }}/mimaReportBinaryIssues"
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2017 EPFL
2 |
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification,
6 | are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice,
9 | this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 | * Neither the name of the EPFL nor the names of its contributors
14 | may be used to endorse or promote products derived from this software
15 | without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # portable-scala-reflect: platform-agnostic reflection for Scala
2 |
3 | [](https://travis-ci.org/portable-scala/portable-scala-reflect)
4 | [](https://www.scala-js.org/)
5 | [](https://www.scala-js.org)
6 | [](https://javadoc.io/doc/org.portable-scala/portable-scala-reflect_2.12/latest/org/portablescala/reflect/index.html)
7 |
8 | The various platforms supported by Scala (JVM, JavaScript and Native) have varying support for run-time reflection.
9 | Even the subset of functionality that is supported across the platforms is exposed through different APIs.
10 |
11 | This library exposes a unified, portable API for run-time reflection in Scala.
12 | It supports Scala/JVM, Scala.js and Scala Native.
13 | To be portable, only the subset of reflection capabilities that is implementable across all platforms is exposed.
14 |
15 | ## Setup
16 |
17 | Add the following line to your (cross-)project's settings in `build.sbt`:
18 |
19 | ```scala
20 | libraryDependencies += "org.portable-scala" %%% "portable-scala-reflect" % "1.1.3"
21 | ```
22 |
23 | `portable-scala-reflect` 1.1.3 supports:
24 |
25 | * Scala 2.11.x, 2.12.x and 2.13.x
26 | * Scala/JVM
27 | * Scala.js 0.6.x and 1.x
28 | * Scala Native 0.4.x and 0.5.x
29 |
30 | ## Usage
31 |
32 | ### Instantiate a class given its name
33 |
34 | In order to reflectively instantiate a class, `portable-scala-reflect` demands that you "enable reflective instantiation" for it.
35 | This is the case if:
36 |
37 | * The class is annotated with `@org.portablescala.reflect.annotation.EnableReflectiveInstantiation`, or
38 | * The class directly or indirectly extend a class or trait annotated with that annotation.
39 |
40 | For example:
41 |
42 | ```scala
43 | import org.portablescala.reflect.annotation.EnableReflectiveInstantiation
44 |
45 | @EnableReflectiveInstantiation
46 | class A // discoverable
47 |
48 | @EnableReflectiveInstantiation
49 | trait SuperTrait
50 |
51 | class B extends SuperTrait // discoverable
52 |
53 | class C extends B // discoverable
54 |
55 | class D // NOT discoverable
56 | ```
57 |
58 | In addition, a class must satisfy the following properties to be discoverable:
59 |
60 | * It must be concrete
61 | * It must have at least one public constructor
62 | * It must not be a local class, i.e., defined inside a method
63 |
64 | If a class is discoverable, you can use the method `lookupInstantiatableClass` of `org.portablescala.reflect.Reflect` to get an `InstantiatableClass` representing it using:
65 |
66 | ```scala
67 | import org.portablescala.reflect._
68 |
69 | val clsOpt = Reflect.lookupInstantiatableClass("fully.qualified.ClassName", someClassLoader)
70 | ```
71 |
72 | The `someClassLoader` argument is optional, and defaults to the current class loader at call site.
73 | It is only meaningful on the JVM.
74 |
75 | Once you have an `InstantiatableClass`, you can use its methods to instantiate the class.
76 | A typical use case is to instantiate the class using its no-argument constructor:
77 |
78 | ```scala
79 | val cls = clsOpt.get // or any safer way to extract the Option
80 | val instance = cls.newInstance()
81 | ```
82 |
83 | For other constructors, you need to use `declaredConstructors` or `getConstructor()` to find the appropriate `InvokableConstructor`, given its parameter types:
84 |
85 | ```scala
86 | val ctor = cls.getConstructor(classOf[Int], classOf[String])
87 | val instance = ctor.newInstance(42, "hello")
88 | ```
89 |
90 | Consult the Scaladoc of each method for more details (conditions, exceptional behavior, etc.).
91 |
92 | ### Load the singleton instance of an `object` given its name
93 |
94 | Similarly to classes, you must enable reflective instantiation on an `object` to be able to reflectively load it.
95 | In addition, the object must satisfy the following property to be discoverable:
96 |
97 | * It must be "static", i.e., top-level or defined inside a static object
98 |
99 | Use the method `Reflect.lookupLoadableModuleClass` to discover an object ("module" is the technical name of an `object` in Scala).
100 |
101 | ```scala
102 | import org.portablescala.reflect._
103 |
104 | val clsOpt = Reflect.lookupLoadableModuleClass("fully.qualified.ObjectName$", someClassLoader)
105 | ```
106 |
107 | The `$` at the end of the object name is required.
108 |
109 | Once you have a `LoadableModuleClass`, you can use its `loadModule()` method to load the singleton instance of the object:
110 |
111 | ```scala
112 | val cls = clsOpt.get // or any safer way to extract the Option
113 | val instance = cls.loadModule()
114 | ```
115 |
116 | Consult the Scaladoc of each method for more details (conditions, exceptional behavior, etc.).
117 |
118 | ### Reflectively call methods
119 |
120 | `portable-scala-reflect` does not provide any API to reflectively call methods.
121 | If the name and signature of a method are statically known, it is possible to use a structural type in Scala instead, as follows:
122 |
123 | ```scala
124 | val obj: Any = ??? // an object on which we want to call a method.
125 |
126 | type ReflectiveAccess = {
127 | def theMethod(x: Int): Int
128 | }
129 |
130 | val result = obj.asInstanceOf[ReflectiveAccess].theMethod(42)
131 | ```
132 |
133 | If the name or signature of the method is not statically known, you are out of luck: there is no way to perform such a reflective call in Scala.js nor Scala Native, so `portable-scala-reflect` does not provide any API for it.
134 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | // shadow sbt-scalajs' crossProject and CrossType until Scala.js 1.0.0 is released
2 | import com.typesafe.tools.mima.core._
3 | import sbtcrossproject.{crossProject, CrossType}
4 |
5 | val previousVersion = "1.1.3"
6 |
7 | inThisBuild(Def.settings(
8 | crossScalaVersions := Seq("2.12.13", "2.11.12", "2.13.4"),
9 | scalaVersion := crossScalaVersions.value.head,
10 | version := "1.1.4-SNAPSHOT",
11 | organization := "org.portable-scala",
12 |
13 | scalacOptions ++= Seq(
14 | "-deprecation",
15 | "-feature",
16 | "-encoding",
17 | "utf-8",
18 | "-Xfatal-warnings",
19 | ),
20 |
21 | homepage := Some(url("https://github.com/portable-scala/portable-scala-reflect")),
22 | licenses += ("BSD New",
23 | url("https://github.com/portable-scala/portable-scala-reflect/blob/master/LICENSE")),
24 | scmInfo := Some(ScmInfo(
25 | url("https://github.com/portable-scala/portable-scala-reflect"),
26 | "scm:git:git@github.com:portable-scala/portable-scala-reflect.git",
27 | Some("scm:git:git@github.com:portable-scala/portable-scala-reflect.git"))),
28 | ))
29 |
30 | lazy val `portable-scala-reflect` = crossProject(JSPlatform, JVMPlatform, NativePlatform)
31 | .in(file("."))
32 | .settings(
33 | scalacOptions in (Compile, doc) -= "-Xfatal-warnings",
34 |
35 | mimaPreviousArtifacts +=
36 | organization.value %%% moduleName.value % previousVersion,
37 |
38 | publishMavenStyle := true,
39 | publishTo := {
40 | val nexus = "https://oss.sonatype.org/"
41 | if (isSnapshot.value)
42 | Some("snapshots" at nexus + "content/repositories/snapshots")
43 | else
44 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
45 | },
46 | pomExtra := (
47 |
48 |
49 | sjrd
50 | Sébastien Doeraene
51 | https://github.com/sjrd/
52 |
53 |
54 | gzm0
55 | Tobias Schlatter
56 | https://github.com/gzm0/
57 |
58 |
59 | densh
60 | Denys Shabalin
61 | https://github.com/densh/
62 |
63 |
64 | ),
65 | pomIncludeRepository := { _ => false },
66 | )
67 | .jvmSettings(
68 | // Macros
69 | libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value % Provided,
70 |
71 | // Speed up compilation a bit. Our .java files do not need to see the .scala files.
72 | compileOrder := CompileOrder.JavaThenScala,
73 |
74 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test",
75 | )
76 | .jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))
77 | .nativeConfigure(_.enablePlugins(ScalaNativeJUnitPlugin))
78 |
--------------------------------------------------------------------------------
/js/src/main/scala/org/portablescala/reflect/Reflect.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | import scala.scalajs.reflect.{Reflect => SJSReflect}
4 |
5 | object Reflect {
6 | /** Reflectively looks up a loadable module class.
7 | *
8 | * A module class is the technical term referring to the class of a Scala
9 | * `object`. The object or one of its super types (classes or traits) must
10 | * be annotated with
11 | * [[org.portablescala.reflect.annotation.EnableReflectiveInstantiation @EnableReflectiveInstantiation]].
12 | * Moreover, the object must be "static", i.e., declared at the top-level of
13 | * a package or inside a static object.
14 | *
15 | * If the module class cannot be found, either because it does not exist,
16 | * was not `@EnableReflectiveInstantiation` or was not static, this method
17 | * returns `None`.
18 | *
19 | * @param fqcn
20 | * Fully-qualified name of the module class, including its trailing `$`
21 | */
22 | def lookupLoadableModuleClass(fqcn: String): Option[LoadableModuleClass] =
23 | SJSReflect.lookupLoadableModuleClass(fqcn)
24 |
25 | /** Reflectively looks up a loadable module class.
26 | *
27 | * In Scala.js, this method ignores the parameter `loader`. Calling this
28 | * method is equivalent to
29 | * {{{
30 | * Reflect.lookupLoadableModuleClass(fqcn)
31 | * }}}
32 | *
33 | * @param fqcn
34 | * Fully-qualified name of the module class, including its trailing `$`
35 | *
36 | * @param loader
37 | * Ignored
38 | */
39 | def lookupLoadableModuleClass(fqcn: String,
40 | loader: ClassLoader): Option[LoadableModuleClass] = {
41 | lookupLoadableModuleClass(fqcn)
42 | }
43 |
44 | /** Reflectively looks up an instantiable class.
45 | *
46 | * The class or one of its super types (classes or traits) must be annotated
47 | * with
48 | * [[org.portablescala.reflect.annotation.EnableReflectiveInstantiation @EnableReflectiveInstantiation]].
49 | * Moreover, the class must not be abstract, nor be a local class (i.e., a
50 | * class defined inside a `def`). Inner classes (defined inside another
51 | * class) are supported.
52 | *
53 | * If the class cannot be found, either because it does not exist,
54 | * was not `@EnableReflectiveInstantiation` or was abstract or local, this
55 | * method returns `None`.
56 | *
57 | * @param fqcn
58 | * Fully-qualified name of the class
59 | */
60 | def lookupInstantiatableClass(fqcn: String): Option[InstantiatableClass] =
61 | SJSReflect.lookupInstantiatableClass(fqcn)
62 |
63 | /** Reflectively looks up an instantiable class.
64 | *
65 | * In Scala.js, this method ignores the parameter `loader`. Calling this
66 | * method is equivalent to
67 | * {{{
68 | * Reflect.lookupInstantiatableClass(fqcn)
69 | * }}}
70 | *
71 | * @param fqcn
72 | * Fully-qualified name of the class
73 | *
74 | * @param loader
75 | * Ignored
76 | */
77 | def lookupInstantiatableClass(fqcn: String,
78 | loader: ClassLoader): Option[InstantiatableClass] = {
79 | lookupInstantiatableClass(fqcn)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/js/src/main/scala/org/portablescala/reflect/annotation/package.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | package object annotation {
4 | type EnableReflectiveInstantiation =
5 | scala.scalajs.reflect.annotation.EnableReflectiveInstantiation
6 | }
7 |
--------------------------------------------------------------------------------
/js/src/main/scala/org/portablescala/reflect/package.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala
2 |
3 | package object reflect {
4 | type InstantiatableClass = scala.scalajs.reflect.InstantiatableClass
5 |
6 | type InvokableConstructor = scala.scalajs.reflect.InvokableConstructor
7 |
8 | type LoadableModuleClass = scala.scalajs.reflect.LoadableModuleClass
9 | }
10 |
--------------------------------------------------------------------------------
/jvm/src/main/java/org/portablescala/reflect/annotation/EnableReflectiveInstantiation.java:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect.annotation;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Retention(RetentionPolicy.RUNTIME)
6 | @Target(ElementType.TYPE)
7 | public @interface EnableReflectiveInstantiation {}
8 |
--------------------------------------------------------------------------------
/jvm/src/main/scala/org/portablescala/reflect/InstantiatableClass.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | import java.lang.reflect.InvocationTargetException
4 |
5 | /** A wrapper for a class that can be instantiated.
6 | *
7 | * @param runtimeClass
8 | * The `java.lang.Class[_]` representing the class.
9 | */
10 | final class InstantiatableClass private[reflect] (val runtimeClass: Class[_]) {
11 | /** A list of the public constructors declared in this class. */
12 | val declaredConstructors: List[InvokableConstructor] =
13 | runtimeClass.getConstructors().map(new InvokableConstructor(_)).toList
14 |
15 | /** Instantiates this class using its zero-argument constructor.
16 | *
17 | * @throws java.lang.InstantiationException
18 | * (caused by a `NoSuchMethodException`)
19 | * If this class does not have a public zero-argument constructor.
20 | */
21 | def newInstance(): Any = {
22 | try {
23 | runtimeClass.getDeclaredConstructor().newInstance()
24 | } catch {
25 | case e: InvocationTargetException if e.getCause != null =>
26 | throw e.getCause
27 | case e: NoSuchMethodException =>
28 | throw new InstantiationException(runtimeClass.getName).initCause(e)
29 | case _: IllegalAccessException =>
30 | /* The constructor exists but is private; make it look like it does not
31 | * exist at all.
32 | */
33 | throw new InstantiationException(runtimeClass.getName).initCause(
34 | new NoSuchMethodException(runtimeClass.getName + ".()"))
35 | }
36 | }
37 |
38 | /** Looks up a public constructor identified by the types of its formal
39 | * parameters.
40 | *
41 | * If no such public constructor exists, returns `None`.
42 | */
43 | def getConstructor(parameterTypes: Class[_]*): Option[InvokableConstructor] = {
44 | try {
45 | Some(new InvokableConstructor(
46 | runtimeClass.getConstructor(parameterTypes: _*)))
47 | } catch {
48 | case _: NoSuchMethodException => None
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/jvm/src/main/scala/org/portablescala/reflect/InvokableConstructor.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | import java.lang.reflect.Constructor
4 |
5 | /** A description of a constructor that can reflectively invoked. */
6 | final class InvokableConstructor private[reflect] (ctor: Constructor[_]) {
7 | /** The `Class[_]` objects representing the formal parameters of this
8 | * constructor.
9 | */
10 | val parameterTypes: List[Class[_]] = ctor.getParameterTypes().toList
11 |
12 | /** Invokes this constructor to instantiate a new object.
13 | *
14 | * If the underlying constructor throws an exception `e`, then `newInstance`
15 | * throws `e`, unlike `java.lang.reflect.Constructor.newInstance` which
16 | * would wrap it in a `java.lang.reflect.InvocationTargetException`.
17 | *
18 | * @param args
19 | * The formal arguments to be given to the constructor.
20 | */
21 | def newInstance(args: Any*): Any = {
22 | try {
23 | ctor.newInstance(args.asInstanceOf[Seq[AnyRef]]: _*)
24 | } catch {
25 | case e: java.lang.reflect.InvocationTargetException =>
26 | val cause = e.getCause
27 | if (cause == null)
28 | throw e
29 | else
30 | throw cause
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/jvm/src/main/scala/org/portablescala/reflect/LoadableModuleClass.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | /** A wrapper for a module class that can be loaded.
4 | *
5 | * @param runtimeClass
6 | * The `java.lang.Class[_]` representing the module class.
7 | */
8 | final class LoadableModuleClass private[reflect] (val runtimeClass: Class[_]) {
9 | /** Loads the module instance and returns it.
10 | *
11 | * If the underlying constructor throws an exception `e`, then `loadModule`
12 | * throws `e`, unlike `java.lang.reflect.Field.get` which would wrap it in a
13 | * `java.lang.reflect.ExceptionInInitializerError`.
14 | */
15 | def loadModule(): Any = {
16 | try {
17 | runtimeClass.getField("MODULE$").get(null)
18 | } catch {
19 | case e: java.lang.ExceptionInInitializerError =>
20 | val cause = e.getCause
21 | if (cause == null)
22 | throw e
23 | else
24 | throw cause
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/jvm/src/main/scala/org/portablescala/reflect/Reflect.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | import scala.language.experimental.macros
4 | import scala.collection.mutable
5 | import java.lang.reflect._
6 | import org.portablescala.reflect.annotation._
7 | import org.portablescala.reflect.internal.Macros
8 |
9 | object Reflect {
10 |
11 | /** Reflectively looks up a loadable module class using the current class
12 | * loader.
13 | *
14 | * A module class is the technical term referring to the class of a Scala
15 | * `object`. The object or one of its super types (classes or traits) must
16 | * be annotated with
17 | * [[org.portablescala.reflect.annotation.EnableReflectiveInstantiation @EnableReflectiveInstantiation]].
18 | * Moreover, the object must be "static", i.e., declared at the top-level of
19 | * a package or inside a static object.
20 | *
21 | * If the module class cannot be found, either because it does not exist,
22 | * was not `@EnableReflectiveInstantiation` or was not static, this method
23 | * returns `None`.
24 | *
25 | * This method is equivalent to calling
26 | * {{{
27 | * Reflect.lookupLoadableModuleClass(fqcn, this.getClass.getClassLoader)
28 | * }}}
29 | *
30 | * @param fqcn
31 | * Fully-qualified name of the module class, including its trailing `$`
32 | */
33 | def lookupLoadableModuleClass(fqcn: String): Option[LoadableModuleClass] =
34 | macro Macros.lookupLoadableModuleClass
35 |
36 | /** Reflectively looks up a loadable module class.
37 | *
38 | * A module class is the technical term referring to the class of a Scala
39 | * `object`. The object or one of its super types (classes or traits) must
40 | * be annotated with
41 | * [[org.portablescala.reflect.annotation.EnableReflectiveInstantiation @EnableReflectiveInstantiation]].
42 | * Moreover, the object must be "static", i.e., declared at the top-level of
43 | * a package or inside a static object.
44 | *
45 | * If the module class cannot be found, either because it does not exist,
46 | * was not `@EnableReflectiveInstantiation` or was not static, this method
47 | * returns `None`.
48 | *
49 | * @param fqcn
50 | * Fully-qualified name of the module class, including its trailing `$`
51 | *
52 | * @param loader
53 | * Class loader to use to load the module class
54 | */
55 | def lookupLoadableModuleClass(fqcn: String,
56 | loader: ClassLoader): Option[LoadableModuleClass] = {
57 | load(fqcn, loader).filter(isModuleClass).map(new LoadableModuleClass(_))
58 | }
59 |
60 | /** Reflectively looks up an instantiatable class using the current class
61 | * loader.
62 | *
63 | * The class or one of its super types (classes or traits) must be annotated
64 | * with
65 | * [[org.portablescala.reflect.annotation.EnableReflectiveInstantiation @EnableReflectiveInstantiation]].
66 | * Moreover, the class must not be abstract, nor be a local class (i.e., a
67 | * class defined inside a `def` or inside an anonymous function). Inner
68 | * classes (defined inside another class) are supported.
69 | *
70 | * If the class cannot be found, either because it does not exist,
71 | * was not `@EnableReflectiveInstantiation` or was abstract or local, this
72 | * method returns `None`.
73 | *
74 | * This method is equivalent to calling
75 | * {{{
76 | * Reflect.lookupInstantiatableClass(fqcn, this.getClass.getClassLoader)
77 | * }}}
78 | *
79 | * @param fqcn
80 | * Fully-qualified name of the class
81 | */
82 | def lookupInstantiatableClass(fqcn: String): Option[InstantiatableClass] =
83 | macro Macros.lookupInstantiatableClass
84 |
85 | /** Reflectively looks up an instantiatable class.
86 | *
87 | * The class or one of its super types (classes or traits) must be annotated
88 | * with
89 | * [[org.portablescala.reflect.annotation.EnableReflectiveInstantiation @EnableReflectiveInstantiation]].
90 | * Moreover, the class must not be abstract, nor be a local class (i.e., a
91 | * class defined inside a `def` or inside an anonymous function). Inner
92 | * classes (defined inside another class) are supported.
93 | *
94 | * If the class cannot be found, either because it does not exist,
95 | * was not `@EnableReflectiveInstantiation` or was abstract or local, this
96 | * method returns `None`.
97 | *
98 | * @param fqcn
99 | * Fully-qualified name of the class
100 | *
101 | * @param loader
102 | * Class loader to use to load the class
103 | */
104 | def lookupInstantiatableClass(fqcn: String,
105 | loader: ClassLoader): Option[InstantiatableClass] = {
106 | load(fqcn, loader).filter(isInstantiatableClass).map(new InstantiatableClass(_))
107 | }
108 |
109 | private def isModuleClass(clazz: Class[_]): Boolean = {
110 | try {
111 | val fld = clazz.getField("MODULE$")
112 | clazz.getName.endsWith("$") && (fld.getModifiers & Modifier.STATIC) != 0
113 | } catch {
114 | case _: NoSuchFieldException => false
115 | }
116 | }
117 |
118 | private def isInstantiatableClass(clazz: Class[_]): Boolean = {
119 | /* A local class will have a non-null *enclosing* class, but a null
120 | * *declaring* class. For a top-level class, both are null, and for an
121 | * inner class (non-local), both are the same non-null class.
122 | */
123 | def isLocalClass: Boolean =
124 | clazz.getEnclosingClass() != clazz.getDeclaringClass()
125 |
126 | (clazz.getModifiers() & Modifier.ABSTRACT) == 0 &&
127 | clazz.getConstructors().length > 0 &&
128 | !isModuleClass(clazz) &&
129 | !isLocalClass
130 | }
131 |
132 | private def load(fqcn: String, loader: ClassLoader): Option[Class[_]] = {
133 | try {
134 | /* initialize = false, so that the constructor of a module class is not
135 | * executed right away. It will only be executed when we call
136 | * `loadModule`.
137 | */
138 | val clazz = Class.forName(fqcn, false, loader)
139 | if (inheritsAnnotation(clazz)) Some(clazz)
140 | else None
141 | } catch {
142 | case _: ClassNotFoundException => None
143 | }
144 | }
145 |
146 | private def inheritsAnnotation(clazz: Class[_]): Boolean = {
147 | val cache = mutable.Map.empty[Class[_], Boolean]
148 |
149 | def c(clazz: Class[_]): Boolean =
150 | cache.getOrElseUpdate(clazz, l(clazz))
151 |
152 | def l(clazz: Class[_]): Boolean = {
153 | if (clazz.getAnnotation(classOf[EnableReflectiveInstantiation]) != null) {
154 | true
155 | } else {
156 | (Iterator(clazz.getSuperclass) ++ clazz.getInterfaces.iterator)
157 | .filter(_ != null)
158 | .exists(c)
159 | }
160 | }
161 |
162 | c(clazz)
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/jvm/src/main/scala/org/portablescala/reflect/internal/Macros.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect.internal
2 |
3 | import org.portablescala.reflect._
4 |
5 | /* Macro definitions are enclosed in a dedicated `Macros` object,
6 | * so that their metadata (the types involved etc.) don't pollute `Reflect`'s metadata.
7 | * This enables using `Reflect`'s methods without `scala-reflect` JAR
8 | * https://github.com/scala/bug/issues/8090
9 | * https://github.com/xeno-by/sbt-example-paradise210/issues/1#issuecomment-20996354
10 | */
11 | object Macros {
12 | /** Magic to get cross-compiling access to `blackbox.Context` with a fallback
13 | * on `macros.Context`, without deprecation warning in any Scala version.
14 | */
15 | private object MacroCompat {
16 | object Scope1 {
17 | object blackbox
18 | }
19 | import Scope1._
20 |
21 | object Scope2 {
22 | import scala.reflect.macros._
23 | object Inner {
24 | import blackbox._
25 | type BlackboxContext = Context
26 | }
27 | }
28 | }
29 |
30 | import MacroCompat.Scope2.Inner.BlackboxContext
31 |
32 | private def currentClassLoaderExpr(
33 | c: BlackboxContext { type PrefixType = Reflect.type }): c.Expr[ClassLoader] = {
34 | import c.universe._
35 | val enclosingClassTree = c.reifyEnclosingRuntimeClass
36 | if (enclosingClassTree.isEmpty)
37 | c.abort(c.enclosingPosition, "call site does not have an enclosing class")
38 | val enclosingClassExpr = c.Expr[java.lang.Class[_]](enclosingClassTree)
39 | reify {
40 | enclosingClassExpr.splice.getClassLoader()
41 | }
42 | }
43 |
44 | def lookupLoadableModuleClass(
45 | c: BlackboxContext { type PrefixType = Reflect.type })(
46 | fqcn: c.Expr[String]): c.Expr[Option[LoadableModuleClass]] = {
47 | import c.universe._
48 | val loaderExpr = currentClassLoaderExpr(c)
49 | reify {
50 | c.prefix.splice.lookupLoadableModuleClass(fqcn.splice, loaderExpr.splice)
51 | }
52 | }
53 |
54 | def lookupInstantiatableClass(
55 | c: BlackboxContext { type PrefixType = Reflect.type })(
56 | fqcn: c.Expr[String]): c.Expr[Option[InstantiatableClass]] = {
57 | import c.universe._
58 | val loaderExpr = currentClassLoaderExpr(c)
59 | reify {
60 | c.prefix.splice.lookupInstantiatableClass(fqcn.splice, loaderExpr.splice)
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/native/src/main/scala/org/portablescala/reflect/Reflect.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | import scala.scalanative.reflect.{Reflect => ScalaNativeReflect}
4 |
5 | object Reflect {
6 | /** Reflectively looks up a loadable module class.
7 | *
8 | * A module class is the technical term referring to the class of a Scala
9 | * `object`. The object or one of its super types (classes or traits) must
10 | * be annotated with
11 | * [[org.portablescala.reflect.annotation.EnableReflectiveInstantiation @EnableReflectiveInstantiation]].
12 | * Moreover, the object must be "static", i.e., declared at the top-level of
13 | * a package or inside a static object.
14 | *
15 | * If the module class cannot be found, either because it does not exist,
16 | * was not `@EnableReflectiveInstantiation` or was not static, this method
17 | * returns `None`.
18 | *
19 | * @param fqcn
20 | * Fully-qualified name of the module class, including its trailing `$`
21 | */
22 | def lookupLoadableModuleClass(fqcn: String): Option[LoadableModuleClass] =
23 | ScalaNativeReflect.lookupLoadableModuleClass(fqcn)
24 |
25 | /** Reflectively looks up a loadable module class.
26 | *
27 | * In Scala Native, this method ignores the parameter `loader`. Calling this
28 | * method is equivalent to
29 | * {{{
30 | * Reflect.lookupLoadableModuleClass(fqcn)
31 | * }}}
32 | *
33 | * @param fqcn
34 | * Fully-qualified name of the module class, including its trailing `$`
35 | *
36 | * @param loader
37 | * Ignored
38 | */
39 | def lookupLoadableModuleClass(fqcn: String,
40 | loader: ClassLoader): Option[LoadableModuleClass] = {
41 | lookupLoadableModuleClass(fqcn)
42 | }
43 |
44 | /** Reflectively looks up an instantiable class.
45 | *
46 | * The class or one of its super types (classes or traits) must be annotated
47 | * with
48 | * [[org.portablescala.reflect.annotation.EnableReflectiveInstantiation @EnableReflectiveInstantiation]].
49 | * Moreover, the class must not be abstract, nor be a local class (i.e., a
50 | * class defined inside a `def`). Inner classes (defined inside another
51 | * class) are supported.
52 | *
53 | * If the class cannot be found, either because it does not exist,
54 | * was not `@EnableReflectiveInstantiation` or was abstract or local, this
55 | * method returns `None`.
56 | *
57 | * @param fqcn
58 | * Fully-qualified name of the class
59 | */
60 | def lookupInstantiatableClass(fqcn: String): Option[InstantiatableClass] =
61 | ScalaNativeReflect.lookupInstantiatableClass(fqcn)
62 |
63 | /** Reflectively looks up an instantiable class.
64 | *
65 | * In Scala Native, this method ignores the parameter `loader`. Calling this
66 | * method is equivalent to
67 | * {{{
68 | * Reflect.lookupInstantiatableClass(fqcn)
69 | * }}}
70 | *
71 | * @param fqcn
72 | * Fully-qualified name of the class
73 | *
74 | * @param loader
75 | * Ignored
76 | */
77 | def lookupInstantiatableClass(fqcn: String,
78 | loader: ClassLoader): Option[InstantiatableClass] = {
79 | lookupInstantiatableClass(fqcn)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/native/src/main/scala/org/portablescala/reflect/annotation/package.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | package object annotation {
4 | type EnableReflectiveInstantiation =
5 | scala.scalanative.reflect.annotation.EnableReflectiveInstantiation
6 | }
7 |
--------------------------------------------------------------------------------
/native/src/main/scala/org/portablescala/reflect/package.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala
2 |
3 | package object reflect {
4 | type InstantiatableClass = scala.scalanative.reflect.InstantiatableClass
5 |
6 | type InvokableConstructor = scala.scalanative.reflect.InvokableConstructor
7 |
8 | type LoadableModuleClass = scala.scalanative.reflect.LoadableModuleClass
9 | }
10 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.9.9
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | val scalaJSVersion =
2 | Option(System.getenv("SCALAJS_VERSION")).filter(_ != "").getOrElse("1.0.0")
3 |
4 | val scalaNativeVersion =
5 | Option(System.getenv("SCALANATIVE_VERSION")).filter(_ != "").getOrElse("0.4.4")
6 |
7 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion)
8 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
9 |
10 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % scalaNativeVersion)
11 | addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2")
12 |
13 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1")
14 |
--------------------------------------------------------------------------------
/shared/src/test/scala/org/portablescala/reflect/ReflectTest.scala:
--------------------------------------------------------------------------------
1 | package org.portablescala.reflect
2 |
3 | import scala.reflect.ClassTag
4 |
5 | import org.junit.Assert._
6 | import org.junit.Assume._
7 | import org.junit.Test
8 |
9 | import org.portablescala.reflect.annotation.EnableReflectiveInstantiation
10 |
11 | package subpackage {
12 | @EnableReflectiveInstantiation
13 | private class PrivateClassEnableDirect {
14 | override def toString(): String = "instance of PrivateClassEnableDirect"
15 | }
16 | }
17 |
18 | class ReflectTest {
19 | import ReflectTest.{Accessors, VC, ConstructorThrowsMessage, intercept}
20 |
21 | private final val Prefix = "org.portablescala.reflect.ReflectTest$"
22 |
23 | private final val NameClassEnableDirect =
24 | Prefix + "ClassEnableDirect"
25 | private final val NameClassEnableDirectNoZeroArgCtor =
26 | Prefix + "ClassEnableDirectNoZeroArgCtor"
27 | private final val NameObjectEnableDirect =
28 | Prefix + "ObjectEnableDirect$"
29 | private final val NameTraitEnableDirect =
30 | Prefix + "TraitEnableDirect"
31 | private final val NameAbstractClassEnableDirect =
32 | Prefix + "AbstractClassEnableDirect"
33 | private final val NameClassNoPublicConstructorEnableDirect =
34 | Prefix + "ClassNoPublicConstructorEnableDirect"
35 |
36 | private final val NameInnerClass = {
37 | Prefix + "ClassWithInnerClassWithEnableReflectiveInstantiation$" +
38 | "InnerClassWithEnableReflectiveInstantiation"
39 | }
40 |
41 | private final val NameClassEnableIndirect =
42 | Prefix + "ClassEnableIndirect"
43 | private final val NameClassEnableIndirectNoZeroArgCtor =
44 | Prefix + "ClassEnableIndirectNoZeroArgCtor"
45 | private final val NameObjectEnableIndirect =
46 | Prefix + "ObjectEnableIndirect$"
47 | private final val NameTraitEnableIndirect =
48 | Prefix + "TraitEnableIndirect"
49 | private final val NameAbstractClassEnableIndirect =
50 | Prefix + "AbstractClassEnableIndirect"
51 | private final val NameClassNoPublicConstructorEnableIndirect =
52 | Prefix + "ClassNoPublicConstructorEnableIndirect"
53 |
54 | private final val NameClassDisable =
55 | Prefix + "ClassDisable"
56 | private final val NameObjectDisable =
57 | Prefix + "ObjectDisable$"
58 | private final val NameTraitDisable =
59 | Prefix + "TraitDisable"
60 |
61 | private final val NameInnerObject = {
62 | Prefix + "ClassWithInnerObjectWithEnableReflectiveInstantiation$" +
63 | "InnerObjectWithEnableReflectiveInstantiation"
64 | }
65 |
66 | private final val NameObjectWithInitialization =
67 | Prefix + "ObjectWithInitialization$"
68 | private final val NameObjectWithThrowingCtor =
69 | Prefix + "ObjectWithThrowingCtor$"
70 | private final val NameClassWithThrowingCtor =
71 | Prefix + "ClassWithThrowingCtor"
72 |
73 | private final val NamePrivateClassEnableDirect =
74 | "org.portablescala.reflect.subpackage.PrivateClassEnableDirect"
75 |
76 | @Test def testClassRuntimeClass(): Unit = {
77 | def test(name: String): Unit = {
78 | val optClassData = Reflect.lookupInstantiatableClass(name)
79 | assertTrue(name, optClassData.isDefined)
80 | val classData = optClassData.get
81 |
82 | val runtimeClass = optClassData.get.runtimeClass
83 | assertEquals(name, name, runtimeClass.getName)
84 | }
85 |
86 | test(NameClassEnableDirect)
87 | test(NameClassEnableDirectNoZeroArgCtor)
88 | test(NameClassEnableIndirect)
89 | test(NameClassEnableIndirectNoZeroArgCtor)
90 | }
91 |
92 | @Test def testObjectRuntimeClass(): Unit = {
93 | def test(name: String): Unit = {
94 | val optClassData = Reflect.lookupLoadableModuleClass(name)
95 | assertTrue(name, optClassData.isDefined)
96 | val classData = optClassData.get
97 |
98 | val runtimeClass = optClassData.get.runtimeClass
99 | assertEquals(name, name, runtimeClass.getName)
100 | }
101 |
102 | test(NameObjectEnableDirect)
103 | test(NameObjectEnableIndirect)
104 | }
105 |
106 | @Test def testClassCannotBeFound(): Unit = {
107 | def test(name: String): Unit =
108 | assertTrue(name, Reflect.lookupInstantiatableClass(name).isEmpty)
109 |
110 | test(NameObjectEnableDirect)
111 | test(NameTraitEnableDirect)
112 | test(NameAbstractClassEnableDirect)
113 | test(NameClassNoPublicConstructorEnableDirect)
114 | test(NameObjectEnableIndirect)
115 | test(NameTraitEnableIndirect)
116 | test(NameAbstractClassEnableIndirect)
117 | test(NameClassNoPublicConstructorEnableIndirect)
118 | test(NameClassDisable)
119 | test(NameObjectDisable)
120 | test(NameTraitDisable)
121 | }
122 |
123 | @Test def testObjectCannotBeFound(): Unit = {
124 | def test(name: String): Unit =
125 | assertTrue(name, Reflect.lookupLoadableModuleClass(name).isEmpty)
126 |
127 | test(NameClassEnableDirect)
128 | test(NameClassEnableDirectNoZeroArgCtor)
129 | test(NameTraitEnableDirect)
130 | test(NameAbstractClassEnableDirect)
131 | test(NameClassNoPublicConstructorEnableDirect)
132 | test(NameClassEnableIndirect)
133 | test(NameTraitEnableIndirect)
134 | test(NameAbstractClassEnableIndirect)
135 | test(NameClassNoPublicConstructorEnableIndirect)
136 | test(NameClassDisable)
137 | test(NameObjectDisable)
138 | test(NameTraitDisable)
139 | }
140 |
141 | @Test def testClassNoArgCtor(): Unit = {
142 | for (name <- Seq(NameClassEnableDirect, NameClassEnableIndirect)) {
143 | val optClassData = Reflect.lookupInstantiatableClass(name)
144 | assertTrue(name, optClassData.isDefined)
145 | val classData = optClassData.get
146 |
147 | val instance = classData.newInstance().asInstanceOf[Accessors]
148 | assertEquals(name, -1, instance.x)
149 | assertEquals(name, name.stripPrefix(Prefix), instance.y)
150 | }
151 | }
152 |
153 | @Test def testClassNoArgCtorErrorCase(): Unit = {
154 | for (name <- Seq(NameClassEnableDirectNoZeroArgCtor,
155 | NameClassEnableIndirectNoZeroArgCtor)) {
156 | val optClassData = Reflect.lookupInstantiatableClass(name)
157 | assertTrue(name, optClassData.isDefined)
158 | val classData = optClassData.get
159 |
160 | intercept[InstantiationException](classData.newInstance())
161 | }
162 | }
163 |
164 | @Test def testClassCtorWithArgs(): Unit = {
165 | for (name <- Seq(NameClassEnableDirect, NameClassEnableDirectNoZeroArgCtor,
166 | NameClassEnableIndirect, NameClassEnableIndirectNoZeroArgCtor)) {
167 | val optClassData = Reflect.lookupInstantiatableClass(name)
168 | assertTrue(optClassData.isDefined)
169 | val classData = optClassData.get
170 |
171 | val optCtorIntString =
172 | classData.getConstructor(classOf[Int], classOf[String])
173 | assertTrue(optCtorIntString.isDefined)
174 | val instanceIntString =
175 | optCtorIntString.get.newInstance(543, "foobar").asInstanceOf[Accessors]
176 | assertEquals(543, instanceIntString.x)
177 | assertEquals("foobar", instanceIntString.y)
178 |
179 | val optCtorInt = classData.getConstructor(classOf[Int])
180 | assertTrue(optCtorInt.isDefined)
181 | val instanceInt =
182 | optCtorInt.get.newInstance(123).asInstanceOf[Accessors]
183 | assertEquals(123, instanceInt.x)
184 | assertEquals(name.stripPrefix(Prefix), instanceInt.y)
185 |
186 | // Value class is seen as its underlying
187 | val optCtorShort = classData.getConstructor(classOf[Short])
188 | assertTrue(optCtorShort.isDefined)
189 | val instanceShort =
190 | optCtorShort.get.newInstance(21.toShort).asInstanceOf[Accessors]
191 | assertEquals(42, instanceShort.x)
192 | assertEquals(name.stripPrefix(Prefix), instanceShort.y)
193 |
194 | // Non-existent
195 | assertFalse(classData.getConstructor(classOf[Boolean]).isDefined)
196 | assertFalse(classData.getConstructor(classOf[VC]).isDefined)
197 |
198 | // Non-public
199 | assertFalse(classData.getConstructor(classOf[String]).isDefined)
200 | assertFalse(classData.getConstructor(classOf[Double]).isDefined)
201 | }
202 | }
203 |
204 | @Test def testInnerClass(): Unit = {
205 | val outer = new ReflectTest.ClassWithInnerClassWithEnableReflectiveInstantiation(15)
206 |
207 | val optClassData = Reflect.lookupInstantiatableClass(NameInnerClass)
208 | assertTrue(optClassData.isDefined)
209 | val classData = optClassData.get
210 |
211 | val optCtorOuterString =
212 | classData.getConstructor(outer.getClass, classOf[String])
213 | assertTrue(optCtorOuterString.isDefined)
214 | val instanceOuterString =
215 | optCtorOuterString.get.newInstance(outer, "babar").asInstanceOf[Accessors]
216 | assertEquals(15, instanceOuterString.x)
217 | assertEquals("babar", instanceOuterString.y)
218 | }
219 |
220 | private val classInsideLambdaInsideCtor: () => Class[_] = { () =>
221 | @EnableReflectiveInstantiation
222 | class LocalClassWithEnableReflectiveInstantiationInsideLambdaInsideCtor
223 |
224 | classOf[LocalClassWithEnableReflectiveInstantiationInsideLambdaInsideCtor]
225 | }
226 |
227 | @Test def testLocalClass(): Unit = {
228 | def assertCannotFind(c: Class[_]): Unit = {
229 | val fqcn = c.getName
230 | assertFalse(fqcn, Reflect.lookupInstantiatableClass(fqcn).isDefined)
231 | }
232 |
233 | // Inside a method
234 | @EnableReflectiveInstantiation
235 | class LocalClassWithEnableReflectiveInstantiationInsideMethod
236 |
237 | assertCannotFind(
238 | classOf[LocalClassWithEnableReflectiveInstantiationInsideMethod])
239 |
240 | // In a lambda whose owner is ultimately the constructor of the class
241 | assertCannotFind(classInsideLambdaInsideCtor())
242 |
243 | // Inside lambda whose owner is a method
244 | val f = { () =>
245 | @EnableReflectiveInstantiation
246 | class LocalClassWithEnableReflectiveInstantiationInsideLambdaInsideMethod
247 |
248 | assertCannotFind(
249 | classOf[LocalClassWithEnableReflectiveInstantiationInsideLambdaInsideMethod])
250 | }
251 | f()
252 | }
253 |
254 | @Test def testObjectLoad(): Unit = {
255 | for (name <- Seq(NameObjectEnableDirect, NameObjectEnableIndirect)) {
256 | val optClassData = Reflect.lookupLoadableModuleClass(name)
257 | assertTrue(name, optClassData.isDefined)
258 | val classData = optClassData.get
259 |
260 | val instance = classData.loadModule().asInstanceOf[Accessors]
261 | assertEquals(name, 101, instance.x)
262 | assertEquals(name, name.stripPrefix(Prefix), instance.y)
263 | }
264 | }
265 |
266 | @Test def testObjectLoadInitialization(): Unit = {
267 | assertFalse(ReflectTest.ObjectWithInitializationHasBeenInitialized)
268 | val optClassData =
269 | Reflect.lookupLoadableModuleClass(NameObjectWithInitialization)
270 | assertTrue(optClassData.isDefined)
271 | val classData = optClassData.get
272 | assertFalse(ReflectTest.ObjectWithInitializationHasBeenInitialized)
273 |
274 | classData.loadModule()
275 | assertTrue(ReflectTest.ObjectWithInitializationHasBeenInitialized)
276 | }
277 |
278 | @Test def testExceptionsInConstructor(): Unit = {
279 | val objClassData =
280 | Reflect.lookupLoadableModuleClass(NameObjectWithThrowingCtor).get
281 | val e1 = intercept[ArithmeticException] {
282 | objClassData.loadModule()
283 | }
284 | assertEquals(ConstructorThrowsMessage, e1.getMessage)
285 |
286 | val clsClassData =
287 | Reflect.lookupInstantiatableClass(NameClassWithThrowingCtor).get
288 | val e2 = intercept[ArithmeticException] {
289 | clsClassData.newInstance()
290 | }
291 | assertEquals(ConstructorThrowsMessage, e2.getMessage)
292 |
293 | val ctor = clsClassData.getConstructor().get
294 | val e3 = intercept[ArithmeticException] {
295 | ctor.newInstance()
296 | }
297 | assertEquals(ConstructorThrowsMessage, e3.getMessage)
298 | }
299 |
300 | @Test def testPrivateClass(): Unit = {
301 | // Private classes are discoverable
302 |
303 | val optClassData = Reflect.lookupInstantiatableClass(NamePrivateClassEnableDirect)
304 | assertTrue("1", optClassData.isDefined)
305 | val classData = optClassData.get
306 |
307 | val obj = classData.newInstance()
308 | assertEquals("2", "instance of PrivateClassEnableDirect", obj.toString())
309 | }
310 |
311 | @Test def testInnerObjectWithEnableReflectiveInstantiation_issue_3228(): Unit = {
312 | assertFalse(Reflect.lookupLoadableModuleClass(NameInnerObject).isDefined)
313 | assertFalse(Reflect.lookupInstantiatableClass(NameInnerObject).isDefined)
314 | }
315 |
316 | @Test def testLocalClassWithReflectiveInstantiationInLambda_issue_3227(): Unit = {
317 | // Test that the presence of the following code does not prevent linking
318 | val f = { () =>
319 | @EnableReflectiveInstantiation
320 | class Foo
321 | }
322 | identity(f) // discard f without compiler warning
323 | }
324 | }
325 |
326 | object ReflectTest {
327 | private final val ConstructorThrowsMessage = "constructor throws"
328 |
329 | def intercept[T <: Throwable : ClassTag](body: => Unit): T = {
330 | try {
331 | body
332 | throw new AssertionError("no exception was thrown")
333 | } catch {
334 | case t: T => t
335 | }
336 | }
337 |
338 | trait Accessors {
339 | val x: Int
340 | val y: String
341 | }
342 |
343 | final class VC(val self: Short) extends AnyVal
344 |
345 | /* FIXME In the classes below, protected constructors are commented out,
346 | * because Scala.js and Scala/JVM do not agree on whether they should be
347 | * visible. Scala.js says no, but Scala/JVM compiles them as public, and
348 | * therefore says yes.
349 | */
350 |
351 | // Entities with directly enabled reflection
352 |
353 | @EnableReflectiveInstantiation
354 | class ClassEnableDirect(val x: Int, val y: String) extends Accessors {
355 | def this(x: Int) = this(x, "ClassEnableDirect")
356 | def this() = this(-1)
357 | def this(vc: VC) = this(vc.self.toInt * 2)
358 |
359 | //protected def this(y: String) = this(-5, y)
360 | private def this(d: Double) = this(d.toInt)
361 | }
362 |
363 | @EnableReflectiveInstantiation
364 | class ClassEnableDirectNoZeroArgCtor(val x: Int, val y: String)
365 | extends Accessors {
366 | def this(x: Int) = this(x, "ClassEnableDirectNoZeroArgCtor")
367 | def this(vc: VC) = this(vc.self.toInt * 2)
368 |
369 | //protected def this(y: String) = this(-5, y)
370 | private def this(d: Double) = this(d.toInt)
371 | }
372 |
373 | @EnableReflectiveInstantiation
374 | object ObjectEnableDirect extends Accessors {
375 | val x = 101
376 | val y = "ObjectEnableDirect$"
377 | }
378 |
379 | @EnableReflectiveInstantiation
380 | trait TraitEnableDirect extends Accessors
381 |
382 | @EnableReflectiveInstantiation
383 | abstract class AbstractClassEnableDirect(val x: Int, val y: String)
384 | extends Accessors {
385 |
386 | def this(x: Int) = this(x, "AbstractClassEnableDirect")
387 | def this() = this(-1)
388 | def this(vc: VC) = this(vc.self.toInt * 2)
389 |
390 | //protected def this(y: String) = this(-5, y)
391 | private def this(d: Double) = this(d.toInt)
392 | }
393 |
394 | @EnableReflectiveInstantiation
395 | class ClassNoPublicConstructorEnableDirect private (val x: Int, val y: String)
396 | extends Accessors {
397 |
398 | //protected def this(y: String) = this(-5, y)
399 | }
400 |
401 | class ClassWithInnerClassWithEnableReflectiveInstantiation(_x: Int) {
402 | @EnableReflectiveInstantiation
403 | class InnerClassWithEnableReflectiveInstantiation(_y: String)
404 | extends Accessors {
405 | val x = _x
406 | val y = _y
407 | }
408 | }
409 |
410 | // Entities with reflection enabled by inheritance
411 |
412 | @EnableReflectiveInstantiation
413 | trait EnablingTrait
414 |
415 | class ClassEnableIndirect(val x: Int, val y: String)
416 | extends EnablingTrait with Accessors {
417 |
418 | def this(x: Int) = this(x, "ClassEnableIndirect")
419 | def this() = this(-1)
420 | def this(vc: VC) = this(vc.self.toInt * 2)
421 |
422 | //protected def this(y: String) = this(-5, y)
423 | private def this(d: Double) = this(d.toInt)
424 | }
425 |
426 | class ClassEnableIndirectNoZeroArgCtor(val x: Int, val y: String)
427 | extends EnablingTrait with Accessors {
428 | def this(x: Int) = this(x, "ClassEnableIndirectNoZeroArgCtor")
429 | def this(vc: VC) = this(vc.self.toInt * 2)
430 |
431 | //protected def this(y: String) = this(-5, y)
432 | private def this(d: Double) = this(d.toInt)
433 | }
434 |
435 | object ObjectEnableIndirect extends EnablingTrait with Accessors {
436 | val x = 101
437 | val y = "ObjectEnableIndirect$"
438 | }
439 |
440 | trait TraitEnableIndirect extends EnablingTrait with Accessors
441 |
442 | abstract class AbstractClassEnableIndirect(val x: Int, val y: String)
443 | extends EnablingTrait with Accessors {
444 |
445 | def this(x: Int) = this(x, "AbstractClassEnableIndirect")
446 | def this() = this(-1)
447 | def this(vc: VC) = this(vc.self.toInt * 2)
448 |
449 | //protected def this(y: String) = this(-5, y)
450 | private def this(d: Double) = this(d.toInt)
451 | }
452 |
453 | class ClassNoPublicConstructorEnableIndirect private (
454 | val x: Int, val y: String)
455 | extends EnablingTrait with Accessors {
456 |
457 | //protected def this(y: String) = this(-5, y)
458 | }
459 |
460 | // Entities with reflection disabled
461 |
462 | class ClassDisable(val x: Int, val y: String) extends Accessors
463 |
464 | object ObjectDisable extends Accessors {
465 | val x = 101
466 | val y = "ObjectDisable$"
467 | }
468 |
469 | trait TraitDisable extends Accessors
470 |
471 | // Corner cases
472 |
473 | var ObjectWithInitializationHasBeenInitialized: Boolean = false
474 |
475 | @EnableReflectiveInstantiation
476 | object ObjectWithInitialization {
477 | ObjectWithInitializationHasBeenInitialized = true
478 | }
479 |
480 | @EnableReflectiveInstantiation
481 | object ObjectWithThrowingCtor {
482 | throw new ArithmeticException(ConstructorThrowsMessage)
483 | }
484 |
485 | @EnableReflectiveInstantiation
486 | class ClassWithThrowingCtor {
487 | throw new ArithmeticException(ConstructorThrowsMessage)
488 | }
489 |
490 | // Regression cases
491 |
492 | class ClassWithInnerObjectWithEnableReflectiveInstantiation {
493 | @EnableReflectiveInstantiation
494 | object InnerObjectWithEnableReflectiveInstantiation
495 | }
496 | }
497 |
--------------------------------------------------------------------------------