001
002 /*
003 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004 *
005 * $Id: IdGenerator.java 312 2012-03-26 21:46:17Z archie.cobbs $
006 */
007
008 package org.dellroad.stuff.java;
009
010 import java.lang.ref.Reference;
011 import java.lang.ref.ReferenceQueue;
012 import java.lang.ref.WeakReference;
013 import java.util.HashMap;
014 import java.util.LinkedList;
015 import java.util.concurrent.Callable;
016
017 /**
018 * Registry of unique IDs for objects.
019 *
020 * <p>
021 * Instances support creating unique {@code long} ID numbers for objects, as well as setting the unique ID
022 * to a specific value for any unregistered object.
023 *
024 * <p>
025 * This class uses object identity, not {@link Object#equals Object.equals()}, to distinguish objects.
026 *
027 * <p>
028 * Weak references are used to ensure that registered objects can be garbage collected normally.
029 *
030 * <p>
031 * New {@code long} ID numbers are issued serially; after 2<sup>64</sup>-1 invocations of {@link #getId getId()},
032 * an {@link IllegalStateException} will be thrown.
033 *
034 * @see org.dellroad.stuff.jibx.IdMapper
035 */
036 public class IdGenerator {
037
038 private static final ThreadLocal<LinkedList<IdGenerator>> CURRENT = new ThreadLocal<LinkedList<IdGenerator>>() {
039 @Override
040 public LinkedList<IdGenerator> initialValue() {
041 return new LinkedList<IdGenerator>();
042 }
043 };
044
045 private final HashMap<Ref, Long> idMap = new HashMap<Ref, Long>();
046 private final HashMap<Long, Ref> refMap = new HashMap<Long, Ref>();
047 private final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
048
049 private long next = 1;
050
051 /**
052 * Get a unique ID for the given object.
053 *
054 * <p>
055 * If this method has been previously invoked on this instance with the same {@code obj} parameter (where "same" means
056 * object identity, not {@link Object#equals Object.equals()} identity), then the same ID value will be returned.
057 * Otherwise a new ID value will be returned.
058 *
059 * <p>
060 * New IDs are assigned sequentially starting at {@code 1}. No conflict avoidance with IDs assigned
061 * via {@link #setId setId()} is performed; if there is a conflict, an exception is thrown.
062 *
063 * @throws IllegalArgumentException if {@code obj} is null
064 * @throws IllegalStateException if the next sequential ID has already been assigned to a different object
065 * via {@link #setId setId()}
066 * @throws IllegalStateException if all 2<sup>64</sup>-1 values have been used up
067 * @return a non-zero, unique identifier for {@code obj}
068 */
069 public synchronized long getId(Object obj) {
070 if (obj == null)
071 throw new IllegalArgumentException("null obj");
072 this.flush();
073 Ref ref = new Ref(obj, this.queue);
074 Long id = this.idMap.get(ref);
075 if (id == null) {
076 if (this.next == 0)
077 throw new IllegalStateException("no more identifiers left!");
078 id = this.next++;
079 this.idMap.put(ref, id);
080 this.refMap.put(id, ref);
081 }
082 return id;
083 }
084
085 /**
086 * Assign a unique ID to the given object. Does nothing if the object and ID number are already associated.
087 *
088 * @param obj object to assign
089 * @param id unique ID number to assign
090 * @throws IllegalArgumentException if {@code obj} is null
091 * @throws IllegalArgumentException if {@code id} has already been assigned to some other object
092 */
093 public synchronized void setId(Object obj, long id) {
094 if (obj == null)
095 throw new IllegalArgumentException("null obj");
096 this.flush();
097 Ref ref = this.refMap.get(id);
098 if (ref != null) {
099 if (ref.get() != obj)
100 throw new IllegalArgumentException("id " + id + " is already assigned to another object");
101 return;
102 }
103 ref = new Ref(obj, this.queue);
104 this.idMap.put(ref, id);
105 this.refMap.put(id, ref);
106 }
107
108 /**
109 * Get the object assigned to the given ID.
110 *
111 * @param id unique ID
112 * @return object associated with that ID, or null if no object is assigned to {@code id}
113 */
114 public synchronized Object getObject(long id) {
115 this.flush();
116 Ref ref = this.refMap.get(id);
117 return ref != null ? ref.get() : null;
118 }
119
120 /**
121 * Flush any cleared weak references.
122 *
123 * <p>
124 * This operation is invoked by {@link #getId getId()}, so it's not necessary to explicitly invoke it.
125 * However, if a lot of previously ID'd objects have been garbage collected since the last call to
126 * {@link #getId getId()}, then invoking this method may free up some additional memory.
127 */
128 public synchronized void flush() {
129 Reference<? extends Object> entry;
130 while ((entry = this.queue.poll()) != null) {
131 Ref ref = (Ref)entry;
132 Long id = this.idMap.get(ref);
133 this.idMap.remove(ref);
134 this.refMap.remove(id);
135 }
136 }
137
138 /**
139 * Create a new {@link IdGenerator} and make it available via {@link #get()} for the duration of the given operation.
140 *
141 * <p>
142 * This method is re-entrant: nested invocations of this method in the same thread will cause new {@link IdGenerator}
143 * instances to be created and used for the duration of the nested action.
144 *
145 * @param action action to perform, and which may successfully invoke {@link #get}
146 * @throws NullPointerException if {@code action} is null
147 */
148 public static void run(final Runnable action) {
149 IdGenerator.CURRENT.get().push(new IdGenerator());
150 try {
151 action.run();
152 } finally {
153 IdGenerator.CURRENT.get().pop();
154 }
155 }
156
157 /**
158 * Create a new {@link IdGenerator} and make it available via {@link #get()} for the duration of the given operation.
159 *
160 * <p>
161 * This method is re-entrant: nested invocations of this method in the same thread will cause new {@link IdGenerator}
162 * instances to be created and used for the duration of the nested action.
163 *
164 * @param action action to perform, and which may successfully invoke {@link #get}
165 * @return result of invoking {@code action}
166 * @throws NullPointerException if {@code action} is null
167 */
168 public static <R> R run(final Callable<R> action) throws Exception {
169 IdGenerator.CURRENT.get().push(new IdGenerator());
170 try {
171 return action.call();
172 } finally {
173 IdGenerator.CURRENT.get().pop();
174 }
175 }
176
177 /**
178 * Get the {@link IdGenerator} associated with the current thread.
179 * This method only works when the current thread is running within an invocation of {@link #run run()};
180 * otherwise, an {@link IllegalStateException} is thrown.
181 *
182 * @return the {@link IdGenerator} created in the most recent, still running invocation of {@link #run} in this thread
183 * @throws IllegalStateException if there is not such instance
184 */
185 public static IdGenerator get() {
186 IdGenerator current = IdGenerator.CURRENT.get().peek();
187 if (current == null)
188 throw new IllegalStateException("not running within an invocation of run()");
189 return current;
190 }
191
192 // Reference to a registered object that weakly references the actual object
193 private static final class Ref extends WeakReference<Object> {
194
195 private final int hashCode;
196
197 Ref(Object obj, ReferenceQueue<Object> queue) {
198 super(obj, queue);
199 if (obj == null)
200 throw new IllegalArgumentException("null obj");
201 this.hashCode = System.identityHashCode(obj);
202 }
203
204 @Override
205 public boolean equals(Object obj) {
206 if (obj == this)
207 return true;
208 if (obj == null || obj.getClass() != this.getClass())
209 return false;
210 Ref that = (Ref)obj;
211 obj = this.get();
212 return obj != null ? obj == that.get() : false;
213 }
214
215 @Override
216 public int hashCode() {
217 return this.hashCode;
218 }
219 }
220 }
221