// Path: sres/src/main/java/me/tatarka/sres/AbstractTrackable.java
// public abstract class AbstractTrackable implements Trackable {
//     private Set<Listener> listeners = new LinkedHashSet<Listener>();
// 
//     @Override
//     public void addListener(Listener listener) {
//         listeners.add(listener);
//     }
// 
//     @Override
//     public void removeListener(Listener listener) {
//         listeners.remove(listener);
//     }
// 
//     protected void notifyChange() {
//         ThreadHandlerProvider.getDefault().postToMainThread(new Runnable() {
//             @Override
//             public void run() {
//                 for (Listener listener : listeners) listener.onChange();
//             }
//         });
//     }
// }
// 
// Path: sres/src/main/java/me/tatarka/sres/ChangeTracker.java
// public class ChangeTracker {
//     private static ThreadLocal<Boolean> isTracking = new ThreadLocal<Boolean>() {
//         @Override
//         protected Boolean initialValue() {
//             return false;
//         }
//     };
//     private static ThreadLocal<List<Trackable>> trackedObservables = new ThreadLocal<List<Trackable>>() {
//         @Override
//         protected List<Trackable> initialValue() {
//             return new ArrayList<>();
//         }
//     };
// 
//     private Map<Trackable.Listener, List<Trackable>> observableMap = new HashMap<Trackable.Listener, List<Trackable>>();
// 
//     private static void startTracking() {
//         isTracking.set(true);
//     }
// 
//     private static List<Trackable> stopTracking() {
//         isTracking.set(false);
//         List<Trackable> properties = new ArrayList<Trackable>(trackedObservables.get());
//         trackedObservables.get().clear();
//         return properties;
//     }
// 
//     /**
//      * Notifies that the given trackable wants to be tracked. This should be called when a listener
//      * is registered on #{addListener}. For example, a @{link Property} calls it on @{link
//      * Property#get}.
//      *
//      * @param trackable the trackable to track
//      */
//     public static void track(Trackable trackable) {
//         if (isTracking.get()) trackedObservables.get().add(trackable);
//     }
// 
//     /**
//      * Clears all registered listeners so they will no longer be called when the trackable changes.
//      */
//     public void clear() {
//         ensureMainThread("clear");
// 
//         for (Map.Entry<Trackable.Listener, List<Trackable>> entry : observableMap.entrySet()) {
//             for (Trackable trackable : entry.getValue()) trackable.removeListener(entry.getKey());
//         }
//         observableMap.clear();
//     }
// 
//     /**
//      * Adds the listener. The listener will immediately be called to discover which trackables it
//      * tracks. Then it will be called every time any of it's trackables change.
//      *
//      * @param listener the listener
//      */
//     public void addListener(Trackable.Listener listener) {
//         ensureMainThread("addListener");
// 
//         startTracking();
//         listener.onChange();
//         List<Trackable> newEntry = stopTracking();
//         List<Trackable> oldEntry = observableMap.put(listener, newEntry);
//         if (oldEntry != null) {
//             for (Trackable trackable : oldEntry) trackable.removeListener(listener);
//         }
//         for (Trackable trackable : newEntry) trackable.addListener(listener);
//     }
// 
//     /**
//      * Removes the listener. It will no longer be called when it's trackables change.
//      *
//      * @param listener the listener
//      */
//     public void removeListener(Trackable.Listener listener) {
//         ensureMainThread("removeListener");
// 
//         List<Trackable> entry = observableMap.remove(listener);
//         if (entry != null) {
//             for (Trackable trackable : entry) trackable.removeListener(listener);
//         }
//     }
// 
//     private void ensureMainThread(String name) {
//         if (!ThreadHandlerProvider.getDefault().isOnMainThread()) {
//             throw new IllegalStateException("ChangeTracker" + name + "() must be called on the UI thread");
//         }
//     }
// }


import me.tatarka.sres.AbstractTrackable;
import me.tatarka.sres.ChangeTracker;
package me.tatarka.sres.property;


/**
 * A Property is a value in which to can track changes.
 */
public class IntProperty extends AbstractTrackable {
    private int value;

    /**
     * Constructs a new property with the given initial value.
     *
     * @param value the initial value
     */
    public IntProperty(int value) {
        this.value = value;
    }

