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