blob: b5ae8f9793a468d214cbcf6d6962a24e84f399e2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.kafka.common.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* A memory-efficient hash multiset which tracks the order of insertion of elements.
* See org.apache.kafka.common.utils.ImplicitLinkedHashCollection for implementation details.
*
* This class is a multi-set because it allows multiple elements to be inserted that are
* equal to each other.
*
* We use reference equality when adding elements to the set. A new element A can
* be added if there is no existing element B such that A == B. If an element B
* exists such that A.equals(B), A will still be added.
*
* When deleting an element A from the set, we will try to delete the element B such
* that A == B. If no such element can be found, we will try to delete an element B
* such that A.equals(B).
*
* contains() and find() are unchanged from the base class-- they will look for element
* based on object equality, not reference equality.
*
* This multiset does not allow null elements. It does not have internal synchronization.
*/
public class ImplicitLinkedHashMultiCollection<E extends ImplicitLinkedHashCollection.Element>
extends ImplicitLinkedHashCollection<E> {
public ImplicitLinkedHashMultiCollection() {
super(0);
}
public ImplicitLinkedHashMultiCollection(int expectedNumElements) {
super(expectedNumElements);
}
public ImplicitLinkedHashMultiCollection(Iterator<E> iter) {
super(iter);
}
/**
* Adds a new element to the appropriate place in the elements array.
*
* @param newElement The new element to add.
* @param addElements The elements array.
* @return The index at which the element was inserted, or INVALID_INDEX
* if the element could not be inserted.
*/
@Override
int addInternal(Element newElement, Element[] addElements) {
int slot = slot(addElements, newElement);
for (int seen = 0; seen < addElements.length; seen++) {
Element element = addElements[slot];
if (element == null) {
addElements[slot] = newElement;
return slot;
}
if (element == newElement) {
return INVALID_INDEX;
}
slot = (slot + 1) % addElements.length;
}
throw new RuntimeException("Not enough hash table slots to add a new element.");
}
/**
* Find an element matching an example element.
*
* @param key The element to match.
*
* @return The match index, or INVALID_INDEX if no match was found.
*/
@Override
int findElementToRemove(Object key) {
if (key == null || size() == 0) {
return INVALID_INDEX;
}
int slot = slot(elements, key);
int bestSlot = INVALID_INDEX;
for (int seen = 0; seen < elements.length; seen++) {
Element element = elements[slot];
if (element == null) {
return bestSlot;
}
if (key == element) {
return slot;
} else if (key.equals(element)) {
bestSlot = slot;
}
slot = (slot + 1) % elements.length;
}
return INVALID_INDEX;
}
/**
* Returns all of the elements e in the collection such that
* key.equals(e) and key.hashCode() == e.hashCode().
*
* @param key The element to match.
*
* @return All of the matching elements.
*/
final public List<E> findAll(E key) {
if (key == null || size() == 0) {
return Collections.<E>emptyList();
}
ArrayList<E> results = new ArrayList<>();
int slot = slot(elements, key);
for (int seen = 0; seen < elements.length; seen++) {
Element element = elements[slot];
if (element == null) {
break;
}
if (key.equals(element)) {
@SuppressWarnings("unchecked")
E result = (E) elements[slot];
results.add(result);
}
slot = (slot + 1) % elements.length;
}
return results;
}
}