package org.mockitousage.jls; import net.bytebuddy.ClassFileVersion; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import static net.bytebuddy.ClassFileVersion.JAVA_V6; import static net.bytebuddy.ClassFileVersion.JAVA_V7; import static net.bytebuddy.ClassFileVersion.JAVA_V8; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Illustrate differences in the JLS depending on the Java version. */ @RunWith(Enclosed.class) public class JLS_15_12_2_5Test { /** * The JLS §15.12.2.5 states that the compiler must chose the most specific overload in Java 6 or Java 7, * but with generics in the matcher, <strong>javac</strong> selects the upper bound, which is {@code Object}, * as such javac selects the most generic method. * * https://docs.oracle.com/javase/specs/jls/se6/html/expressions.html#15.12.2.5 * https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.5 * * <blockquote> * <p>If more than one member method is both accessible and applicable to a method invocation, it is necessary to * choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses * the rule that the most specific method is chosen.</p> * * <p>The informal intuition is that one method is more specific than another if any invocation handled by * the first method could be passed on to the other one without a compile-time type error.</p> * </blockquote> */ public static class JLS_15_12_2_5_Java6_Java7_Test { @Before public void setUp() throws Exception { Assume.assumeTrue(ClassFileVersion.of(JLS_15_12_2_5_Java6_Java7_Test.class).equals(JAVA_V6) || ClassFileVersion.of(JLS_15_12_2_5_Java6_Java7_Test.class).equals(JAVA_V7)); } @Test public void with_single_arg() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.oneArg(isNull())).thenReturn("ok"); assertThat(mock.oneArg(null)).describedAs("Most generic method chosen for matcher " + "(isNull generic upper bound is Object), but null applies " + "to select most specific method") .isEqualTo(null); } @Test public void with_single_arg_and_matcher_cast() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.oneArg((String) isNull())).thenReturn("ok"); assertThat(mock.oneArg(null)).describedAs("Most specific method enforced for matcher via cast").isEqualTo("ok"); } @Test public void with_single_arg_and_null_Object_reference() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.oneArg(isNull())).thenReturn("ok"); Object arg = null; assertThat(mock.oneArg(arg)).describedAs("Most generic method chosen for matcher").isEqualTo("ok"); } @Test public void with_variable_arg() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.varargs(isNull())).thenReturn("ok"); assertThat(mock.varargs(null)).describedAs("Most generic method chosen for matcher " + "(isNull generic upper bound is Object), but null applies " + "to select most specific method") .isEqualTo(null); } @Test public void with_variable_arg_and_matcher_String_cast() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.varargs((String) isNull())).thenReturn("ok"); assertThat(mock.varargs(null)).describedAs("Most specific method enforced for matcher via String cast").isEqualTo("ok"); } @Test public void with_variable_arg_and_matcher_String_array_cast() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.varargs((String[]) isNull())).thenReturn("ok"); assertThat(mock.varargs(null)).describedAs("Most specific method enforced for matcher via String[] cast").isEqualTo("ok"); } @Test public void with_variable_arg_and_null_Object_array() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.varargs(isNull())).thenReturn("ok"); Object[] args = null; assertThat(mock.varargs(args)).describedAs("isNull matcher generic upper bound is Object").isEqualTo("ok"); } @Test public void with_variable_arg_and_null_Object_arg() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.varargs(isNull())).thenReturn("ok"); Object arg = null; assertThat(mock.varargs(arg)).describedAs("isNull matcher generic upper bound is Object").isEqualTo("ok"); } } /** * The JLS §15.12.2.5 states that the compiler must chose the most specific overload in Java 8, however the * Java 8 compiler perform a type inference before selecting * * https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2 * <blockquote> * <p>Deciding whether a method is applicable will, in the case of generic methods (§8.4.4), require an analysis * of the type arguments. Type arguments may be passed explicitly or implicitly. If they are passed implicitly, * bounds of the type arguments must be inferred (§18 (Type Inference)) from the argument expressions.</p> * * <p>If several applicable methods have been identified during one of the three phases of applicability * testing, then the most specific one is chosen, as specified in section §15.12.2.5.</p> * </blockquote> * * https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1 * <blockquote> * <p>The definition of potential applicability goes beyond a basic arity check to also take into account * the presence and "shape" of functional interface target types. In some cases involving type argument * inference, a lambda expression appearing as a method invocation argument cannot be properly typed until * after overload resolution. These rules allow the form of the lambda expression to still be taken into * account, discarding obviously incorrect target types that might otherwise cause ambiguity errors.</p> * </blockquote> * * https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5 * <blockquote> * <p>One applicable method m1 is more specific than another applicable method m2, for an invocation with argument * expressions e1, ..., ek, if any of the following are true: * <ul> * * <li>m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek * by §18.5.4.</li> * <li>m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal * parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific * than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).</li> * <li>m2 is not generic, and m1 and m2 are applicable by variable arity invocation, and where the first k * variable arity parameter types of m1 are S1, ..., Sk and the first k variable arity parameter types of m2 * are T1, ..., Tk, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ k). * Additionally, if m2 has k+1 parameters, then the k+1'th variable arity parameter type of m1 is a subtype * of the k+1'th variable arity parameter type of m2.</li> * </ul></p> * * <p>The above conditions are the only circumstances under which one method may be more specific than another.</p> * * <p>A type S is more specific than a type T for any expression if S <: T (§4.10).</p> * </blockquote> */ public static class JLS_15_12_2_5_Java8_Test { @Before public void setUp() throws Exception { Assume.assumeTrue(ClassFileVersion.of(JLS_15_12_2_5_Java8_Test.class).isAtLeast(JAVA_V8)); } @Test public void with_single_arg() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.oneArg(isNull())).thenReturn("ok"); assertThat(mock.oneArg(null)).describedAs("Most specific method chosen for matcher and for null").isEqualTo("ok"); } @Test public void with_single_arg_and_null_Object_reference() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.oneArg(isNull())).thenReturn("ok"); Object arg = null; assertThat(mock.oneArg(arg)).describedAs("not the stubbed method").isEqualTo(null); } @Test public void with_variable_arg() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.varargs(isNull())).thenReturn("ok"); assertThat(mock.varargs(null)).describedAs("Most specific method chosen for matcher and for null").isEqualTo("ok"); } @Test public void with_variable_arg_and_null_Object_array() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.varargs(isNull())).thenReturn("ok"); Object[] args = null; assertThat(mock.varargs(args)).describedAs("Most specific method chosen for matcher").isEqualTo(null); } @Test public void with_variable_arg_and_null_Object_arg() throws Exception { SingleOverload mock = mock(SingleOverload.class); when(mock.varargs(isNull())).thenReturn("ok"); Object arg = null; assertThat(mock.varargs(arg)).describedAs("Most specific method chosen for matcher").isEqualTo(null); } } interface SingleOverload { String oneArg(Object arg); String oneArg(String arg); String varargs(Object... args); String varargs(String... args); } }