Skip to content

Commit 67e26d5

Browse files
committed
HHH-18326 New utility collections based on instance identity
1 parent bd557ef commit 67e26d5

File tree

5 files changed

+638
-1
lines changed

5 files changed

+638
-1
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: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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 org.checkerframework.checker.nullness.qual.NonNull;
8+
import org.checkerframework.checker.nullness.qual.Nullable;
9+
import org.hibernate.engine.spi.InstanceIdentity;
10+
11+
import java.util.Collection;
12+
import java.util.Map;
13+
import java.util.Objects;
14+
import java.util.Set;
15+
import java.util.function.BiConsumer;
16+
import java.util.stream.Collectors;
17+
18+
/**
19+
* Utility collection backed by {@link PagedArray} that takes advantage of {@link InstanceIdentity}'s
20+
* unique identifier to store objects and provide {@link Map}-like functionalities.
21+
* <p>
22+
* Methods accessing / modifying the map with {@link Object} typed parameters will need
23+
* to type check against the instance identity interface which might be inefficient,
24+
* so it's recommended to use the position (int) based variant of those methods.
25+
* <p>
26+
* Iterating through the whole map is most efficient with {@link #forEach}, and since
27+
* we simply iterate the underlying array, it's also concurrent and reentrant safe.
28+
*/
29+
public class InstanceIdentityMap<K extends InstanceIdentity, V> implements Map<K, V> {
30+
private static final class Entry<K, V> implements Map.Entry<K, V> {
31+
private final K key;
32+
private final V value;
33+
34+
public Entry(K key, V value) {
35+
this.key = key;
36+
this.value = value;
37+
}
38+
39+
@Override
40+
public K getKey() {
41+
return key;
42+
}
43+
44+
@Override
45+
public V getValue() {
46+
return value;
47+
}
48+
49+
@Override
50+
public V setValue(V value) {
51+
throw new UnsupportedOperationException();
52+
}
53+
54+
@Override
55+
public boolean equals(Object o) {
56+
if ( o == this ) {
57+
return true;
58+
}
59+
60+
return o instanceof Map.Entry<?, ?> e
61+
&& Objects.equals( key, e.getKey() )
62+
&& Objects.equals( value, e.getValue() );
63+
}
64+
65+
@Override
66+
public int hashCode() {
67+
int result = key.hashCode();
68+
result = 31 * result + Objects.hashCode( value );
69+
return result;
70+
}
71+
}
72+
73+
private final PagedArray<Entry<K, V>> backingArray;
74+
75+
public InstanceIdentityMap() {
76+
backingArray = new PagedArray<>();
77+
}
78+
79+
@Override
80+
public int size() {
81+
return backingArray.size();
82+
}
83+
84+
@Override
85+
public boolean isEmpty() {
86+
return backingArray.isEmpty();
87+
}
88+
89+
90+
/**
91+
* Returns {@code true} if this map contains a mapping for the specified instance id.
92+
*
93+
* @param instanceId instance id whose presence in this map is to be tested
94+
* @param key key instance to double-check instance equality
95+
* @return {@code true} if this map contains a mapping for the specified instance id
96+
* @implNote This method accesses the backing array with the provided instance id, but performs an instance
97+
* equality check ({@code ==}) with the provided key to ensure it corresponds to the mapped one
98+
*/
99+
public boolean containsKey(int instanceId, K key) {
100+
return get( instanceId, key ) != null;
101+
}
102+
103+
/**
104+
* @inheritDoc
105+
* @implNote This only works for {@link InstanceIdentity} keys, and it's inefficient
106+
* since we need to do a type check. Prefer using {@link #containsKey(int, K)}.
107+
*/
108+
@Override
109+
public boolean containsKey(Object key) {
110+
if ( key instanceof InstanceIdentity instance ) {
111+
return containsKey( instance.$$_hibernate_getInstanceId(), (K) key );
112+
}
113+
throw new ClassCastException( "Provided key does not support instance identity" );
114+
}
115+
116+
@Override
117+
public boolean containsValue(Object value) {
118+
for ( V v : values() ) {
119+
if ( Objects.equals( value, v ) ) {
120+
return true;
121+
}
122+
}
123+
return false;
124+
}
125+
126+
/**
127+
* Returns the value to which the specified instance id is mapped, or {@code null} if this map
128+
* contains no mapping for the instance id.
129+
*
130+
* @param instanceId the instance id whose associated value is to be returned
131+
* @param key key instance to double-check instance equality
132+
* @return the value to which the specified instance id is mapped,
133+
* or {@code null} if this map contains no mapping for the instance id
134+
* @implNote This method accesses the backing array with the provided instance id, but performs an instance
135+
* equality check ({@code ==}) with the provided key to ensure it corresponds to the mapped one
136+
*/
137+
public @Nullable V get(int instanceId, K key) {
138+
if ( instanceId < 0 ) {
139+
return null;
140+
}
141+
142+
final Entry<K, V> entry = backingArray.get( instanceId );
143+
return entry != null && entry.getKey() == key ? entry.getValue() : null;
144+
}
145+
146+
/**
147+
* @inheritDoc
148+
* @implNote This only works for {@link InstanceIdentity} keys, and it's inefficient
149+
* since we need to do a type check. Prefer using {@link #get(int, K)}.
150+
*/
151+
@Override
152+
public @Nullable V get(Object key) {
153+
if ( key instanceof InstanceIdentity instance ) {
154+
//noinspection unchecked
155+
return get( instance.$$_hibernate_getInstanceId(), (K) instance );
156+
}
157+
throw new ClassCastException( "Provided key does not support instance identity" );
158+
}
159+
160+
@Override
161+
public @Nullable V put(K key, V value) {
162+
final int instanceId = key.$$_hibernate_getInstanceId();
163+
final Entry<K, V> old = backingArray.set( instanceId, new Entry<>( key, value ) );
164+
return old != null ? old.getValue() : null;
165+
}
166+
167+
/**
168+
* Removes the mapping for an instance id from this map if it is present (optional operation).
169+
*
170+
* @param instanceId instance id whose mapping is to be removed from the map
171+
* @param key key instance to double-check instance equality
172+
* @return the previous value associated with {@code instanceId}, or {@code null} if there was no mapping for it.
173+
* @implNote This method accesses the backing array with the provided instance id, but performs an instance
174+
* equality check ({@code ==}) with the provided key to ensure it corresponds to the mapped one
175+
*/
176+
public @Nullable V remove(int instanceId, K key) {
177+
final Entry<K, V> old = backingArray.remove( instanceId );
178+
if ( old != null ) {
179+
// Check that the provided instance really matches with the key contained in the map
180+
if ( old.getKey() != key ) {
181+
// If it doesn't, reset the array value to the old entry
182+
backingArray.set( instanceId, old );
183+
}
184+
return old.getValue();
185+
}
186+
return null;
187+
}
188+
189+
/**
190+
* @inheritDoc
191+
* @implNote This only works for {@link InstanceIdentity} keys, and it's inefficient
192+
* since we need to do a type check. Prefer using {@link #remove(int, K)}.
193+
*/
194+
@Override
195+
public @Nullable V remove(Object key) {
196+
if ( key instanceof InstanceIdentity instance ) {
197+
//noinspection unchecked
198+
return remove( instance.$$_hibernate_getInstanceId(), (K) key );
199+
}
200+
throw new IllegalArgumentException( "Provided key does not support instance identity" );
201+
}
202+
203+
@Override
204+
public void putAll(Map<? extends K, ? extends V> m) {
205+
for ( Map.Entry<? extends K, ? extends V> entry : m.entrySet() ) {
206+
put( entry.getKey(), entry.getValue() );
207+
}
208+
}
209+
210+
@Override
211+
public @Nullable V putIfAbsent(K key, V value) {
212+
V v = get( key.$$_hibernate_getInstanceId(), key );
213+
if ( v == null ) {
214+
v = put( key, value );
215+
}
216+
return v;
217+
}
218+
219+
@Override
220+
public void clear() {
221+
backingArray.clear();
222+
}
223+
224+
/**
225+
* Returns a read-only Set view of the keys contained in this map.
226+
*/
227+
@Override
228+
public @NonNull Set<K> keySet() {
229+
return backingArray.stream().map( Entry::getKey ).collect( Collectors.toUnmodifiableSet() );
230+
}
231+
232+
/**
233+
* Returns a read-only Collection view of the values contained in this map.
234+
*/
235+
@Override
236+
public @NonNull Collection<V> values() {
237+
return backingArray.stream().map( Entry::getValue ).collect( Collectors.toUnmodifiableSet() );
238+
}
239+
240+
/**
241+
* Returns a read-only Set view of the mappings contained in this map.
242+
*/
243+
@Override
244+
public @NonNull Set<Map.Entry<K, V>> entrySet() {
245+
return backingArray.stream().collect( Collectors.toUnmodifiableSet() );
246+
}
247+
248+
@Override
249+
public void forEach(BiConsumer<? super K, ? super V> action) {
250+
backingArray.forEach( element -> action.accept( element.getKey(), element.getValue() ) );
251+
}
252+
253+
public Map.Entry<K, V>[] toArray() {
254+
//noinspection unchecked
255+
return backingArray.stream().toArray( Map.Entry[]::new );
256+
}
257+
}

0 commit comments

Comments
 (0)