Make IntObjectHashMap extend Map

Motivation:

It would be useful to support the Java `Map` interface in our primitive maps.

Modifications:

Renamed current methods to "pXXX", where p is short for "primitive". Made the template for all primitive maps extend the appropriate Map interface.

Result:

Fixes #3970
This commit is contained in:
nmittler 2015-07-13 14:53:20 -07:00
parent 07ae838677
commit 9e8b1ea587
13 changed files with 593 additions and 247 deletions

View File

@ -387,8 +387,7 @@ public class DefaultHttp2Connection implements Http2Connection {
@Override
public Http2Stream forEachChild(Http2StreamVisitor visitor) throws Http2Exception {
for (IntObjectHashMap.Entry<DefaultStream> entry : children.entries()) {
Http2Stream stream = entry.value();
for (DefaultStream stream : children.values()) {
if (!visitor.visit(stream)) {
return stream;
}

View File

@ -484,7 +484,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
char id = (char) payload.readUnsignedShort();
long value = payload.readUnsignedInt();
try {
settings.put(id, value);
settings.put(id, Long.valueOf(value));
} catch (IllegalArgumentException e) {
switch(id) {
case SETTINGS_MAX_FRAME_SIZE:

View File

@ -213,7 +213,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + settings.size() * SETTING_ENTRY_LENGTH);
writeFrameHeaderInternal(buf, payloadLength, SETTINGS, new Http2Flags(), 0);
for (CharObjectMap.Entry<Long> entry : settings.entries()) {
for (CharObjectMap.PrimitiveEntry<Long> entry : settings.entries()) {
writeUnsignedShort(entry.key(), buf);
writeUnsignedInt(entry.value(), buf);
}

View File

@ -14,18 +14,6 @@
*/
package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.collection.CharObjectHashMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static io.netty.handler.codec.base64.Base64Dialect.URL_SAFE;
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
@ -36,6 +24,18 @@ import static io.netty.util.CharsetUtil.UTF_8;
import static io.netty.util.ReferenceCountUtil.release;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.collection.CharObjectMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Client-side cleartext upgrade codec from HTTP to HTTP/2.
*/
@ -105,7 +105,7 @@ public class Http2ClientUpgradeCodec implements HttpClientUpgradeHandler.Upgrade
// Serialize the payload of the SETTINGS frame.
int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
buf = ctx.alloc().buffer(payloadLength);
for (CharObjectHashMap.Entry<Long> entry : settings.entries()) {
for (CharObjectMap.PrimitiveEntry<Long> entry : settings.entries()) {
writeUnsignedShort(entry.key(), buf);
writeUnsignedInt(entry.value(), buf);
}

View File

@ -46,6 +46,8 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
* the standard settings will not cause the map capacity to change.
*/
private static final int DEFAULT_CAPACITY = (int) (NUM_STANDARD_SETTINGS / DEFAULT_LOAD_FACTOR) + 1;
private static final Long FALSE = 0L;
private static final Long TRUE = 1L;
public Http2Settings() {
this(DEFAULT_CAPACITY);
@ -84,7 +86,7 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
* @throws IllegalArgumentException if verification of the setting fails.
*/
public Http2Settings headerTableSize(int value) {
put(SETTINGS_HEADER_TABLE_SIZE, (long) value);
put(SETTINGS_HEADER_TABLE_SIZE, Long.valueOf(value));
return this;
}
@ -96,14 +98,14 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
if (value == null) {
return null;
}
return value != 0L;
return TRUE.equals(value);
}
/**
* Sets the {@code SETTINGS_ENABLE_PUSH} value.
*/
public Http2Settings pushEnabled(boolean enabled) {
put(SETTINGS_ENABLE_PUSH, enabled ? 1L : 0L);
put(SETTINGS_ENABLE_PUSH, enabled ? TRUE : FALSE);
return this;
}
@ -120,7 +122,7 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
* @throws IllegalArgumentException if verification of the setting fails.
*/
public Http2Settings maxConcurrentStreams(long value) {
put(SETTINGS_MAX_CONCURRENT_STREAMS, value);
put(SETTINGS_MAX_CONCURRENT_STREAMS, Long.valueOf(value));
return this;
}
@ -137,7 +139,7 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
* @throws IllegalArgumentException if verification of the setting fails.
*/
public Http2Settings initialWindowSize(int value) {
put(SETTINGS_INITIAL_WINDOW_SIZE, (long) value);
put(SETTINGS_INITIAL_WINDOW_SIZE, Long.valueOf(value));
return this;
}
@ -154,7 +156,7 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
* @throws IllegalArgumentException if verification of the setting fails.
*/
public Http2Settings maxFrameSize(int value) {
put(SETTINGS_MAX_FRAME_SIZE, (long) value);
put(SETTINGS_MAX_FRAME_SIZE, Long.valueOf(value));
return this;
}
@ -183,7 +185,7 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
value = Integer.MAX_VALUE;
}
put(SETTINGS_MAX_HEADER_LIST_SIZE, (long) value);
put(SETTINGS_MAX_HEADER_LIST_SIZE, Long.valueOf(value));
return this;
}

View File

@ -46,6 +46,7 @@ import io.netty.util.collection.IntObjectMap;
import io.netty.util.concurrent.EventExecutor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@ -896,10 +897,10 @@ public class DefaultHttp2RemoteFlowControllerTest {
// Send a bunch of data on each stream.
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
streamSizes.put(STREAM_A, 400);
streamSizes.put(STREAM_B, 500);
streamSizes.put(STREAM_C, 600);
streamSizes.put(STREAM_D, 700);
streamSizes.put(STREAM_A, (Integer) 400);
streamSizes.put(STREAM_B, (Integer) 500);
streamSizes.put(STREAM_C, (Integer) 600);
streamSizes.put(STREAM_D, (Integer) 700);
FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@ -922,11 +923,11 @@ public class DefaultHttp2RemoteFlowControllerTest {
streamableBytesForTree(stream0));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_A, STREAM_C, STREAM_D)),
streamableBytesForTree(streamA));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
streamableBytesForTree(streamB));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
streamableBytesForTree(streamC));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
streamableBytesForTree(streamD));
}
@ -966,10 +967,10 @@ public class DefaultHttp2RemoteFlowControllerTest {
// Send a bunch of data on each stream.
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
streamSizes.put(STREAM_A, 400);
streamSizes.put(STREAM_B, 500);
streamSizes.put(STREAM_C, 600);
streamSizes.put(STREAM_D, 700);
streamSizes.put(STREAM_A, (Integer) 400);
streamSizes.put(STREAM_B, (Integer) 500);
streamSizes.put(STREAM_C, (Integer) 600);
streamSizes.put(STREAM_D, (Integer) 700);
FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@ -996,9 +997,9 @@ public class DefaultHttp2RemoteFlowControllerTest {
streamableBytesForTree(streamA));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B, STREAM_C, STREAM_D)),
streamableBytesForTree(streamB));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
streamableBytesForTree(streamC));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
streamableBytesForTree(streamD));
}
@ -1041,11 +1042,11 @@ public class DefaultHttp2RemoteFlowControllerTest {
// Send a bunch of data on each stream.
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
streamSizes.put(STREAM_A, 400);
streamSizes.put(STREAM_B, 500);
streamSizes.put(STREAM_C, 600);
streamSizes.put(STREAM_D, 700);
streamSizes.put(STREAM_E, 900);
streamSizes.put(STREAM_A, (Integer) 400);
streamSizes.put(STREAM_B, (Integer) 500);
streamSizes.put(STREAM_C, (Integer) 600);
streamSizes.put(STREAM_D, (Integer) 700);
streamSizes.put(STREAM_E, (Integer) 900);
FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@ -1072,11 +1073,11 @@ public class DefaultHttp2RemoteFlowControllerTest {
assertEquals(calculateStreamSizeSum(streamSizes,
Arrays.asList(STREAM_A, STREAM_E, STREAM_C, STREAM_D)),
streamableBytesForTree(streamA));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
streamableBytesForTree(streamB));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
streamableBytesForTree(streamC));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
streamableBytesForTree(streamD));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_E, STREAM_C, STREAM_D)),
streamableBytesForTree(streamE));
@ -1106,10 +1107,10 @@ public class DefaultHttp2RemoteFlowControllerTest {
// Send a bunch of data on each stream.
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
streamSizes.put(STREAM_A, 400);
streamSizes.put(STREAM_B, 500);
streamSizes.put(STREAM_C, 600);
streamSizes.put(STREAM_D, 700);
streamSizes.put(STREAM_A, (Integer) 400);
streamSizes.put(STREAM_B, (Integer) 500);
streamSizes.put(STREAM_C, (Integer) 600);
streamSizes.put(STREAM_D, (Integer) 700);
FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@ -1133,11 +1134,11 @@ public class DefaultHttp2RemoteFlowControllerTest {
streamableBytesForTree(stream0));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C, STREAM_D)),
streamableBytesForTree(streamA));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
streamableBytesForTree(streamB));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
streamableBytesForTree(streamC));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
streamableBytesForTree(streamD));
}
@ -1174,10 +1175,10 @@ public class DefaultHttp2RemoteFlowControllerTest {
// Send a bunch of data on each stream.
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
streamSizes.put(STREAM_A, 400);
streamSizes.put(STREAM_B, 500);
streamSizes.put(STREAM_C, 600);
streamSizes.put(STREAM_D, 700);
streamSizes.put(STREAM_A, (Integer) 400);
streamSizes.put(STREAM_B, (Integer) 500);
streamSizes.put(STREAM_C, (Integer) 600);
streamSizes.put(STREAM_D, (Integer) 700);
FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@ -1201,10 +1202,10 @@ public class DefaultHttp2RemoteFlowControllerTest {
streamableBytesForTree(stream0));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_A, STREAM_D)),
streamableBytesForTree(streamA));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
streamableBytesForTree(streamB));
assertEquals(0, streamableBytesForTree(streamC));
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
streamableBytesForTree(streamD));
}

