View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.channel;
17  
18  import org.jboss.netty.util.internal.ConcurrentIdentityWeakKeyHashMap;
19  
20  import java.util.Collections;
21  import java.util.Iterator;
22  import java.util.Map.Entry;
23  import java.util.concurrent.ConcurrentMap;
24  
25  /**
26   * A global variable that is local to a {@link Channel}.  Think of this as a
27   * variation of {@link ThreadLocal} whose key is a {@link Channel} rather than
28   * a {@link Thread#currentThread()}.  One difference is that you always have to
29   * specify the {@link Channel} to access the variable.
30   * <p>
31   * Alternatively, you might want to use the
32   * {@link ChannelHandlerContext#setAttachment(Object) ChannelHandlerContext.attachment}
33   * property, which performs better.
34   * @apiviz.stereotype utility
35   */
36  public class ChannelLocal<T> implements Iterable<Entry<Channel, T>> {
37  
38      private final ConcurrentMap<Channel, T> map =
39          new ConcurrentIdentityWeakKeyHashMap<Channel, T>();
40  
41      private final ChannelFutureListener remover = new ChannelFutureListener() {
42          public void operationComplete(ChannelFuture future) throws Exception {
43              remove(future.getChannel());
44          }
45      };
46  
47      private final boolean removeOnClose;
48  
49      /**
50       * Creates a {@link Channel} local variable by calling {@link #ChannelLocal(boolean)} with
51       * {@code false} as parameter
52       */
53      public ChannelLocal() {
54          this(false);
55      }
56  
57      /**
58       * Creates a {@link Channel} local variable.
59       *
60       * @param removeOnClose if {@code true} the {@link ChannelLocal} will remove a
61       *                      {@link Channel} from it own once the {@link Channel} was closed.
62       */
63      public ChannelLocal(boolean removeOnClose) {
64          this.removeOnClose = removeOnClose;
65      }
66      /**
67       * Returns the initial value of the variable.  By default, it returns
68       * {@code null}.  Override it to change the initial value.
69       *
70       * @param channel the channel where this local variable is accessed with
71       */
72      protected T initialValue(Channel channel) {
73          return null;
74      }
75  
76      /**
77       * Returns the value of this variable.
78       */
79      public T get(Channel channel) {
80          if (channel == null) {
81              throw new NullPointerException("channel");
82          }
83  
84          T value = map.get(channel);
85          if (value == null) {
86              value = initialValue(channel);
87              if (value != null) {
88                  T oldValue = setIfAbsent(channel, value);
89                  if (oldValue != null) {
90                      value = oldValue;
91                  }
92              }
93          }
94          return value;
95      }
96  
97      /**
98       * Sets the value of this variable.
99       *
100      * @return the old value. {@code null} if there was no old value.
101      */
102     public T set(Channel channel, T value) {
103         if (value == null) {
104             return remove(channel);
105         } else {
106             if (channel == null) {
107                 throw new NullPointerException("channel");
108             }
109             T old = map.put(channel, value);
110             if (removeOnClose) {
111                 channel.getCloseFuture().addListener(remover);
112             }
113             return old;
114         }
115     }
116 
117     /**
118      * Sets the value of this variable only when no value was set.
119      *
120      * @return {@code null} if the specified value was set.
121      *         An existing value if failed to set.
122      */
123     public T setIfAbsent(Channel channel, T value) {
124         if (value == null) {
125             return get(channel);
126         } else {
127             if (channel == null) {
128                 throw new NullPointerException("channel");
129             }
130             T mapping = map.putIfAbsent(channel, value);
131 
132             if (removeOnClose && mapping == null) {
133                 channel.getCloseFuture().addListener(remover);
134             }
135             return mapping;
136         }
137     }
138 
139     /**
140      * Removes the variable and returns the removed value.  If no value was set,
141      * this method returns the return value of {@link #initialValue(Channel)},
142      * which is {@code null} by default.
143      *
144      * @return the removed value.
145      *         {@linkplain #initialValue(Channel) an initial value} (by default
146      *         {@code null}) if no value was set.
147      */
148     public T remove(Channel channel) {
149         if (channel == null) {
150             throw new NullPointerException("channel");
151         }
152         T removed = map.remove(channel);
153         if (removed == null) {
154             return initialValue(channel);
155         } else {
156             if (removeOnClose) {
157                 channel.getCloseFuture().removeListener(remover);
158             }
159             return removed;
160         }
161     }
162 
163     /**
164      * Returns a <strong>read-only</strong> {@link Iterator} that holds all {@link Entry}'s of this ChannelLocal
165      */
166     public Iterator<Entry<Channel, T>> iterator() {
167         return Collections.unmodifiableSet(map.entrySet()).iterator();
168     }
169 }