Skip to content

Add support for JDK 17 #557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
# Definition of the build matrix
strategy:
matrix:
java: [8, 11, 14]
java: [8, 11, 17]

# All build steps
# SINGLE-MATRIX-JOB means that the step does not need to be executed on every job in the matrix
Expand Down
1 change: 0 additions & 1 deletion .sbtopts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
-J-Xmx8G
-J-XX:MaxMetaspaceSize=1G
-J-XX:+CMSClassUnloadingEnabled
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
java -Xms512M -Xmx2G -Xss2M -XX:ReservedCodeCacheSize=192m -XX:+CMSClassUnloadingEnabled -Dfile.encoding=UTF-8 -jar sbt/sbt-launch.jar -Dsbt.parser.simple=true clean +test 'set every publishTo := Some(Resolver.file("local-repo", file("target/dist")))' '+ publish'
java -Xms512M -Xmx2G -Xss2M -XX:ReservedCodeCacheSize=192m -Dfile.encoding=UTF-8 -jar sbt/sbt-launch.jar -Dsbt.parser.simple=true clean +test 'set every publishTo := Some(Resolver.file("local-repo", file("target/dist")))' '+ publish'
136 changes: 84 additions & 52 deletions common/src/main/scala/org/mockito/ReflectionUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ object ReflectionUtils {
import scala.reflect.runtime.{ universe => ru }
import ru._

val JavaVersion: Int =
System.getProperty("java.version").split("\\.") match {
case Array("1", v, _*) => v.toInt // Java 8 style: 1.8.x
case Array(v, _*) => v.toInt // Java 9+ style: 11.x, 17.x, etc.
}

implicit def symbolToMethodSymbol(sym: Symbol): Symbols#MethodSymbol = sym.asInstanceOf[Symbols#MethodSymbol]

private val mirror = runtimeMirror(getClass.getClassLoader)
Expand Down Expand Up @@ -51,29 +57,23 @@ object ReflectionUtils {
def returnsValueClass: Boolean = findTypeSymbol.exists(_.returnType.typeSymbol.isDerivedValueClass)

private def resolveWithScalaGenerics: Option[Class[_]] =
scala.util
.Try {
findTypeSymbol
.filter(_.returnType.typeSymbol.isClass)
.map(_.asMethod.returnType.typeSymbol.asClass)
.map(mirror.runtimeClass)
}
.toOption
.flatten
uTry {
findTypeSymbol
.filter(_.returnType.typeSymbol.isClass)
.map(_.asMethod.returnType.typeSymbol.asClass)
.map(mirror.runtimeClass)
}.toOption.flatten

private def findTypeSymbol =
scala.util
.Try {
mirror
.classSymbol(method.getDeclaringClass)
.info
.decls
.collectFirst {
case symbol if isNonConstructorMethod(symbol) && customMirror.methodToJava(symbol) === method => symbol
}
}
.toOption
.flatten
uTry {
mirror
.classSymbol(method.getDeclaringClass)
.info
.decls
.collectFirst {
case symbol if isNonConstructorMethod(symbol) && customMirror.methodToJava(symbol) === method => symbol
}
}.toOption.flatten

private def resolveWithJavaGenerics: Option[Class[_]] =
try Some(GenericsResolver.resolve(invocation.getMock.getClass).`type`(method.getDeclaringClass).method(method).resolveReturnClass())
Expand All @@ -85,45 +85,47 @@ object ReflectionUtils {
private def isNonConstructorMethod(d: ru.Symbol): Boolean = d.isMethod && !d.isConstructor

def extraInterfaces[T](implicit $wtt: WeakTypeTag[T], $ct: ClassTag[T]): List[Class[_]] =
scala.util
.Try {
val cls = clazz($ct)
$wtt.tpe match {
case RefinedType(types, _) =>
types.map($wtt.mirror.runtimeClass).collect {
case c: Class[_] if c.isInterface && c != cls => c
}
case _ => List.empty
}
uTry {
val cls = clazz($ct)
$wtt.tpe match {
case RefinedType(types, _) =>
types.map($wtt.mirror.runtimeClass).collect {
case c: Class[_] if c.isInterface && c != cls => c
}
case _ => List.empty
}
.toOption
}.toOption
.getOrElse(List.empty)

def methodsWithLazyOrVarArgs(classes: Seq[Class[_]]): Seq[(Method, Set[Int])] =
classes.flatMap { clazz =>
scala.util
.Try {
mirror
.classSymbol(clazz)
.info
.members
.collect {
case symbol if isNonConstructorMethod(symbol) =>
symbol -> symbol.typeSignature.paramLists.flatten.zipWithIndex.collect {
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
case (p, idx) if p.typeSignature.toString.endsWith("*") => idx
}.toSet
}
.collect {
case (symbol, indices) if indices.nonEmpty => customMirror.methodToJava(symbol) -> indices
}
.toSeq
}
.toOption
uTry {
mirror
.classSymbol(clazz)
.info
.members
.collect {
case symbol if isNonConstructorMethod(symbol) =>
symbol -> symbol.typeSignature.paramLists.flatten.zipWithIndex.collect {
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
case (p, idx) if p.typeSignature.toString.endsWith("*") => idx
}.toSet
}
.collect {
case (symbol, indices) if indices.nonEmpty => customMirror.methodToJava(symbol) -> indices
}
.toSeq
}.toOption
.getOrElse(Seq.empty)
}

def setFinalStatic(field: Field, newValue: Any): Unit = {
def setFinalStatic(field: Field, newValue: AnyRef): Unit =
if (JavaVersion < 17)
setFinalStatic17Minus(field, newValue)
else
setFinalStatic17Plus(field, newValue)

private def setFinalStatic17Minus(field: Field, newValue: AnyRef): Unit = {
val clazz = classOf[java.lang.Class[_]]
field.setAccessible(true)
val modifiersField: Field = uTry(clazz.getDeclaredField("modifiers")) match {
Expand All @@ -150,4 +152,34 @@ object ReflectionUtils {
field.set(null, newValue)
}

private def setFinalStatic17Plus(field: Field, newValue: AnyRef): Unit =
try {
// Try to get Unsafe instance (works with both sun.misc.Unsafe and jdk.internal.misc.Unsafe)
val unsafeClass: Class[_] =
try
Class.forName("sun.misc.Unsafe")
catch {
case _: ClassNotFoundException => Class.forName("jdk.internal.misc.Unsafe")
}

val unsafeField = unsafeClass.getDeclaredField("theUnsafe")
unsafeField.setAccessible(true)
val unsafe = unsafeField.get(null)

// Get methods via reflection to handle both Unsafe implementations
val staticFieldBaseMethod = unsafeClass.getMethod("staticFieldBase", classOf[Field])
val staticFieldOffsetMethod = unsafeClass.getMethod("staticFieldOffset", classOf[Field])
val putObjectMethod = unsafeClass.getMethod("putObject", classOf[Object], classOf[Long], classOf[Object])

// Get base and offset for the field
val base: Object = staticFieldBaseMethod.invoke(unsafe, field)
val offset: Long = staticFieldOffsetMethod.invoke(unsafe, field).asInstanceOf[Long]

// Set the field value directly
putObjectMethod.invoke(unsafe, base, java.lang.Long.valueOf(offset), newValue)
} catch {
case NonFatal(e) =>
throw new IllegalStateException(s"Cannot modify final field ${field.getName}", e)
}

}
Loading