View File

@ -65,14 +65,14 @@ public class Http2SettingsTest {
@Test
public void nonStandardSettingsShouldBeSet() {
char key = 0;
settings.put(key, 123L);
settings.put(key, (Long) 123L);
assertEquals(123L, (long) settings.get(key));
}
@Test
public void settingsShouldSupportUnsignedShort() {
char key = (char) (Short.MAX_VALUE + 1);
settings.put(key, 123L);
settings.put(key, (Long) 123L);
assertEquals(123L, (long) settings.get(key));
}

View File

@ -14,12 +14,12 @@
*/
package io.netty.util.collection;
import io.netty.util.internal.EmptyArrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Utility methods for primitive collections.
@ -61,11 +61,6 @@ public final class PrimitiveCollections {
throw new UnsupportedOperationException("put");
}
@Override
public void putAll(IntObjectMap<Object> sourceMap) {
throw new UnsupportedOperationException("putAll");
}
@Override
public Object remove(int key) {
return null;
@ -81,11 +76,21 @@ public final class PrimitiveCollections {
return true;
}
@Override
public boolean containsKey(Object key) {
return false;
}
@Override
public void clear() {
// Do nothing.
}
@Override
public Set<Integer> keySet() {
return Collections.emptySet();
}
@Override
public boolean containsKey(int key) {
return false;
@ -97,24 +102,39 @@ public final class PrimitiveCollections {
}
@Override
public Iterable<Entry<Object>> entries() {
public Iterable<PrimitiveEntry<Object>> entries() {
return Collections.emptySet();
}
@Override
public int[] keys() {
return EmptyArrays.EMPTY_INTS;
public Object get(Object key) {
return null;
}
@Override
public Object[] values(Class<Object> clazz) {
return EmptyArrays.EMPTY_OBJECTS;
public Object put(Integer key, Object value) {
throw new UnsupportedOperationException();
}
@Override
public Object remove(Object key) {
return null;
}
@Override
public void putAll(Map<? extends Integer, ?> m) {
throw new UnsupportedOperationException();
}
@Override
public Collection<Object> values() {
return Collections.emptyList();
}
@Override
public Set<Entry<Integer, Object>> entrySet() {
return Collections.emptySet();
}
}
/**
@ -122,9 +142,12 @@ public final class PrimitiveCollections {
*
* @param <V> the value type stored in the map.
*/
private static final class UnmodifiableIntObjectMap<V> implements IntObjectMap<V>,
Iterable<IntObjectMap.Entry<V>> {
final IntObjectMap<V> map;
private static final class UnmodifiableIntObjectMap<V> implements IntObjectMap<V> {
private final IntObjectMap<V> map;
private Set<Integer> keySet;
private Set<Entry<Integer, V>> entrySet;
private Collection<V> values;
private Iterable<PrimitiveEntry<V>> entries;
UnmodifiableIntObjectMap(IntObjectMap<V> map) {
this.map = map;
@ -140,11 +163,6 @@ public final class PrimitiveCollections {
throw new UnsupportedOperationException("put");
}
@Override
public void putAll(IntObjectMap<V> sourceMap) {
throw new UnsupportedOperationException("putAll");
}
@Override
public V remove(int key) {
throw new UnsupportedOperationException("remove");
@ -171,42 +189,80 @@ public final class PrimitiveCollections {
}
@Override
public boolean containsValue(V value) {
public boolean containsValue(Object value) {
return map.containsValue(value);
}
@Override
public Iterable<Entry<V>> entries() {
return this;
public boolean containsKey(Object key) {
return map.containsKey(key);
}
@Override
public Iterator<Entry<V>> iterator() {
return new IteratorImpl(map.entries().iterator());
public V get(Object key) {
return map.get(key);
}
@Override
public int[] keys() {
return map.keys();
public V put(Integer key, V value) {
throw new UnsupportedOperationException("put");
}
@Override
public V[] values(Class<V> clazz) {
return map.values(clazz);
public V remove(Object key) {
throw new UnsupportedOperationException("remove");
}
@Override
public void putAll(Map<? extends Integer, ? extends V> m) {
throw new UnsupportedOperationException("putAll");
}
@Override
public Iterable<PrimitiveEntry<V>> entries() {
if (entries == null) {
entries = new Iterable<PrimitiveEntry<V>>() {
@Override
public Iterator<PrimitiveEntry<V>> iterator() {
return new IteratorImpl(map.entries().iterator());
}
};
}
return entries;
}
@Override
public Set<Integer> keySet() {
if (keySet == null) {
keySet = Collections.unmodifiableSet(map.keySet());
}
return keySet;
}
@Override
public Set<Entry<Integer, V>> entrySet() {
if (entrySet == null) {
entrySet = Collections.unmodifiableSet(map.entrySet());
}
return entrySet;
}
@Override
public Collection<V> values() {
return map.values();
if (values == null) {
values = Collections.unmodifiableCollection(map.values());
}
return values;
}
/**
* Unmodifiable wrapper for an iterator.
*/
private class IteratorImpl implements Iterator<Entry<V>> {
final Iterator<Entry<V>> iter;
private class IteratorImpl implements Iterator<PrimitiveEntry<V>> {
final Iterator<PrimitiveEntry<V>> iter;
IteratorImpl(Iterator<Entry<V>> iter) {
IteratorImpl(Iterator<PrimitiveEntry<V>> iter) {
this.iter = iter;
}
@ -216,7 +272,7 @@ public final class PrimitiveCollections {
}
@Override
public Entry<V> next() {
public PrimitiveEntry<V> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
@ -232,10 +288,10 @@ public final class PrimitiveCollections {
/**
* Unmodifiable wrapper for an entry.
*/
private class EntryImpl implements Entry<V> {
final Entry<V> entry;
private class EntryImpl implements PrimitiveEntry<V> {
final PrimitiveEntry<V> entry;
EntryImpl(Entry<V> entry) {
EntryImpl(PrimitiveEntry<V> entry) {
this.entry = entry;
}

View File

@ -9,17 +9,19 @@ templateDirs.eachWithIndex { templateDir, i ->
void convertSources(String templateDir, String outputDir) {
String[] keyPrimitives = ["byte", "char", "short", "int", "long"]
String[] keyObjects = ["Byte", "Character", "Short", "Integer", "Long"];
String[] keyObjects = ["Byte", "Character", "Short", "Integer", "Long"]
String[] keyNumberMethod = ["byteValue", "charValue", "shortValue", "intValue", "longValue"]
keyPrimitives.eachWithIndex { keyPrimitive, i ->
convertTemplates templateDir, outputDir, keyPrimitive, keyObjects[i]
convertTemplates templateDir, outputDir, keyPrimitive, keyObjects[i], keyNumberMethod[i]
}
}
void convertTemplates(String templateDir,
String outputDir,
String keyPrimitive,
String keyObject) {
String keyObject,
String keyNumberMethod) {
def keyName = keyPrimitive.capitalize()
def replaceFrom = "(^.*)K([^.]+)\\.template\$"
def replaceTo = "\\1" + keyName + "\\2.java"
@ -32,6 +34,7 @@ void convertTemplates(String templateDir,
filter(token: "K", value: keyName)
filter(token: "k", value: keyPrimitive)
filter(token: "O", value: keyObject)
filter(token: "KEY_NUMBER_METHOD", value: keyNumberMethod)
filter(token: "HASH_CODE", value: hashCodeFn)
}
regexpmapper(from: replaceFrom, to: replaceTo)

View File

@ -17,12 +17,14 @@ package io.netty.util.collection;
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
import java.lang.reflect.Array;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A hash map implementation of {@link @K@ObjectMap} that uses open addressing for keys.
@ -32,7 +34,7 @@ import java.util.NoSuchElementException;
*
* @param <V> The value type stored in the map.
*/
public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectMap.Entry<V>> {
public class @K@ObjectHashMap<V> implements @K@ObjectMap<V> {
/** Default initial capacity. Used if not specified in the constructor */
public static final int DEFAULT_CAPACITY = 8;
@ -57,6 +59,15 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
private int size;
private int mask;
private final Set<@O@> keySet = new KeySet();
private final Set<Entry<@O@, V>> entrySet = new EntrySet();
private final Iterable<PrimitiveEntry<V>> entries = new Iterable<PrimitiveEntry<V>>() {
@Override
public Iterator<PrimitiveEntry<V>> iterator() {
return new PrimitiveIterator();
}
};
public @K@ObjectHashMap() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}
@ -139,9 +150,10 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
}
@Override
public void putAll(@K@ObjectMap<V> sourceMap) {
if (sourceMap instanceof IntObjectHashMap) {
public void putAll(Map<? extends @O@, ? extends V> sourceMap) {
if (sourceMap instanceof @K@ObjectHashMap) {
// Optimization - iterate through the arrays.
@SuppressWarnings("unchecked")
@K@ObjectHashMap<V> source = (@K@ObjectHashMap<V>) sourceMap;
for (int i = 0; i < source.values.length; ++i) {
V sourceValue = source.values[i];
@ -153,8 +165,8 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
}
// Otherwise, just add each entry.
for (Entry<V> entry : sourceMap.entries()) {
put(entry.key(), entry.value());
for (Entry<? extends @O@, ? extends V> entry : sourceMap.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@ -193,8 +205,9 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
}
@Override
public boolean containsValue(V value) {
V v1 = toInternal(value);
public boolean containsValue(Object value) {
@SuppressWarnings("unchecked")
V v1 = toInternal((V) value);
for (V v2 : values) {
// The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE).
if (v2 != null && v2.equals(v1)) {
@ -205,38 +218,8 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
}
@Override
public Iterable<Entry<V>> entries() {
return this;
}
@Override
public Iterator<Entry<V>> iterator() {
return new IteratorImpl();
}
@Override
public @k@[] keys() {
@k@[] outKeys = new @k@[size()];
int targetIx = 0;
for (int i = 0; i < values.length; ++i) {
if (values[i] != null) {
outKeys[targetIx++] = keys[i];
}
}
return outKeys;
}
@Override
public V[] values(Class<V> clazz) {
@SuppressWarnings("unchecked")
V[] outValues = (V[]) Array.newInstance(clazz, size());
int targetIx = 0;
for (V value : values) {
if (value != null) {
outValues[targetIx++] = value;
}
}
return outValues;
public Iterable<PrimitiveEntry<V>> entries() {
return entries;
}
@Override
@ -245,7 +228,8 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
final Iterator<Entry<V>> iter = @K@ObjectHashMap.this.iterator();
final PrimitiveIterator iter = new PrimitiveIterator();
@Override
public boolean hasNext() {
return iter.hasNext();
@ -319,6 +303,40 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
return true;
}
@Override
public boolean containsKey(Object key) {
return containsKey(objectToKey(key));
}
@Override
public V get(Object key) {
return get(objectToKey(key));
}
@Override
public V put(@O@ key, V value) {
return put(objectToKey(key), value);
}
@Override
public V remove(Object key) {
return remove(objectToKey(key));
}
@Override
public Set<@O@> keySet() {
return keySet;
}
@Override
public Set<Entry<@O@, V>> entrySet() {
return entrySet;
}
private @k@ objectToKey(Object key) {
return (@k@) ((@O@) key).@KEY_NUMBER_METHOD@();
}
/**
* Locates the index for the given key. This method probes using double hashing.
*
@ -447,7 +465,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
for (;;) {
if (values[index] == null) {
keys[index] = oldKey;
values[index] = toInternal(oldVal);
values[index] = oldVal;
break;
}
@ -458,10 +476,115 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
}
}
@Override
public String toString() {
if (isEmpty()) {
return "{}";
}
StringBuilder sb = new StringBuilder(4 * size);
sb.append('{');
boolean first = true;
for (int i = 0; i < values.length; ++i) {
V value = values[i];
if (value != null) {
if (!first) {
sb.append(", ");
}
sb.append(keyToString(keys[i])).append('=').append(value == this ? "(this Map)" :
toExternal(value));
first = false;
}
}
return sb.append('}').toString();
}
/**
* Iterator for traversing the entries in this map.
* Helper method called by {@link #toString()} in order to convert a single map key into a string.
* This is protected to allow subclasses to override the appearance of a given key.
*/
private final class IteratorImpl implements Iterator<Entry<V>>, Entry<V> {
protected String keyToString(@k@ key) {
return @O@.toString(key);
}
/**
* Set implementation for iterating over the entries of the map.
*/
private final class EntrySet extends AbstractSet<Entry<@O@, V>> {
@Override
public Iterator<Entry<@O@, V>> iterator() {
return new MapIterator();
}
@Override
public int size() {
return @K@ObjectHashMap.this.size();
}
}
/**
* Set implementation for iterating over the keys.
*/
private final class KeySet extends AbstractSet<@O@> {
@Override
public int size() {
return @K@ObjectHashMap.this.size();
}
@Override
public boolean contains(Object o) {
return @K@ObjectHashMap.this.containsKey(o);
}
@Override
public boolean remove(Object o) {
return @K@ObjectHashMap.this.remove(o) != null;
}
@Override
public boolean retainAll(Collection<?> retainedKeys) {
boolean changed = false;
for(Iterator<PrimitiveEntry<V>> iter = entries().iterator(); iter.hasNext(); ) {
PrimitiveEntry<V> entry = iter.next();
if (!retainedKeys.contains(entry.key())) {
changed = true;
iter.remove();
}
}
return changed;
}
@Override
public void clear() {
@K@ObjectHashMap.this.clear();
}
@Override
public Iterator<@O@> iterator() {
return new Iterator<@O@>() {
private final Iterator<Entry<@O@, V>> iter = entrySet.iterator();
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public @O@ next() {
return iter.next().getKey();
}
@Override
public void remove() {
iter.remove();
}
};
}
}
/**
* Iterator over primitive entries. Entry key/values are overwritten by each call to {@link #next()}.
*/
private final class PrimitiveIterator implements Iterator<PrimitiveEntry<V>>, PrimitiveEntry<V> {
private int prevIndex = -1;
private int nextIndex = -1;
private int entryIndex = -1;
@ -483,7 +606,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
}
@Override
public Entry<V> next() {
public PrimitiveEntry<V> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
@ -524,26 +647,68 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
}
}
@Override
public String toString() {
if (size == 0) {
return "{}";
/**
* Iterator used by the {@link Map} interface.
*/
private final class MapIterator implements Iterator<Entry<@O@, V>> {
private final PrimitiveIterator iter = new PrimitiveIterator();
@Override
public boolean hasNext() {
return iter.hasNext();
}
StringBuilder sb = new StringBuilder(4 * size);
for (int i = 0; i < values.length; ++i) {
V value = values[i];
if (value != null) {
sb.append(sb.length() == 0 ? "{" : ", ");
sb.append(keyToString(keys[i])).append('=').append(value == this ? "(this Map)" : value);
@Override
public Entry<@O@, V> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
iter.next();
return new MapEntry(iter.entryIndex);
}
@Override
public void remove() {
iter.remove();
}
return sb.append('}').toString();
}
/**
* Helper method called by {@link #toString()} in order to convert a single map key into a string.
* A single entry in the map.
*/
protected String keyToString(@k@ key) {
return @O@.toString(key);
final class MapEntry implements Entry<@O@, V> {
private final int entryIndex;
MapEntry(int entryIndex) {
this.entryIndex = entryIndex;
}
@Override
public @O@ getKey() {
verifyExists();
return keys[entryIndex];
}
@Override
public V getValue() {
verifyExists();
return toExternal(values[entryIndex]);
}
@Override
public V setValue(V value) {
verifyExists();
V prevValue = toExternal(values[entryIndex]);
values[entryIndex] = toInternal(value);
return prevValue;
}
private void verifyExists() {
if (values[entryIndex] == null) {
throw new IllegalStateException("The map entry has been removed");
}
}
}
}

View File

@ -14,21 +14,21 @@
*/
package io.netty.util.collection;
import java.util.Collection;
import java.util.Map;
/**
* Interface for a primitive map that uses {@code @k@}s as keys.
*
* @param <V> the value type stored in the map.
*/
public interface @K@ObjectMap<V> {
public interface @K@ObjectMap<V> extends Map<@O@, V> {
/**
* An Entry in the map.
* A primitive entry in the map, provided by the iterator from {@link #entries()}
*
* @param <V> the value type stored in the map.
*/
interface Entry<V> {
interface PrimitiveEntry<V> {
/**
* Gets the key for this entry.
*/
@ -62,11 +62,6 @@ public interface @K@ObjectMap<V> {
*/
V put(@k@ key, V value);
/**
* Puts all of the entries from the given map into this map.
*/
void putAll(@K@ObjectMap<V> sourceMap);
/**
* Removes the entry with the specified key.
*
@ -76,48 +71,14 @@ public interface @K@ObjectMap<V> {
V remove(@k@ key);
/**
* Returns the number of entries contained in this map.
* Gets an iterable to traverse over the primitive entries contained in this map. As an optimization,
* the {@link PrimitiveEntry}s returned by the {@link Iterator} may change as the {@link Iterator}
* progresses. The caller should not rely on {@link PrimitiveEntry} key/value stability.
*/
int size();
/**
* Indicates whether or not this map is empty (i.e {@link #size()} == {@code 0]).
*/
boolean isEmpty();
/**
* Clears all entries from this map.
*/
void clear();
Iterable<PrimitiveEntry<V>> entries();
/**
* Indicates whether or not this map contains a value for the specified key.
*/
boolean containsKey(@k@ key);
/**
* Indicates whether or not the map contains the specified value.
*/
boolean containsValue(V value);
/**
* Gets an iterable collection of the entries contained in this map.
*/
Iterable<Entry<V>> entries();
/**
* Gets the keys contained in this map.
*/
@k@[] keys();
/**
* Gets the values contained in this map.
*/
V[] values(Class<V> clazz);
/**
* Gets the values contatins in this map as a {@link Collection}.
*/
Collection<V> values();
}

View File

@ -18,9 +18,10 @@ import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
@ -87,6 +88,17 @@ public class @K@ObjectHashMapTest {
assertEquals(v, map.get(key));
}
@Test
public void putNewMappingShouldSucceed_mapApi() {
Value v = new Value("v");
@O@ key = (@O@)(@k@) 1;
assertNull(map.put(key, v));
assertEquals(1, map.size());
assertTrue(map.containsKey(key));
assertTrue(map.containsValue(v));
assertEquals(v, map.get(key));
}
@Test
public void putShouldReplaceValue() {
Value v1 = new Value("v1");
@ -103,6 +115,22 @@ public class @K@ObjectHashMapTest {
assertEquals(v2, map.get(key));
}
@Test
public void putShouldReplaceValue_mapApi() {
Value v1 = new Value("v1");
@O@ key = (@O@)(@k@) 1;
assertNull(map.put(key, v1));
// Replace the value.
Value v2 = new Value("v2");
assertSame(v1, map.put(key, v2));
assertEquals(1, map.size());
assertTrue(map.containsKey(key));
assertTrue(map.containsValue(v2));
assertEquals(v2, map.get(key));
}
@Test
public void putShouldGrowMap() {
for (@k@ key = 0; key < (@k@) 255; ++key) {
@ -115,6 +143,19 @@ public class @K@ObjectHashMapTest {
}
}
@Test
public void putShouldGrowMap_mapApi() {
for (@k@ key = 0; key < (@k@) 255; ++key) {
@O@ okey = (@O@) key;
Value v = new Value(@O@.toString(key));
assertNull(map.put(okey, v));
assertEquals(key + 1, map.size());
assertTrue(map.containsKey(okey));
assertTrue(map.containsValue(v));
assertEquals(v, map.get(okey));
}
}
@Test
public void negativeKeyShouldSucceed() {
Value v = new Value("v");
@ -123,12 +164,26 @@ public class @K@ObjectHashMapTest {
assertEquals(v, map.get((@k@) -3));
}
@Test
public void negativeKeyShouldSucceed_mapApi() {
Value v = new Value("v");
map.put((@O@)(@k@) -3, v);
assertEquals(1, map.size());
assertEquals(v, map.get((@O@)(@k@) -3));
}
@Test
public void removeMissingValueShouldReturnNull() {
assertNull(map.remove((@k@) 1));
assertEquals(0, map.size());
}
@Test
public void removeMissingValueShouldReturnNull_mapApi() {
assertNull(map.remove((@O@)(@k@) 1));
assertEquals(0, map.size());
}
@Test
public void removeShouldReturnPreviousValue() {
Value v = new Value("v");
@ -137,6 +192,14 @@ public class @K@ObjectHashMapTest {
assertSame(v, map.remove(key));
}
@Test
public void removeShouldReturnPreviousValue_mapApi() {
Value v = new Value("v");
@O@ key = (@O@)(@k@) 1;
map.put(key, v);
assertSame(v, map.remove(key));
}
/**
* This test is a bit internal-centric. We're just forcing a rehash to occur based on no longer
* having any FREE slots available. We do this by adding and then removing several keys up to
@ -160,24 +223,84 @@ public class @K@ObjectHashMapTest {
assertSame(v, map.get(key));
}
@Test
public void noFreeSlotsShouldRehash_mapApi() {
for (@k@ i = 0; i < 10; ++i) {
map.put(i, new Value(@O@.toString(i)));
// Now mark it as REMOVED so that size won't cause the rehash.
map.remove((@O@) i);
assertEquals(0, map.size());
}
// Now add an entry to force the rehash since no FREE slots are available in the map.
Value v = new Value("v");
@O@ key = (@O@)(@k@) 1;
map.put(key, v);
assertEquals(1, map.size());
assertSame(v, map.get(key));
}
@Test
public void putAllShouldSucceed() {
@K@ObjectHashMap<Value> other = new @K@ObjectHashMap<Value>();
@k@ k1 = 1;
@k@ k2 = 2;
@k@ k3 = 3;
Value v1 = new Value("v1");
Value v2 = new Value("v2");
Value v3 = new Value("v3");
map.put(k1, v1);
map.put(k2, v2);
map.put(k3, v3);
other.put(k1, v1);
other.put(k2, v2);
other.put(k3, v3);
@K@ObjectHashMap<Value> map2 = new @K@ObjectHashMap<Value>();
map2.putAll(map);
assertEquals(3, map2.size());
assertSame(v1, map2.get(k1));
assertSame(v2, map2.get(k2));
assertSame(v3, map2.get(k3));
map.putAll(other);
assertEquals(3, map.size());
assertSame(v1, map.get(k1));
assertSame(v2, map.get(k2));
assertSame(v3, map.get(k3));
}
@Test
public void putAllShouldSucceed_mapApi() {
@K@ObjectHashMap<Value> other = new @K@ObjectHashMap<Value>();
@O@ k1 = (@O@)(@k@) 1;
@O@ k2 = (@O@)(@k@) 2;
@O@ k3 = (@O@)(@k@) 3;
Value v1 = new Value("v1");
Value v2 = new Value("v2");
Value v3 = new Value("v3");
other.put(k1, v1);
other.put(k2, v2);
other.put(k3, v3);
map.putAll(other);
assertEquals(3, map.size());
assertSame(v1, map.get(k1));
assertSame(v2, map.get(k2));
assertSame(v3, map.get(k3));
}
@Test
public void putAllWithJavaMapShouldSucceed_mapApi() {
Map<@O@, Value> other = new HashMap<@O@, Value>();
@O@ k1 = (@O@)(@k@) 1;
@O@ k2 = (@O@)(@k@) 2;
@O@ k3 = (@O@)(@k@) 3;
Value v1 = new Value("v1");
Value v2 = new Value("v2");
Value v3 = new Value("v3");
other.put(k1, v1);
other.put(k2, v2);
other.put(k3, v3);
map.putAll(other);
assertEquals(3, map.size());
assertSame(v1, map.get(k1));
assertSame(v2, map.get(k2));
assertSame(v3, map.get(k3));
}
@Test
@ -201,6 +324,14 @@ public class @K@ObjectHashMapTest {
assertTrue(map.containsValue(null));
}
@Test
public void containsValueShouldFindNull_mapApi() {
map.put((@O@)(@k@) 1, new Value("v1"));
map.put((@O@)(@k@) 2, null);
map.put((@O@)(@k@) 3, new Value("v2"));
assertTrue(map.containsValue(null));
}
@Test
public void containsValueShouldFindInstance() {
Value v = new Value("v1");
@ -210,6 +341,15 @@ public class @K@ObjectHashMapTest {
assertTrue(map.containsValue(v));
}
@Test
public void containsValueShouldFindInstance_mapApi() {
Value v = new Value("v1");
map.put((@O@)(@k@) 1, new Value("v2"));
map.put((@O@)(@k@) 2, new Value("v3"));
map.put((@O@)(@k@) 3, v);
assertTrue(map.containsValue(v));
}
@Test
public void containsValueShouldFindEquivalentValue() {
map.put((@k@) 1, new Value("v1"));
@ -218,6 +358,14 @@ public class @K@ObjectHashMapTest {
assertTrue(map.containsValue(new Value("v2")));
}
@Test
public void containsValueShouldFindEquivalentValue_mapApi() {
map.put((@O@)(@k@) 1, new Value("v1"));
map.put((@O@)(@k@) 2, new Value("v2"));
map.put((@O@)(@k@) 3, new Value("v3"));
assertTrue(map.containsValue(new Value("v2")));
}
@Test
public void containsValueNotFindMissingValue() {
map.put((@k@) 1, new Value("v1"));
@ -226,6 +374,14 @@ public class @K@ObjectHashMapTest {
assertFalse(map.containsValue(new Value("v4")));
}
@Test
public void containsValueNotFindMissingValue_mapApi() {
map.put((@O@)(@k@) 1, new Value("v1"));
map.put((@O@)(@k@) 2, new Value("v2"));
map.put((@O@)(@k@) 3, new Value("v3"));
assertFalse(map.containsValue(new Value("v4")));
}
@Test
public void iteratorShouldTraverseEntries() {
@k@ k1 = 1;
@ -241,8 +397,8 @@ public class @K@ObjectHashMapTest {
map.remove(k4);
Set<@O@> found = new HashSet<@O@>();
for (@K@ObjectMap.Entry<Value> entry : map.entries()) {
assertTrue(found.add(entry.key()));
for (@K@ObjectMap.Entry<@O@, Value> entry : map.entrySet()) {
assertTrue(found.add(entry.getKey()));
}
assertEquals(3, found.size());
assertTrue(found.contains(k1));
@ -264,8 +420,8 @@ public class @K@ObjectHashMapTest {
map.put(k4, new Value("v4"));
map.remove(k4);
@k@[] keys = map.keys();
assertEquals(3, keys.length);
Set<@O@> keys = map.keySet();
assertEquals(3, keys.size());
Set<@O@> expected = new HashSet<@O@>();
expected.add(k1);
@ -298,25 +454,11 @@ public class @K@ObjectHashMapTest {
// Ensure values() return all values.
Set<Value> expected = new HashSet<Value>();
Set<Value> actual = new HashSet<Value>();
expected.add(v1);
expected.add(v2);
expected.add(v3);
Value[] valueArray = map.values(Value.class);
assertEquals(3, valueArray.length);
for (Value value : valueArray) {
assertTrue(actual.add(value));
}
assertEquals(expected, actual);
actual.clear();
Collection<Value> valueCollection = map.values();
assertEquals(3, valueCollection.size());
for (Value value : valueCollection) {
assertTrue(actual.add(value));
}
Set<Value> actual = new HashSet<Value>(map.values());
assertEquals(expected, actual);
}
@ -332,6 +474,18 @@ public class @K@ObjectHashMapTest {
}
}
@Test
public void mapShouldSupportHashingConflicts_mapApi() {
for (int mod = 0; mod < 10; ++mod) {
for (int sz = 1; sz <= 101; sz += 2) {
@K@ObjectHashMap<String> map = new @K@ObjectHashMap<String>(sz);
for (int i = 0; i < 100; ++i) {
map.put((@O@)(@k@)(i * mod), "");
}
}
}
}
@Test
public void hashcodeEqualsTest() {
@K@ObjectHashMap<@O@> map1 = new @K@ObjectHashMap<@O@>();
@ -345,23 +499,27 @@ public class @K@ObjectHashMapTest {
assertEquals(map1.hashCode(), map2.hashCode());
assertEquals(map1, map2);
// Remove one "middle" element, maps should now be non-equals.
@k@[] keys = map1.keys();
map2.remove(keys[50]);
Set<@O@> keys = map1.keySet();
@O@ removed = null;
Iterator<@O@> iter = keys.iterator();
for (int ix = 0; iter.hasNext() && ix < 50; ++ix) {
removed = iter.next();
}
map2.remove(removed);
assertFalse(map1.equals(map2));
// Put it back; will likely be in a different position, but maps will be equal again.
map2.put(keys[50], @O@.valueOf(map1.keys()[50]));
map2.put(removed, removed);
assertEquals(map1, map2);
assertEquals(map1.hashCode(), map2.hashCode());
// Make map2 have one extra element, will be non-equal.
map2.put((@k@) 100, (@k@) 100);
map2.put((@k@) 100, (@O@)(@k@) 100);
assertFalse(map1.equals(map2));
// Rebuild map2 with elements in a different order, again the maps should be equal.
// (These tests with same elements in different order also show that the hashCode
// function does not depend on the internal ordering of entries.)
map2.clear();
Arrays.sort(keys);
for (@k@ key : keys) {
map2.put(key, @O@.valueOf(key));
for (@O@ key : map1.keySet()) {
map2.put(key, key);
}
assertEquals(map1.hashCode(), map2.hashCode());
assertEquals(map1, map2);
@ -424,14 +582,14 @@ public class @K@ObjectHashMapTest {
assertEquals(goodMap.size(), map.size());
@O@[] goodKeys = goodMap.keySet().toArray(new @O@[goodMap.size()]);
Arrays.sort(goodKeys);
@k@ [] keys = map.keys();
@O@[] keys = map.keySet().toArray(new @O@[map.size()]);
Arrays.sort(keys);
for (int i = 0; i < goodKeys.length; ++i) {
assertEquals((@k@) goodKeys[i], keys[i]);
assertEquals(goodKeys[i], keys[i]);
}
// Finally drain the map.
for (@k@ key : map.keys()) {
for (@k@ key : keys) {
assertEquals(goodMap.remove(key), map.remove(key));
}
assertTrue(map.isEmpty());

View File

@ -28,6 +28,7 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
@ -291,8 +292,8 @@ final class EpollEventLoop extends SingleThreadEventLoop {
}
Collection<AbstractEpollChannel> array = new ArrayList<AbstractEpollChannel>(channels.size());
for (IntObjectMap.Entry<AbstractEpollChannel> entry: channels.entries()) {
array.add(entry.value());
for (AbstractEpollChannel channel: channels.values()) {
array.add(channel);
}
for (AbstractEpollChannel ch: array) {