Skip to content

Commit

Permalink
Allow null values
Browse files Browse the repository at this point in the history
  • Loading branch information
snazy committed Aug 16, 2018
1 parent c48f580 commit bdd9c67
Show file tree
Hide file tree
Showing 10 changed files with 571 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2014-2018 Real Logic Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.agrona.collections;

import org.agrona.generation.DoNotSub;

/**
* Variation of {@link Int2ObjectHashMap} that allows {@code null} values.
*
* @param <V> type of values stored in the {@link java.util.Map}
*/
public class Int2NullableObjectHashMap<V> extends Int2ObjectHashMap<V>
{
public Int2NullableObjectHashMap()
{
}

public Int2NullableObjectHashMap(
@DoNotSub final int initialCapacity,
final float loadFactor)
{
super(initialCapacity, loadFactor);
}

/**
* Construct a new map allowing a configuration for initial capacity and load factor.
* @param initialCapacity for the backing array
* @param loadFactor limit for resizing on puts
* @param shouldAvoidAllocation should allocation be avoided by caching iterators and map entries.
*/
public Int2NullableObjectHashMap(
@DoNotSub final int initialCapacity,
final float loadFactor,
final boolean shouldAvoidAllocation)
{
super(initialCapacity, loadFactor, shouldAvoidAllocation);
}

/**
* Copy construct a new map from an existing one.
*
* @param mapToCopy for construction.
*/
public Int2NullableObjectHashMap(final Int2ObjectHashMap<V> mapToCopy)
{
super(mapToCopy);
}

@Override
protected Object mapNullValue(final Object value)
{
return value == null ? NullReference.INSTANCE : value;
}

@Override
protected V unmapNullValue(final Object value)
{
return value == NullReference.INSTANCE ? null : (V)value;
}
}
63 changes: 43 additions & 20 deletions agrona/src/main/java/org/agrona/collections/Int2ObjectHashMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,12 @@ public boolean containsKey(final int key)
public boolean containsValue(final Object value)
{
boolean found = false;
if (null != value)
final Object val = mapNullValue(value);
if (null != val)
{
for (final Object v : values)
{
if (value.equals(v))
if (val.equals(v))
{
found = true;
break;
Expand All @@ -215,8 +216,13 @@ public V get(final Object key)
* @param key for indexing the {@link Map}
* @return the value if found otherwise null
*/
@SuppressWarnings("unchecked")
public V get(final int key)
{
return unmapNullValue(getMapped(key));
}

@SuppressWarnings("unchecked")
protected V getMapped(final int key)
{
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(key, mask);
Expand Down Expand Up @@ -247,7 +253,7 @@ public V get(final int key)
*/
public V computeIfAbsent(final int key, final IntFunction<? extends V> mappingFunction)
{
V value = get(key);
V value = getMapped(key);
if (value == null)
{
value = mappingFunction.apply(key);
Expand All @@ -256,6 +262,10 @@ public V computeIfAbsent(final int key, final IntFunction<? extends V> mappingFu
put(key, value);
}
}
else
{
value = unmapNullValue(value);
}

return value;
}
Expand All @@ -278,7 +288,8 @@ public V put(final Integer key, final V value)
@SuppressWarnings("unchecked")
public V put(final int key, final V value)
{
requireNonNull(value, "Value cannot be null");
final V val = (V)mapNullValue(value);
requireNonNull(val, "Value cannot be null");

V oldValue = null;
@DoNotSub final int mask = values.length - 1;
Expand All @@ -301,14 +312,14 @@ public V put(final int key, final V value)
keys[index] = key;
}

values[index] = value;
values[index] = val;

if (size > resizeThreshold)
{
increaseCapacity();
}

return oldValue;
return unmapNullValue(oldValue);
}

/**
Expand Down Expand Up @@ -346,7 +357,7 @@ public V remove(final int key)
index = ++index & mask;
}

return (V)value;
return unmapNullValue(value);
}

/**
Expand Down Expand Up @@ -438,7 +449,7 @@ public String toString()
while (true)
{
entryIterator.next();
sb.append(entryIterator.getIntKey()).append('=').append(entryIterator.getValue());
sb.append(entryIterator.getIntKey()).append('=').append(unmapNullValue(entryIterator.getValue()));
if (!entryIterator.hasNext())
{
return sb.append('}').toString();
Expand Down Expand Up @@ -475,7 +486,7 @@ public boolean equals(final Object o)
if (null != thisValue)
{
final Object thatValue = that.get(keys[i]);
if (!thisValue.equals(thatValue))
if (!thisValue.equals(mapNullValue(thatValue)))
{
return false;
}
Expand Down Expand Up @@ -504,6 +515,16 @@ public boolean equals(final Object o)
return result;
}

protected Object mapNullValue(final Object value)
{
return value;
}

protected V unmapNullValue(final Object value)
{
return (V)value;
}

/**
* Primitive specialised version of {@link #replace(Object, Object)}
*
Expand Down Expand Up @@ -534,7 +555,7 @@ public V replace(final int key, final V value)
public boolean replace(final int key, final V oldValue, final V newValue)
{
final Object curValue = get(key);
if (curValue == null || !Objects.equals(curValue, oldValue))
if (curValue == null || !Objects.equals(unmapNullValue(curValue), oldValue))
{
return false;
}
Expand Down Expand Up @@ -735,8 +756,9 @@ public void clear()
public boolean contains(final Object o)
{
final Entry entry = (Entry)o;
final V value = get(entry.getKey());
return value != null && value.equals(entry.getValue());
final int key = ((Integer)entry.getKey()).intValue();
final V value = getMapped(key);
return value != null && value.equals(mapNullValue(entry.getValue()));
}
}

Expand Down Expand Up @@ -843,7 +865,7 @@ public V next()
{
findNext();

return (V)values[position()];
return unmapNullValue(values[position()]);
}
}

Expand Down Expand Up @@ -902,7 +924,7 @@ public V setValue(final V value)

@DoNotSub public int hashCode()
{
return Integer.hashCode(getIntKey()) ^ v.hashCode();
return Integer.hashCode(getIntKey()) ^ (v != null ? v.hashCode() : 0);
}

@DoNotSub public boolean equals(final Object o)
Expand All @@ -914,8 +936,8 @@ public V setValue(final V value)

final Map.Entry e = (Entry)o;

return (e.getKey() != null && e.getValue() != null) &&
(e.getKey().equals(k) && e.getValue().equals(v));
return (e.getKey() != null && e.getKey().equals(k)) &&
((e.getValue() == null && v == null) || e.getValue().equals(v));
}

public String toString()
Expand All @@ -937,12 +959,13 @@ public int getIntKey()

public V getValue()
{
return (V)values[position()];
return unmapNullValue(values[position()]);
}

public V setValue(final V value)
{
requireNonNull(value);
final V val = (V)mapNullValue(value);
requireNonNull(val, "Value cannot be null");

if (!this.isPositionValid)
{
Expand All @@ -951,7 +974,7 @@ public V setValue(final V value)

@DoNotSub final int pos = position();
final Object oldValue = values[pos];
values[pos] = value;
values[pos] = val;

return (V)oldValue;
}
Expand Down
31 changes: 31 additions & 0 deletions agrona/src/main/java/org/agrona/collections/NullReference.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2014-2018 Real Logic Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.agrona.collections;

public final class NullReference
{
public static final NullReference INSTANCE = new NullReference();

public int hashCode()
{
return 0;
}

public boolean equals(final Object obj)
{
return obj == this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2014-2018 Real Logic Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.agrona.collections;

/**
* Variation of {@link Object2ObjectHashMap} that allows {@code null} values.
*/
public class Object2NullableObjectHashMap<K, V> extends Object2ObjectHashMap<K, V>
{
public Object2NullableObjectHashMap()
{
}

public Object2NullableObjectHashMap(
final int initialCapacity,
final float loadFactor)
{
super(initialCapacity, loadFactor);
}

/**
* @param initialCapacity for the map to override {@link #MIN_CAPACITY}
* @param loadFactor for the map to override {@link Hashing#DEFAULT_LOAD_FACTOR}.
* @param shouldAvoidAllocation should allocation be avoided by caching iterators and map entries.
*/
public Object2NullableObjectHashMap(
final int initialCapacity,
final float loadFactor,
final boolean shouldAvoidAllocation)
{
super(initialCapacity, loadFactor, shouldAvoidAllocation);
}

@Override
protected Object mapNullValue(final Object value)
{
return value == null ? NullReference.INSTANCE : value;
}

@Override
protected V unmapNullValue(final Object value)
{
return value == NullReference.INSTANCE ? null : (V)value;
}
}
Loading

0 comments on commit bdd9c67

Please sign in to comment.