    /**
     * Sets the property's value, notifying all listeners that it has changed.
     *
     * @param value the new value
     */
    public void set(int value) {
        this.value = value;
// Path: sres/src/main/java/me/tatarka/sres/AbstractTrackable.java
// public abstract class AbstractTrackable implements Trackable {
//     private Set<Listener> listeners = new LinkedHashSet<Listener>();
// 
//     @Override
//     public void addListener(Listener listener) {
//         listeners.add(listener);
//     }
// 
//     @Override
//     public void removeListener(Listener listener) {
//         listeners.remove(listener);
//     }
// 
//     protected void notifyChange() {
//         ThreadHandlerProvider.getDefault().postToMainThread(new Runnable() {
//             @Override
//             public void run() {
//                 for (Listener listener : listeners) listener.onChange();
//             }
//         });
//     }
// }
// 
// Path: sres/src/main/java/me/tatarka/sres/ChangeTracker.java
// public class ChangeTracker {
//     private static ThreadLocal<Boolean> isTracking = new ThreadLocal<Boolean>() {
//         @Override
//         protected Boolean initialValue() {
//             return false;
//         }
//     };
//     private static ThreadLocal<List<Trackable>> trackedObservables = new ThreadLocal<List<Trackable>>() {
//         @Override
//         protected List<Trackable> initialValue() {
//             return new ArrayList<>();
//         }
//     };
// 
//     private Map<Trackable.Listener, List<Trackable>> observableMap = new HashMap<Trackable.Listener, List<Trackable>>();
// 
//     private static void startTracking() {
//         isTracking.set(true);
//     }
// 
//     private static List<Trackable> stopTracking() {
//         isTracking.set(false);
//         List<Trackable> properties = new ArrayList<Trackable>(trackedObservables.get());
//         trackedObservables.get().clear();
//         return properties;
//     }
// 
//     /**
//      * Notifies that the given trackable wants to be tracked. This should be called when a listener
//      * is registered on #{addListener}. For example, a @{link Property} calls it on @{link
//      * Property#get}.
//      *
//      * @param trackable the trackable to track
//      */
//     public static void track(Trackable trackable) {
//         if (isTracking.get()) trackedObservables.get().add(trackable);
//     }
// 
//     /**
//      * Clears all registered listeners so they will no longer be called when the trackable changes.
//      */
//     public void clear() {
//         ensureMainThread("clear");
// 
//         for (Map.Entry<Trackable.Listener, List<Trackable>> entry : observableMap.entrySet()) {
//             for (Trackable trackable : entry.getValue()) trackable.removeListener(entry.getKey());
//         }
//         observableMap.clear();
//     }
// 
//     /**
//      * Adds the listener. The listener will immediately be called to discover which trackables it
//      * tracks. Then it will be called every time any of it's trackables change.
//      *
//      * @param listener the listener
//      */
//     public void addListener(Trackable.Listener listener) {
//         ensureMainThread("addListener");
// 
//         startTracking();
//         listener.onChange();
//         List<Trackable> newEntry = stopTracking();
//         List<Trackable> oldEntry = observableMap.put(listener, newEntry);
//         if (oldEntry != null) {
//             for (Trackable trackable : oldEntry) trackable.removeListener(listener);
//         }
//         for (Trackable trackable : newEntry) trackable.addListener(listener);
//     }
// 
//     /**
//      * Removes the listener. It will no longer be called when it's trackables change.
//      *
//      * @param listener the listener
//      */
//     public void removeListener(Trackable.Listener listener) {
//         ensureMainThread("removeListener");
// 
//         List<Trackable> entry = observableMap.remove(listener);
//         if (entry != null) {
//             for (Trackable trackable : entry) trackable.removeListener(listener);
//         }
//     }
// 
//     private void ensureMainThread(String name) {
//         if (!ThreadHandlerProvider.getDefault().isOnMainThread()) {
//             throw new IllegalStateException("ChangeTracker" + name + "() must be called on the UI thread");
//         }
//     }
// }


// Path: sres/src/main/java/me/tatarka/sres/property/IntProperty.java
import me.tatarka.sres.AbstractTrackable;
import me.tatarka.sres.ChangeTracker;

package me.tatarka.sres.property;


/**
 * A Property is a value in which to can track changes.
 */
public class IntProperty extends AbstractTrackable {
    private int value;

    /**
     * Constructs a new property with the given initial value.
     *
     * @param value the initial value
     */
    public IntProperty(int value) {
        this.value = value;
    }

    /**
     * Sets the property's value, notifying all listeners that it has changed.
     *
     * @param value the new value
     */
    public void set(int value) {
        this.value = value;
        notifyChange();