// Path: src/main/java/no/gorman/database/DBFunctions.java
// public static Object get(Field f, Object instance) {
//     try {
//         return f.get(instance);
//     }catch (IllegalAccessException e) {
//         throw new RuntimeException(e);
//     }
// }
// 
// Path: src/main/java/no/gorman/database/DBFunctions.java
// public static List<Field> getMappedFields(Class<?> clazz) {
//     List<Field> columnFields = asList(clazz.getDeclaredFields()).stream()
//             .filter(f -> f.isAnnotationPresent(Column.class))
//             .collect(toList());
//     columnFields.forEach(f->f.setAccessible(true));
// 
//     if (clazz.getSuperclass() != null) {
//         columnFields.addAll(getMappedFields(clazz.getSuperclass()));
//     }
//     return columnFields;
// }
// 
// Path: src/main/java/no/gorman/database/DBFunctions.java
// public static <T> Field getPrimaryKeyField(T obj) {
//     return getMappedFields(obj.getClass()).stream().filter(f-> getColumn(f).getType() == PrimaryKey).findFirst().orElseThrow(() -> new IllegalArgumentException(obj.getClass().getName() + " has no primary key defined"));
// }


import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static no.gorman.database.DBFunctions.get;
import static no.gorman.database.DBFunctions.getMappedFields;
import static no.gorman.database.DBFunctions.getPrimaryKeyField;
    public static void inform(RowIdentifier rowIdentifier, Object suspect) {
        getSpies(rowIdentifier).orElse(new HashSet<>()).forEach(spy -> spy.suspectAltered(suspect));
    }

    public static void informAllAgents(Object... suspects) {
        for (Object suspect : suspects) {
            findWhoMightBeInterested(suspect).forEach(row -> inform(row, suspect));
        }
    }

    public static void spyOn(Object suspect, Spy spy) {
        for (RowIdentifier key : findRowsToSpyOn(suspect)) {
            if (!spies.containsKey(key)) {
                spies.put(key, new WeakHashMap<>());
            }
            spyRefs.add(new WeakReference<>(spy, terminatedSpies));
            spies.get(key).put(spy, true);
        }
    }

    /**
     * Example:
     * findRowsToSpyOn(<child with child_id=42>) would return the
     * primary key row of the child table: child_id = 42,
     * plus the foreign references referring to this entry. For example
     * the Schedule table's schedule_child_id=42
     *
     */
    public static <T> Collection<RowIdentifier> findRowsToSpyOn(T suspect) {
        List<RowIdentifier> keys = new ArrayList<>();
// Path: src/main/java/no/gorman/database/DBFunctions.java
// public static Object get(Field f, Object instance) {
//     try {
//         return f.get(instance);
//     }catch (IllegalAccessException e) {
//         throw new RuntimeException(e);
//     }
// }
// 
// Path: src/main/java/no/gorman/database/DBFunctions.java
// public static List<Field> getMappedFields(Class<?> clazz) {
//     List<Field> columnFields = asList(clazz.getDeclaredFields()).stream()
//             .filter(f -> f.isAnnotationPresent(Column.class))
//             .collect(toList());
//     columnFields.forEach(f->f.setAccessible(true));
// 
//     if (clazz.getSuperclass() != null) {
//         columnFields.addAll(getMappedFields(clazz.getSuperclass()));
//     }
//     return columnFields;
// }
// 
// Path: src/main/java/no/gorman/database/DBFunctions.java
// public static <T> Field getPrimaryKeyField(T obj) {
//     return getMappedFields(obj.getClass()).stream().filter(f-> getColumn(f).getType() == PrimaryKey).findFirst().orElseThrow(() -> new IllegalArgumentException(obj.getClass().getName() + " has no primary key defined"));
// }


// Path: src/main/java/no/gorman/database/BigBrother.java
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static no.gorman.database.DBFunctions.get;
import static no.gorman.database.DBFunctions.getMappedFields;
import static no.gorman.database.DBFunctions.getPrimaryKeyField;

    public static void inform(RowIdentifier rowIdentifier, Object suspect) {
        getSpies(rowIdentifier).orElse(new HashSet<>()).forEach(spy -> spy.suspectAltered(suspect));
    }

    public static void informAllAgents(Object... suspects) {
        for (Object suspect : suspects) {
            findWhoMightBeInterested(suspect).forEach(row -> inform(row, suspect));
        }
    }

    public static void spyOn(Object suspect, Spy spy) {
        for (RowIdentifier key : findRowsToSpyOn(suspect)) {
            if (!spies.containsKey(key)) {
                spies.put(key, new WeakHashMap<>());
            }
            spyRefs.add(new WeakReference<>(spy, terminatedSpies));
            spies.get(key).put(spy, true);
        }
    }

    /**
     * Example:
     * findRowsToSpyOn(<child with child_id=42>) would return the
     * primary key row of the child table: child_id = 42,
     * plus the foreign references referring to this entry. For example
     * the Schedule table's schedule_child_id=42
     *
     */
    public static <T> Collection<RowIdentifier> findRowsToSpyOn(T suspect) {
        List<RowIdentifier> keys = new ArrayList<>();
        Field pkField = getPrimaryKeyField(suspect);