package none.cvg.variables; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 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 sun.misc.Unsafe; 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.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; /* * DONE: * This test aims at using VarHandles to compare and then set a value of a given variable. * 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. */ @DisplayName("Compare and Set field values") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DisplayNameGeneration(HandlesKataDisplayNames.class) public class TestSolutionCompareAndSet { private Integer currentValue = 2; private volatile Integer privateVolatile = 2; private Integer newValue = 7; @Test @Tag("PASSING") @Order(1) public void compareAndSetUsingAtomicReference() { AtomicReference<Integer> atomicReference = new AtomicReference<>(privateVolatile); boolean exchanged = atomicReference.compareAndSet(privateVolatile, newValue); assertTrue(exchanged, "The value should have been changed to 7, " + "hence exchanged should be true" ); assertEquals(newValue, atomicReference.get(), "The value of the privateVolatile should now be 7"); exchanged = atomicReference.compareAndSet(privateVolatile, newValue); assertFalse(exchanged, "The value should not have changed again, " + "hence exchanged should be false" ); assertEquals(newValue, atomicReference.get(), "The value of the privateVolatile should still be 7"); } @Test @Tag("PASSING") @Order(2) public void compareAndSetUsingAtomicReferenceFieldUpdater() { final AtomicReferenceFieldUpdater<TestSolutionCompareAndSet, Integer> valueUpdater = AtomicReferenceFieldUpdater.newUpdater(TestSolutionCompareAndSet.class, Integer.class, "privateVolatile"); boolean exchanged = valueUpdater.compareAndSet(this, currentValue, newValue); assertTrue(exchanged, "The value should have been changed to 7, " + "hence exchanged should be true" ); assertEquals(newValue, valueUpdater.get(this), "The value of the privateVolatile should now be 7"); exchanged = valueUpdater.compareAndSet(this, 2, 33); assertFalse(exchanged, "The value should not have changed since the expected value " + "did not match, hence exchanged should be false" ); assertEquals(newValue, valueUpdater.get(this), "The value of the privateVolatile should still be 7"); } @Test @Tag("PASSING") @Order(3) public void compareAndSetUsingUnsafe() { try { Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafeInstance.setAccessible(true); final Unsafe unsafe = (Unsafe) theUnsafeInstance.get(null); final long offset; offset = unsafe.objectFieldOffset( TestSolutionCompareAndSet.class.getDeclaredField("privateVolatile")); boolean exchanged = unsafe.compareAndSwapObject(this, offset, currentValue, newValue); assertTrue(exchanged, "The value should have been changed to 7, " + "hence exchanged should be true" ); assertEquals(newValue, unsafe.getObject(this, offset), "The value of the privateVolatile should now be 7"); exchanged = unsafe.compareAndSwapObject(this, offset, 2, 33); assertFalse(exchanged, "The value should not have changed since the expected value " + "did not match, hence exchanged should be false" ); assertEquals(newValue, unsafe.getObject(this, offset), "The value of the privateVolatile should still be 7"); } catch (NoSuchFieldException | IllegalAccessException e) { fail(UNSAFE_FAILURE.getValue() + e.getMessage()); } } @Test @Tag("PASSING") @Order(4) public void compareAndSetUsingVarHandles() { VarHandle varHandle = null; 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 = MethodHandles .privateLookupIn(TestSolutionCompareAndSet.class, MethodHandles.lookup()) .findVarHandle(TestSolutionCompareAndSet.class, "privateVolatile", Integer.class); /* * DONE: * Replace the "false" to a compareAndSet call from 'currentValue' to 'newValue'. * Check API: java.lang.invoke.VarHandle.compareAndSet(...) * Three parameters are needed here: * 1. Instance of the class where the variable is being manipulated. * 2. The current value to compare * 3. The new value to set */ boolean exchanged = varHandle.compareAndSet(this, currentValue, newValue); assertTrue(exchanged, "The value should have been changed to 7, " + "hence exchanged should be true" ); assertEquals(newValue, varHandle.get(this), "The value of the privateVolatile should now be 7"); /* * TODO: * Replace the "false" to a compareAndSet call from 2 to 33. * Check API: java.lang.invoke.VarHandle.compareAndSet(...) * Three parameters are needed here: * 1. Instance of the class where the variable is being manipulated. * 2. The current value to compare * 3. The new value to set */ exchanged = varHandle.compareAndSet(this, 2, 33); assertFalse(exchanged, "The value should not have changed since the expected value " + "did not match, hence exchanged should be false" ); assertEquals(newValue, varHandle.get(this), "The value of the privateVolatile should still be 7"); } catch (NoSuchFieldException | IllegalAccessException e) { fail(TEST_FAILURE.getValue() + e.getMessage()); } } }