├── .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 | [![Build Status](https://travis-ci.org/portable-scala/portable-scala-reflect.svg?branch=master)](https://travis-ci.org/portable-scala/portable-scala-reflect) 4 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-0.6.29.svg)](https://www.scala-js.org/) 5 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.0.0.svg)](https://www.scala-js.org) 6 | [![Scaladoc](https://javadoc-badge.appspot.com/org.portable-scala/portable-scala-reflect_2.12.svg?label=scaladoc)](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 | --------------------------------------------------------------------------------