package com.iluwatar.singleton;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

/**
 * This class provides several test case that test singleton construction.
 *
 * The first proves that multiple calls to the singleton getInstance object are the same when called in the SAME thread.
 * The second proves that multiple calls to the singleton getInstance object are the same when called in the DIFFERENT thread.
 * The third proves that there is a hole if the singleton is created reflectively
 *
 */
public class LazyLoadedSingletonThreadSafetyTest {

    private static final int NUM_THREADS = 5;
    private List<ThreadSafeLazyLoadedIvoryTower> threadObjects = new ArrayList<>();

    //NullObject class so Callable has to return something
    private class NullObject{private NullObject(){}}

    @Test
    public void test_MultipleCallsReturnTheSameObjectInSameThread() {
        //Create several instances in the same calling thread
        ThreadSafeLazyLoadedIvoryTower instance1 = ThreadSafeLazyLoadedIvoryTower.getInstance();
        ThreadSafeLazyLoadedIvoryTower instance2 = ThreadSafeLazyLoadedIvoryTower.getInstance();
        ThreadSafeLazyLoadedIvoryTower instance3 = ThreadSafeLazyLoadedIvoryTower.getInstance();
        //now check they are equal
        assertEquals(instance1, instance1);
        assertEquals(instance1, instance2);
        assertEquals(instance2, instance3);
        assertEquals(instance1, instance3);
    }

    @Test
    public void test_MultipleCallsReturnTheSameObjectInDifferentThreads() throws InterruptedException, ExecutionException {
        {//create several threads and inside each callable instantiate the singleton class
            ExecutorService executorService = Executors.newSingleThreadExecutor();

            List<Callable<NullObject>> threadList = new ArrayList<>();
            for (int i = 0; i < NUM_THREADS; i++) {
                threadList.add(new SingletonCreatingThread());
            }

            ExecutorService service = Executors.newCachedThreadPool();
            List<Future<NullObject>> results = service.invokeAll(threadList);

            //wait for all of the threads to complete
            for (Future res : results) {
                res.get();
            }

            //tidy up the executor
            executorService.shutdown();
        }
        {//now check the contents that were added to threadObjects by each thread
            assertEquals(NUM_THREADS, threadObjects.size());
            assertEquals(threadObjects.get(0), threadObjects.get(1));
            assertEquals(threadObjects.get(1), threadObjects.get(2));
            assertEquals(threadObjects.get(2), threadObjects.get(3));
            assertEquals(threadObjects.get(3), threadObjects.get(4));
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void test_HoleInSingletonCreationIfUsingReflection() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Field[] f = ThreadSafeLazyLoadedIvoryTower.class.getDeclaredFields();
        for (Field ff : f) {
            System.out.println(ff.getDeclaringClass());
        }
        assertEquals("One field only in ThreadSafeLazyLoadedIvoryTower", 1, f.length);
        f[0].setAccessible(true);

        {//reflectively create an object - the singleton field is null
            Class lazyIvoryTowerClazz = Class.forName("com.iluwatar.singleton.ThreadSafeLazyLoadedIvoryTower");
            Constructor<ThreadSafeLazyLoadedIvoryTower> constructor = lazyIvoryTowerClazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            ThreadSafeLazyLoadedIvoryTower instance = constructor.newInstance();
            assertNull(f[0].get(instance));
        }

        //instantiate the singleton but when we do the below code we are creating a new object where it is set to null still
        IvoryTower.getInstance();

        {//reflectively create an object - the singleton field is null as a new object is created
            Class lazyIvoryTowerClazz = Class.forName("com.iluwatar.singleton.ThreadSafeLazyLoadedIvoryTower");
            Constructor<ThreadSafeLazyLoadedIvoryTower> constructor = lazyIvoryTowerClazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            ThreadSafeLazyLoadedIvoryTower instance = constructor.newInstance();
            assertNull(f[0].get(instance));
        }
    }

    private class SingletonCreatingThread implements Callable<NullObject> {
        @Override
        public NullObject call() {
            //instantiate the thread safety class and add to list to test afterwards
            ThreadSafeLazyLoadedIvoryTower instance = ThreadSafeLazyLoadedIvoryTower.getInstance();
            threadObjects.add(instance);
            return new NullObject();//return null object (cannot return Void)
        }
    }
}

@@ -3260,32 +3260,54 @@
 tiationException
+, NoSuchFieldException
  %7B%0A        Field
@@ -3310,10 +3310,8 @@
 ield
-%5B%5D
  f =
@@ -3368,192 +3368,19 @@
 ield
-s();%0A        for (Field ff : f) %7B%0A            System.out.println(ff.getDeclaringClass());%0A        %7D%0A        assertEquals(%22One field only in ThreadSafeLazyLoadedIvoryTower%22, 1, f.length
+(%22instance%22
 );%0A 
@@ -3387,19 +3387,16 @@
        f
-%5B0%5D
 .setAcce
@@ -3851,35 +3851,32 @@
     assertNull(f
-%5B0%5D
 .get(instance));
@@ -4491,32 +4491,32 @@
 .newInstance();%0A
+
             asse
@@ -4527,11 +4527,8 @@
 ll(f
-%5B0%5D
 .get

Fix unit test by makinig getField use the field name directly