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 }