1. /*
  2. * @(#)file ThreadContext.java
  3. * @(#)author Sun Microsystems, Inc.
  4. * @(#)version 1.10
  5. * @(#)date 04/09/15
  6. *
  7. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  8. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  9. *
  10. */
  11. package com.sun.jmx.snmp;
  12. import java.util.Stack;
  13. import java.util.EmptyStackException;
  14. /**
  15. * <p><b>Warning: The interface of this class is subject to change.
  16. * Use at your own risk.</b></p>
  17. *
  18. * <p>This class associates a context with each thread that
  19. * references it. The context is a set of mappings between Strings
  20. * and Objects. It is managed as a stack, typically with code like
  21. * this:</p>
  22. *
  23. * <pre>
  24. * ThreadContext oldContext = ThreadContext.push(myKey, myObject);
  25. * // plus possibly further calls to ThreadContext.push...
  26. * try {
  27. * doSomeOperation();
  28. * } finally {
  29. * ThreadContext.restore(oldContext);
  30. * }
  31. * </pre>
  32. *
  33. * <p>The <code>try</code>...<code>finally</code> block ensures that
  34. * the <code>restore</code> is done even if
  35. * <code>doSomeOperation</code> terminates abnormally (with an
  36. * exception).</p>
  37. *
  38. * <p>A thread can consult its own context using
  39. * <code>ThreadContext.get(myKey)</code>. The result is the
  40. * value that was most recently pushed with the given key.</p>
  41. *
  42. * <p>A thread cannot read or modify the context of another thread.</p>
  43. *
  44. * <p><b>This API is a Sun Microsystems internal API and is subject
  45. * to change without notice.</b></p>
  46. * @version 1.10 12/19/03
  47. * @author Sun Microsystems, Inc
  48. */
  49. public class ThreadContext implements Cloneable {
  50. /* The context of a thread is stored as a linked list. At the
  51. head of the list is the value returned by localContext.get().
  52. At the tail of the list is a sentinel ThreadContext value with
  53. "previous" and "key" both null. There is a different sentinel
  54. object for each thread.
  55. Because a null key indicates the sentinel, we reject attempts to
  56. push context entries with a null key.
  57. The reason for using a sentinel rather than just terminating
  58. the list with a null reference is to protect against incorrect
  59. or even malicious code. If you have a reference to the
  60. sentinel value, you can erase the context stack. Only the
  61. caller of the first "push" that put something on the stack can
  62. get such a reference, so if that caller does not give this
  63. reference away, no one else can erase the stack.
  64. If the restore method took a null reference to mean an empty
  65. stack, anyone could erase the stack, since anyone can make a
  66. null reference.
  67. When the stack is empty, we discard the sentinel object and
  68. have localContext.get() return null. Then we recreate the
  69. sentinel object on the first subsequent push.
  70. ThreadContext objects are immutable. As a consequence, you can
  71. give a ThreadContext object to setInitialContext that is no
  72. longer current. But the interface says this can be rejected,
  73. in case we remove immutability later. */
  74. /* We have to comment out "final" here because of a bug in the JDK1.1
  75. compiler. Uncomment it when we discard 1.1 compatibility. */
  76. private /*final*/ ThreadContext previous;
  77. private /*final*/ String key;
  78. private /*final*/ Object value;
  79. private ThreadContext(ThreadContext previous, String key, Object value) {
  80. this.previous = previous;
  81. this.key = key;
  82. this.value = value;
  83. }
  84. /**
  85. * <p>Get the Object that was most recently pushed with the given key.</p>
  86. *
  87. * @param key the key of interest.
  88. *
  89. * @return the last Object that was pushed (using
  90. * <code>push</code>) with that key and not subsequently cancelled
  91. * by a <code>restore</code> or null if there is no such object.
  92. * A null return value may also indicate that the last Object
  93. * pushed was the value <code>null</code>. Use the
  94. * <code>contains</code> method to distinguish this case from the
  95. * case where there is no Object.
  96. *
  97. * @exception IllegalArgumentException if <code>key</code> is null.
  98. */
  99. public static Object get(String key) throws IllegalArgumentException {
  100. ThreadContext context = contextContaining(key);
  101. if (context == null)
  102. return null;
  103. else
  104. return context.value;
  105. }
  106. /**
  107. * <p>Check whether a value with the given key exists in the stack.
  108. * This means that the <code>push</code> method was called with
  109. * this key and it was not cancelled by a subsequent
  110. * <code>restore</code>. This method is useful when the
  111. * <code>get</code> method returns null, to distinguish between
  112. * the case where the key exists in the stack but is associated
  113. * with a null value, and the case where the key does not exist in
  114. * the stack.</p>
  115. *
  116. * @return true if the key exists in the stack.
  117. *
  118. * @exception IllegalArgumentException if <code>key</code> is null.
  119. */
  120. public static boolean contains(String key)
  121. throws IllegalArgumentException {
  122. return (contextContaining(key) != null);
  123. }
  124. /**
  125. * <p>Find the ThreadContext in the stack that contains the given key,
  126. * or return null if there is none.</p>
  127. *
  128. * @exception IllegalArgumentException if <code>key</code> is null.
  129. */
  130. private static ThreadContext contextContaining(String key)
  131. throws IllegalArgumentException {
  132. if (key == null)
  133. throw new IllegalArgumentException("null key");
  134. for (ThreadContext context = getContext();
  135. context != null;
  136. context = context.previous) {
  137. if (key.equals(context.key))
  138. return context;
  139. /* Note that "context.key" may be null if "context" is the
  140. sentinel, so don't write "if (context.key.equals(key))"! */
  141. }
  142. return null;
  143. }
  144. // /**
  145. // * Change the value that was most recently associated with the given key
  146. // * in a <code>push</code> operation not cancelled by a subsequent
  147. // * <code>restore</code>. If there is no such association, nothing happens
  148. // * and the return value is null.
  149. // *
  150. // * @param key the key of interest.
  151. // * @param value the new value to associate with that key.
  152. // *
  153. // * @return the value that was previously associated with the key, or null
  154. // * if the key does not exist in the stack.
  155. // *
  156. // * @exception IllegalArgumentException if <code>key</code> is null.
  157. // */
  158. // public static Object set(String key, Object value)
  159. // throws IllegalArgumentException {
  160. // ThreadContext context = contextContaining(key);
  161. // if (context == null)
  162. // return null;
  163. // Object old = context.value;
  164. // context.value = value;
  165. // return old;
  166. // }
  167. /**
  168. * <p>Push an object on the context stack with the given key.
  169. * This operation can subsequently be undone by calling
  170. * <code>restore</code> with the ThreadContext value returned
  171. * here.</p>
  172. *
  173. * @param key the key that will be used to find the object while it is
  174. * on the stack.
  175. * @param value the value to be associated with that key. It may be null.
  176. *
  177. * @return a ThreadContext that can be given to <code>restore</code> to
  178. * restore the stack to its state before the <code>push</code>.
  179. *
  180. * @exception IllegalArgumentException if <code>key</code> is null.
  181. */
  182. public static ThreadContext push(String key, Object value)
  183. throws IllegalArgumentException {
  184. if (key == null)
  185. throw new IllegalArgumentException("null key");
  186. ThreadContext oldContext = getContext();
  187. if (oldContext == null)
  188. oldContext = new ThreadContext(null, null, null); // make sentinel
  189. ThreadContext newContext = new ThreadContext(oldContext, key, value);
  190. setContext(newContext);
  191. return oldContext;
  192. }
  193. /**
  194. * <p>Return an object that can later be supplied to <code>restore</code>
  195. * to restore the context stack to its current state. The object can
  196. * also be given to <code>setInitialContext</code>.</p>
  197. *
  198. * @return a ThreadContext that represents the current context stack.
  199. */
  200. public static ThreadContext getThreadContext() {
  201. return getContext();
  202. }
  203. /**
  204. * <p>Restore the context stack to an earlier state. This typically
  205. * undoes the effect of one or more <code>push</code> calls.</p>
  206. *
  207. * @param oldContext the state to return. This is usually the return
  208. * value of an earlier <code>push</code> operation.
  209. *
  210. * @exception NullPointerException if <code>oldContext</code> is null.
  211. * @exception IllegalArgumentException if <code>oldContext</code>
  212. * does not represent a context from this thread, or if that
  213. * context was undone by an earlier <code>restore</code>.
  214. */
  215. public static void restore(ThreadContext oldContext)
  216. throws NullPointerException, IllegalArgumentException {
  217. /* The following test is not strictly necessary in the code as it
  218. stands today, since the reference to "oldContext.key" would
  219. generate a NullPointerException anyway. But if someone
  220. didn't notice that during subsequent changes, they could
  221. accidentally permit restore(null) with the semantics of
  222. trashing the context stack. */
  223. if (oldContext == null)
  224. throw new NullPointerException();
  225. /* Check that the restored context is in the stack. */
  226. for (ThreadContext context = getContext();
  227. context != oldContext;
  228. context = context.previous) {
  229. if (context == null) {
  230. throw new IllegalArgumentException("Restored context is not " +
  231. "contained in current " +
  232. "context");
  233. }
  234. }
  235. /* Discard the sentinel if the stack is empty. This means that it
  236. is an error to call "restore" a second time with the
  237. ThreadContext value that means an empty stack. That's why we
  238. don't say that it is all right to restore the stack to the
  239. state it was already in. */
  240. if (oldContext.key == null)
  241. oldContext = null;
  242. setContext(oldContext);
  243. }
  244. /**
  245. * <p>Set the initial context of the calling thread to a context obtained
  246. * from another thread. After this call, the calling thread will see
  247. * the same results from the <code>get</code> method as the thread
  248. * from which the <code>context</code> argument was obtained, at the
  249. * time it was obtained.</p>
  250. *
  251. * <p>The <code>context</code> argument must be the result of an earlier
  252. * <code>push</code> or <code>getThreadContext</code> call. It is an
  253. * error (which may or may not be detected) if this context has been
  254. * undone by a <code>restore</code>.</p>
  255. *
  256. * <p>The context stack of the calling thread must be empty before this
  257. * call, i.e., there must not have been a <code>push</code> not undone
  258. * by a subsequent <code>restore</code>.</p>
  259. *
  260. * @exception IllegalArgumentException if the context stack was
  261. * not empty before the call. An implementation may also throw this
  262. * exception if <code>context</code> is no longer current in the
  263. * thread from which it was obtained.
  264. */
  265. /* We rely on the fact that ThreadContext objects are immutable.
  266. This means that we don't have to check that the "context"
  267. argument is valid. It necessarily represents the head of a
  268. valid chain of ThreadContext objects, even if the thread from
  269. which it was obtained has subsequently been set to a point
  270. later in that chain using "restore". */
  271. public void setInitialContext(ThreadContext context)
  272. throws IllegalArgumentException {
  273. /* The following test assumes that we discard sentinels when the
  274. stack is empty. */
  275. if (getContext() != null)
  276. throw new IllegalArgumentException("previous context not empty");
  277. setContext(context);
  278. }
  279. private static ThreadContext getContext() {
  280. return (ThreadContext) localContext.get();
  281. }
  282. private static void setContext(ThreadContext context) {
  283. localContext.set(context);
  284. }
  285. private static ThreadLocal localContext = new ThreadLocal();
  286. }