Skip to content

Commit b63331e

Browse files
committed
HHH-18326 New utility collections based on instance identity
1 parent db96a7d commit b63331e

File tree

5 files changed

+831
-0
lines changed

5 files changed

+831
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.engine.spi;
6+
7+
/**
8+
* Contract for classes whose instances are uniquely identifiable through a simple {@code int} value,
9+
* and can be mapped via {@link org.hibernate.internal.util.collections.InstanceIdentityMap}.
10+
*/
11+
public interface InstanceIdentity {
12+
/**
13+
* Retrieve the unique identifier of this instance
14+
*
15+
* @return the unique instance identifier
16+
*/
17+
int $$_hibernate_getInstanceId();
18+
19+
/**
20+
* Set the value of the unique identifier for this instance
21+
*
22+
* @param instanceId the unique identifier value to set
23+
*/
24+
void $$_hibernate_setInstanceId(int instanceId);
25+
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.internal.util.collections;
6+
7+
import java.util.ArrayList;
8+
import java.util.Arrays;
9+
import java.util.Iterator;
10+
import java.util.NoSuchElementException;
11+
12+
/**
13+
* Array-like structures that organizes elements in {@link Page}s, automatically allocating
14+
* more as needed. Access to data via absolute index is efficient as it requires
15+
* a constant amount of operations.
16+
*
17+
* @param <E> the type of elements contained in the array
18+
*/
19+
public class AbstractPagedArray<E> {
20+
// It's important that capacity is a power of 2 to allow calculating page index and offset within the page
21+
// with simple division and modulo operations; also static final so JIT can inline these operations.
22+
private static final int PAGE_CAPACITY = 1 << 5; // 32, 16 key + value pairs
23+
24+
/**
25+
* Represents a page of {@link #PAGE_CAPACITY} objects in the overall array.
26+
*/
27+
protected static final class Page<E> {
28+
private final Object[] elements;
29+
private int lastNotEmptyOffset;
30+
31+
public Page() {
32+
elements = new Object[PAGE_CAPACITY];
33+
lastNotEmptyOffset = -1;
34+
}
35+
36+
/**
37+
* Clears the contents of the page.
38+
*/
39+
public void clear() {
40+
// We need to null out everything to prevent GC nepotism (see https://hibernate.atlassian.net/browse/HHH-19047)
41+
Arrays.fill( elements, 0, lastNotEmptyOffset + 1, null );
42+
lastNotEmptyOffset = -1;
43+
}
44+
45+
/**
46+
* Set the provided element at the specified offset.
47+
*
48+
* @param offset the offset in the page where to set the element
49+
* @param element the element to set
50+
* @return the previous element at {@code offset} if one existed, or {@code null}
51+
*/
52+
public E set(int offset, Object element) {
53+
if ( offset >= PAGE_CAPACITY ) {
54+
throw new IllegalArgumentException( "The required offset is beyond page capacity" );
55+
}
56+
final Object old = elements[offset];
57+
if ( element != null ) {
58+
if ( offset > lastNotEmptyOffset ) {
59+
lastNotEmptyOffset = offset;
60+
}
61+
}
62+
else if ( lastNotEmptyOffset == offset && old != null ) {
63+
// must search backward for the first not empty offset
64+
int i = offset;
65+
for ( ; i >= 0; i-- ) {
66+
if ( elements[i] != null ) {
67+
break;
68+
}
69+
}
70+
lastNotEmptyOffset = i;
71+
}
72+
elements[offset] = element;
73+
//noinspection unchecked
74+
return (E) old;
75+
}
76+
77+
/**
78+
* Get the element at the specified offset.
79+
*
80+
* @param offset the offset in the page where to set the element
81+
* @return the element at {@code index} if one existed, or {@code null}
82+
*/
83+
public E get(final int offset) {
84+
if ( offset >= PAGE_CAPACITY ) {
85+
throw new IllegalArgumentException( "The required offset is beyond page capacity" );
86+
}
87+
if ( offset > lastNotEmptyOffset ) {
88+
return null;
89+
}
90+
//noinspection unchecked
91+
return (E) elements[offset];
92+
}
93+
94+
int lastNotEmptyOffset() {
95+
return lastNotEmptyOffset;
96+
}
97+
}
98+
99+
protected final ArrayList<Page<E>> elementPages;
100+
101+
public AbstractPagedArray() {
102+
elementPages = new ArrayList<>();
103+
}
104+
105+
protected static int toPageIndex(final int index) {
106+
return index / PAGE_CAPACITY;
107+
}
108+
109+
protected static int toPageOffset(final int index) {
110+
return index % PAGE_CAPACITY;
111+
}
112+
113+
/**
114+
* Utility methods that retrieves an {@link Page} based on the absolute index in the array.
115+
*
116+
* @param index the absolute index of the array
117+
* @return the page corresponding to the provided index, or {@code null}
118+
*/
119+
protected Page<E> getPage(int index) {
120+
final int pageIndex = toPageIndex( index );
121+
if ( pageIndex < elementPages.size() ) {
122+
return elementPages.get( pageIndex );
123+
}
124+
return null;
125+
}
126+
127+
/**
128+
* Returns the element from the array at the specified index
129+
*
130+
* @param index the absolute index in the underlying array
131+
* @return the value contained in the array at the specified position, or {@code null}
132+
*/
133+
protected E get(int index) {
134+
final Page<E> page = getPage( index );
135+
return page != null ? page.get( toPageOffset( index ) ) : null;
136+
}
137+
138+
/**
139+
* Sets the specified index to the provided element
140+
*
141+
* @param index the absolute index in the underlying array
142+
* @param element the element to set
143+
* @return the value previously contained in the array at the specified position, or {@code null}
144+
*/
145+
protected E set(int index, E element) {
146+
final Page<E> page = getOrCreateEntryPage( index );
147+
return page.set( toPageOffset( index ), element );
148+
}
149+
150+
/**
151+
* Utility methods that retrieves or initializes a {@link Page} based on the absolute index in the array.
152+
*
153+
* @param index the absolute index of the array
154+
* @return the page corresponding to the provided index
155+
*/
156+
protected Page<E> getOrCreateEntryPage(int index) {
157+
final int pages = elementPages.size();
158+
final int pageIndex = toPageIndex( index );
159+
if ( pageIndex < pages ) {
160+
final Page<E> page = elementPages.get( pageIndex );
161+
if ( page != null ) {
162+
return page;
163+
}
164+
final Page<E> newPage = new Page<>();
165+
elementPages.set( pageIndex, newPage );
166+
return newPage;
167+
}
168+
elementPages.ensureCapacity( pageIndex + 1 );
169+
for ( int i = pages; i < pageIndex; i++ ) {
170+
elementPages.add( null );
171+
}
172+
final Page<E> page = new Page<>();
173+
elementPages.add( page );
174+
return page;
175+
}
176+
177+
public void clear() {
178+
for ( Page<E> entryPage : elementPages ) {
179+
entryPage.clear();
180+
}
181+
elementPages.clear();
182+
elementPages.trimToSize();
183+
}
184+
185+
protected abstract class PagedArrayIterator<T> implements Iterator<T> {
186+
int index; // current absolute index in the array
187+
boolean indexValid; // to avoid unnecessary next computation
188+
189+
@Override
190+
public boolean hasNext() {
191+
for ( int i = toPageIndex( index ); i < elementPages.size(); i++ ) {
192+
final Page<E> page = elementPages.get( i );
193+
if ( page != null ) {
194+
for ( int j = toPageOffset( index ); j <= page.lastNotEmptyOffset; j++ ) {
195+
if ( page.get( j ) != null ) {
196+
index = i * PAGE_CAPACITY + j;
197+
return indexValid = true;
198+
}
199+
}
200+
}
201+
}
202+
index = elementPages.size() * PAGE_CAPACITY;
203+
return false;
204+
}
205+
206+
protected int nextIndex() {
207+
if ( !indexValid && !hasNext() ) {
208+
throw new NoSuchElementException();
209+
}
210+
211+
indexValid = false;
212+
int lastReturnedIndex = index;
213+
index++;
214+
return lastReturnedIndex;
215+
}
216+
}
217+
}

0 commit comments

Comments
 (0)