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 java.util.Collections;
19  import java.util.Iterator;
20  import java.util.Map.Entry;
21  import java.util.concurrent.ConcurrentMap;
22  
23  import org.jboss.netty.util.internal.ConcurrentIdentityWeakKeyHashMap;
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  
42      private final ChannelFutureListener remover = new ChannelFutureListener() {
43          public void operationComplete(ChannelFuture future) throws Exception {
44              remove(future.getChannel());
45          }
46      };
47  
48      private final boolean removeOnClose;
49  
50      /**
51       * Creates a {@link Channel} local variable by calling {@link #ChannelLocal(boolean)} with
52       * <code>false</code> as parameter
53       */
54      public ChannelLocal() {
55          this(false);
56      }
57  
58      /**
59       * Creates a {@link Channel} local variable.
60       *
61       * @param removeOnClose if <code>true</code> the {@link ChannelLocal} will remove a
62       *                      {@link Channel} from it own once the {@link Channel} was closed.
63       */
64      public ChannelLocal(boolean removeOnClose) {
65          this.removeOnClose = removeOnClose;
66      }
67      /**
68       * Returns the initial value of the variable.  By default, it returns
69       * {@code null}.  Override it to change the initial value.
70       *
71       * @param channel the channel where this local variable is accessed with
72       */
73      protected T initialValue(Channel channel) {
74          return null;
75      }
76  
77      /**
78       * Returns the value of this variable.
79       */
80      public T get(Channel channel) {
81          if (channel == null) {
82              throw new NullPointerException("channel");
83          }
84  
85          T value = map.get(channel);
86          if (value == null) {
87              value = initialValue(channel);
88              if (value != null) {
89                  T oldValue = setIfAbsent(channel, value);
90                  if (oldValue != null) {
91                      value = oldValue;
92                  }
93              }
94          }
95          return value;
96      }
97  
98      /**
99       * Sets the value of this variable.
100      *
101      * @return the old value. {@code null} if there was no old value.
102      */
103     public T set(Channel channel, T value) {
104         if (value == null) {
105             return remove(channel);
106         } else {
107             if (channel == null) {
108                 throw new NullPointerException("channel");
109             }
110             T old = map.put(channel, value);
111             if (removeOnClose) {
112                 channel.getCloseFuture().addListener(remover);
113             }
114             return old;
115         }
116     }
117 
118     /**
119      * Sets the value of this variable only when no value was set.
120      *
121      * @return {@code null} if the specified value was set.
122      *         An existing value if failed to set.
123      */
124     public T setIfAbsent(Channel channel, T value) {
125         if (value == null) {
126             return get(channel);
127         } else {
128             if (channel == null) {
129                 throw new NullPointerException("channel");
130             }
131             T mapping = map.putIfAbsent(channel, value);
132 
133             if (removeOnClose && mapping == null) {
134                 channel.getCloseFuture().addListener(remover);
135             }
136             return mapping;
137         }
138     }
139 
140     /**
141      * Removes the variable and returns the removed value.  If no value was set,
142      * this method returns the return value of {@link #initialValue(Channel)},
143      * which is {@code null} by default.
144      *
145      * @return the removed value.
146      *         {@linkplain #initialValue(Channel) an initial value} (by default
147      *         {@code null}) if no value was set.
148      */
149     public T remove(Channel channel) {
150         if (channel == null) {
151             throw new NullPointerException("channel");
152         }
153         T removed = map.remove(channel);
154         if (removed == null) {
155             return initialValue(channel);
156         } else {
157             if (removeOnClose) {
158                 channel.getCloseFuture().removeListener(remover);
159             }
160             return removed;
161         }
162     }
163 
164     /**
165      * Returns a <strong>read-only</strong> {@link Iterator} that holds all {@link Entry}'s of this ChannelLocal
166      */
167     public Iterator<Entry<Channel, T>> iterator() {
168         return Collections.unmodifiableSet(map.entrySet()).iterator();
169     }
170 }