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 }