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 }