Improved on the SvcConfig and renamed it.
[jsvc.git] / src / dolda / jsvc / ContextParam.java
diff --git a/src/dolda/jsvc/ContextParam.java b/src/dolda/jsvc/ContextParam.java
new file mode 100644 (file)
index 0000000..f146dc3
--- /dev/null
@@ -0,0 +1,87 @@
+package dolda.jsvc;
+
+import java.util.*;
+
+public class ContextParam<T> {
+    private boolean bound;
+    private T value;
+    private final Object id = new Object();
+    private Map<ThreadContext, T> perctx = new WeakHashMap<ThreadContext, T>();
+    private Map<Thread, T> perthr = new WeakHashMap<Thread, T>();
+       
+    public ContextParam(T def) {
+       this.value = def;
+       this.bound = true;
+    }
+       
+    public ContextParam() {
+       this.bound = false;
+    }
+       
+    public synchronized T get() {
+       Thread th = Thread.currentThread();
+       if(perthr.containsKey(th))
+           return(perthr.get(th));
+       ThreadContext ctx = getctx();
+       if(perctx.containsKey(ctx))
+           return(perctx.get(ctx));
+       if(!bound)
+           throw(new IllegalStateException("No value is bound to this parameter."));
+       return(value);
+    }
+       
+    public synchronized T ctxset(T val) {
+       ThreadContext ctx = getctx();
+       return(perctx.put(ctx, val));
+    }
+    
+    private static ThreadContext getctx() {
+       for(ThreadGroup tg = Thread.currentThread().getThreadGroup(); tg != null; tg = tg.getParent()) {
+           if(tg instanceof ThreadContext)
+               return((ThreadContext)tg);
+       }
+       return(null);
+    }
+    
+    public static Responder let(final Responder next, Object... params) {
+       final Map<ContextParam, Object> values = new HashMap<ContextParam, Object>();
+       if((params.length % 2) != 0)
+           throw(new IllegalArgumentException("SvcConfig.let takes only an even number of parameters"));
+       for(int i = 0; i < params.length; i += 2)
+           values.put((ContextParam)params[i], params[i + 1]);
+       
+       return(new Responder() {
+               /* This can very well actually be set to something
+                * of the wrong type, but since the result would,
+                * obviously, be a ClassCastException either way,
+                * this way is at least the more convenient. */
+               @SuppressWarnings("unchecked")
+               public void respond(Request req) {
+                   final Map<ContextParam, Object> old = new HashMap<ContextParam, Object>();
+                   Thread th = Thread.currentThread();
+                   for(Map.Entry<ContextParam, Object> val : values.entrySet()) {
+                       ContextParam p = val.getKey();
+                       synchronized(p) {
+                           if(p.perthr.containsKey(th))
+                               old.put(p, p.perthr.get(th));
+                           p.perthr.put(th, val.getValue());
+                       }
+                   }
+                   try {
+                       next.respond(req);
+                   } finally {
+                       for(Map.Entry<ContextParam, Object> val : values.entrySet()) {
+                           ContextParam p = val.getKey();
+                           synchronized(p) {
+                               if(old.containsKey(p)) {
+                                   p.perthr.put(th, old.get(p));
+                               } else {
+                                   p.perthr.remove(th);
+                               }
+                           }
+                       }
+                   }
+               }
+           });
+    }
+}