
/*
 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.java;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;

/**
 * Generates unique IDs for any object.
 *
 * <p>
 * This class uses object identity, not {@link Object#equals Object.equals()}, to distinguish objects.
 * Weak references are used to ensure that identified objects can be garbage collected normally.
 *
 * <p>
 * The {@code long} ID numbers are issued serially; after 2<sup>64</sup>-1 invocations of {@link #getId getId()},
 * an {@link IllegalStateException} will be thrown.
 */
public class IdGenerator {

    private final HashMap<Object, Long> map = new HashMap<Object, Long>();
    private final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();

    private long next = 1;

    /**
     * Get a unique ID for the given object.
     *
     * <p>
     * If this method has been previously invoked on this instance with the same {@code obj} parameter (where "same" means
     * object identity, not {@link Object#equals Object.equals()} identity), then the same ID value will be returned.
     * Otherwise a new ID value will be returned.
     *
     * @throws IllegalArgumentException if {@code obj} is null
     * @throws IllegalStateException if all 2<sup>64</sup>-1 values have been used up
     * @return a non-zero, unique identifier for {@code obj}
     */
    public long getId(Object obj) {
        if (obj == null)
            throw new IllegalArgumentException("null obj");
        Key key = new Key(obj, this.queue);
        synchronized (this) {
            this.flush();
            Long id = this.map.get(key);
            if (id == null) {
                if (this.next == 0)
                    throw new IllegalStateException("no more identifiers left!");
                id = this.next++;
                this.map.put(key, id);
            }
            return id;
        }
    }

    /**
     * Flush any cleared weak references.
     *
     * <p>
     * This operation is invoked by {@link #getId getId()}, so it's not necessary to explicitly invoke it.
     * However, if a lot of previously ID'd objects have been garbage collected since the last call to
     * {@link #getId getId()}, then invoking this method may free up some additional memory.
     */
    public synchronized void flush() {
        Reference<? extends Object> key;
        while ((key = this.queue.poll()) != null)
            this.map.remove(key);
    }

    // Our hash key that weakly references the actual object
    private static final class Key extends WeakReference<Object> {

        private final int hashCode;

        Key(Object obj, ReferenceQueue<Object> queue) {
            super(obj, queue);
            if (obj == null)
                throw new IllegalArgumentException("null obj");
            this.hashCode = System.identityHashCode(obj);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this)
                return true;
            if (obj == null || obj.getClass() != this.getClass())
                return false;
            Key that = (Key)obj;
            obj = this.get();
            return obj != null ? obj == that.get() : false;
        }

        @Override
        public int hashCode() {
            return this.hashCode;
        }
    }
}


@@ -237,16 +237,45 @@
 HashMap;
+%0Aimport java.util.LinkedList;
 %0A%0A/**%0A *
@@ -704,36 +704,339 @@
 .%0A *
-/%0Apublic class IdGenerator %7B
+%0A * @see org.dellroad.stuff.jibx.IdMapper%0A */%0Apublic class IdGenerator %7B%0A%0A    private static final ThreadLocal%3CLinkedList%3CIdGenerator%3E%3E CURRENT = new ThreadLocal%3CLinkedList%3CIdGenerator%3E%3E() %7B%0A        @Override%0A        public LinkedList%3CIdGenerator%3E initialValue() %7B%0A            return new LinkedList%3CIdGenerator%3E();%0A        %7D%0A    %7D;
 %0A%0A  
@@ -2886,24 +2886,1477 @@
 ey);%0A    %7D%0A%0A
+    /**%0A     * Create a new %7B@link IdGenerator%7D and make it available via %7B@link #get()%7D for the duration of the given operation.%0A     *%0A     * %3Cp%3E%0A     * This method is re-entrant: nested invocations of this method in the same thread will cause new %7B@link IdGenerator%7D%0A     * instances to be created and used for the duration of the nested action.%0A     *%0A     * @param action action to perform, and which may successfully invoke %7B@link #get%7D%0A     * @throws NullPointerException if %7B@code action%7D is null%0A     */%0A    public static void run(final Runnable action) %7B%0A        IdGenerator.CURRENT.get().push(new IdGenerator());%0A        try %7B%0A            action.run();%0A        %7D finally %7B%0A            IdGenerator.CURRENT.get().pop();%0A        %7D%0A    %7D%0A%0A    /**%0A     * Get the %7B@link IdGenerator%7D associated with the current thread.%0A     * This method only works when the current thread is running within an invocation of %7B@link #run run()%7D;%0A     * otherwise, an %7B@link IllegalStateException%7D is thrown.%0A     *%0A     * @return the %7B@link IdGenerator%7D created in the most recent, still running invocation of %7B@link #run%7D in this thread%0A     * @throws IllegalStateException if there is not such instance%0A     */%0A    public static IdGenerator get() %7B%0A        IdGenerator current = IdGenerator.CURRENT.get().peek();%0A        if (current == null)%0A            throw new IllegalStateException(%22not running within an invocation of run()%22);%0A        return current;%0A    %7D%0A%0A
     // Our h

Add run() and get() methods for thread-local IdGenerator access.