package none.cvg.variables; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import none.cvg.handles.HandlesKataDisplayNames; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import static none.cvg.handles.ErrorMessages.REFLECTION_FAILURE; import static none.cvg.handles.ErrorMessages.TEST_FAILURE; import static none.cvg.handles.ErrorMessages.UNSAFE_FAILURE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; /* * DONE: * This test aims at accessing public and private variables as well as a single and a * multidimensional array on an existing object. * Each solved test shows how this can be achieved with the traditional reflection calls. * Each unsolved test provides a few hints that will allow the kata-taker to manually solve * the exercise to achieve the same goal with MethodHandles/VarHandles. */ @DisplayName("Get field value") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DisplayNameGeneration(HandlesKataDisplayNames.class) public class TestSolutionGetter { public Integer publicVariable = 1; private Integer privateVariable = 2; private int[] privatePrimitiveArrayVariable = {1, 2, 3}; private int[][] privatePrimitive2DArrayVariable = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; /* -------------------------------------------------------------------------------------- */ /* * BEGIN: PUBLIC VARIABLE USING REFLECTION AND VARIABLE HANDLES */ @Test @Tag("PASSING") @Order(1) public void getPublicVariableFromConstructedClassViaReflection() { try { Class<?> clazz = TestSolutionGetter.class; Field publicVariableField = clazz.getDeclaredField("publicVariable"); assertEquals(1, publicVariableField.get(this), "The value of the field should be 1"); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { fail(REFLECTION_FAILURE.getValue() + e.getMessage()); } } @Test @Tag("PASSING") @Order(2) public void getPublicVariableFromConstructedClassViaVarHandles() { try { /* * DONE: * Replace the "null"s with valid values to get a VarHandle. * Check API: java.lang.invoke.MethodHandles.lookup() * Check API: java.lang.invoke.MethodHandles.Lookup.in(?) * HINT: param is Target class * Check API: java.lang.invoke.MethodHandles.Lookup.findVarHandle(?, ?, ?) * HINT: params are Declaring class, Variable name, Variable type */ VarHandle publicVariableVarHandle = MethodHandles .lookup() .in(TestSolutionGetter.class) .findVarHandle(TestSolutionGetter.class, "publicVariable", Integer.class); assertEquals(publicVariableVarHandle.coordinateTypes().size(), 1, "There should only be one coordinateType"); assertEquals(TestSolutionGetter.class, publicVariableVarHandle.coordinateTypes().get(0), "The only coordinate type is TestKataGetter"); assertEquals(1, publicVariableVarHandle.get(this), "The value of the field should be 1"); } catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) { fail(TEST_FAILURE.getValue() + e.getMessage()); } } /* -------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------- */ /* * BEGIN: PRIVATE VARIABLE USING REFLECTION AND VARIABLE HANDLES */ @Test @Tag("PASSING") @Order(3) public void getPrivateVariableFromConstructedClassViaReflection() { try { Class<?> clazz = TestSolutionGetter.class; Field privateVariableField = clazz.getDeclaredField("privateVariable"); privateVariableField.setAccessible(true); assertEquals(2, privateVariableField.get(this), "The value of the field should be 2"); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { fail(REFLECTION_FAILURE.getValue() + e.getMessage()); } } @Test @Tag("PASSING") @Order(4) public void getPrivateVariableFromConstructedClassViaVarHandles() { try { /* * DONE: * Replace the "null"s with valid values to get a VarHandle. * Check API: java.lang.invoke.MethodHandles.privateLookupIn(?, ?) * HINT: params are Target class and type of lookup * Check API: java.lang.invoke.MethodHandles.Lookup.findVarHandle(?, ?, ?) * HINT: params are Declaring class, Variable name, Variable type */ VarHandle privateVariableVarHandle = MethodHandles .privateLookupIn(TestSolutionGetter.class, MethodHandles.lookup()) .findVarHandle(TestSolutionGetter.class, "privateVariable", Integer.class); assertEquals(1, privateVariableVarHandle.coordinateTypes().size(), "There should only be one coordinateType"); assertEquals(TestSolutionGetter.class, privateVariableVarHandle.coordinateTypes().get(0), "The only coordinate type is AttributeGetterTest"); assertTrue(privateVariableVarHandle.isAccessModeSupported(VarHandle.AccessMode.GET), "Access mode for a GET should be true"); assertEquals(2, privateVariableVarHandle.get(this), "The value of the field should be 2"); } catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) { fail(TEST_FAILURE.getValue() + e.getMessage()); } } /* -------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------- */ /* * BEGIN: PRIVATE 1-DIMENSIONAL ARRAY VARIABLE USING REFLECTION AND VARIABLE HANDLES */ @Test @Tag("PASSING") @Order(5) public void getPrimitiveArrayFromConstructedClassViaReflection() { int[] reflectedArray = null; try { Class<?> clazz = TestSolutionGetter.class; Field privatePrimitiveArrayVariableField = clazz.getDeclaredField("privatePrimitiveArrayVariable"); privatePrimitiveArrayVariableField.setAccessible(true); Class<?> fieldClass = privatePrimitiveArrayVariableField.getType(); if (fieldClass.isArray()) { reflectedArray = int[].class.cast(privatePrimitiveArrayVariableField.get(this)); } assertEquals(3, reflectedArray.length, "The length of the array should be 3"); assertEquals(1, reflectedArray[0], "The first element of the array should be 1"); assertEquals(3, reflectedArray[2], "The third of the array should be 3"); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { fail(UNSAFE_FAILURE.getValue() + e.getMessage()); } } @Test @Tag("PASSING") @Order(6) public void getPrimitiveArrayFromConstructedClassViaVarHandles() { try { /* * DONE: * Replace the "null"s with valid values to get a VarHandle. * Check API: java.lang.invoke.MethodHandles.privateLookupIn(?, ?) * HINT: params are Target class, Lookup type * Check API: java.lang.invoke.MethodHandles.Lookup.findVarHandle(?, ?, ?) * HINT: params are Declaring class, Variable name, Variable type */ VarHandle privatePrimitiveArrayVariableVarHandle = MethodHandles .privateLookupIn(TestSolutionGetter.class, MethodHandles.lookup()) .findVarHandle(TestSolutionGetter.class, "privatePrimitiveArrayVariable", int[].class); assertEquals(1, privatePrimitiveArrayVariableVarHandle.coordinateTypes().size(), "There should only be one coordinateType"); assertEquals(TestSolutionGetter.class, privatePrimitiveArrayVariableVarHandle.coordinateTypes().get(0), "The only coordinate type is AttributeGetterTest"); int[] varHandleTypeArray = int[].class.cast( privatePrimitiveArrayVariableVarHandle.get(this)); /* * TODO: * Replace the "null"s with valid values to get a VarHandle. * Check API: java.lang.invoke.MethodHandles.arrayElementVarHandle(?) */ VarHandle arrayElementHandle = MethodHandles.arrayElementVarHandle(int[].class); assertEquals(3, varHandleTypeArray.length, "The length of the array should be 3"); assertEquals(1, arrayElementHandle.get(privatePrimitiveArrayVariable, 0), "The first element of the array should be 1"); assertEquals(3, arrayElementHandle.get(privatePrimitiveArrayVariable, 2), "The third of the array should be 3"); } catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) { fail(TEST_FAILURE.getValue() + e.getMessage()); } } /* -------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------- */ /* * BEGIN: PRIVATE 2-DIMENSIONAL ARRAY VARIABLE USING REFLECTION AND VARIABLE HANDLES */ @Test @Tag("PASSING") @Order(7) public void get2DimensionalPrimitiveArrayFromConstructedClassViaReflection() { int[][] reflectedArray = null; try { Class<?> clazz = TestSolutionGetter.class; Field privatePrimitive2DArrayVariableField = clazz.getDeclaredField("privatePrimitive2DArrayVariable"); privatePrimitive2DArrayVariableField.setAccessible(true); Class<?> fieldClass = privatePrimitive2DArrayVariableField.getType(); if (fieldClass.isArray()) { reflectedArray = int[][].class.cast(privatePrimitive2DArrayVariableField.get(this)); } assertEquals(3, reflectedArray.length, "The length of the array should be 3"); assertEquals(1, reflectedArray[0][0], "The first of first element of the array should be 1"); assertEquals(9, reflectedArray[2][2], "The third of third of the array should be 9"); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { fail(UNSAFE_FAILURE.getValue() + e.getMessage()); } } @Test @Tag("PASSING") @Order(8) public void get2DimensionalPrimitiveArrayFromConstructedClassViaVarHandles() { try { /* * DONE: * Replace the "null"s with valid values to get a VarHandle. * Check API: java.lang.invoke.MethodHandles.privateLookupIn(?, ?) * HINT: params are Target class, Lookup type * Check API: java.lang.invoke.MethodHandles.Lookup.findVarHandle(?, ?, ?) * HINT: params are Declaring class, Variable name, Variable type */ VarHandle privatePrimitive2DArrayVariableVarHandle = MethodHandles .privateLookupIn(TestSolutionGetter.class, MethodHandles.lookup()) .findVarHandle(TestSolutionGetter.class, "privatePrimitive2DArrayVariable", int[][].class); assertEquals(1, privatePrimitive2DArrayVariableVarHandle.coordinateTypes().size(), "There should only be one coordinateType"); assertEquals(TestSolutionGetter.class, privatePrimitive2DArrayVariableVarHandle.coordinateTypes().get(0), "The only coordinate type is AttributeGetterTest"); int[][] varHandleTypeArray = int[][].class.cast( privatePrimitive2DArrayVariableVarHandle.get(this)); /* * TODO: * Replace the "null"s with valid values to get a VarHandle. * Check API: java.lang.invoke.MethodHandles.arrayElementVarHandle(?) */ VarHandle arrayElementHandle = MethodHandles.arrayElementVarHandle(int[][].class); assertEquals(3, varHandleTypeArray.length, "The length of the array should be 3"); assertEquals(1, ((int[]) arrayElementHandle.get(privatePrimitive2DArrayVariable, 0))[0], "The first element of the array should be 1"); assertEquals(9, ((int[]) arrayElementHandle.get(privatePrimitive2DArrayVariable, 2))[2], "The last element of the array should be 9"); } catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) { fail(TEST_FAILURE.getValue() + e.getMessage()); } } }