Skip to content

Commit 458de91

Browse files
authored
Make BytesReference an interface (#48171)
BytesReference is currently an abstract class which is extended by various implementations. This makes it very difficult to use the delegation pattern. The implication of this is that our releasable BytesReference is a PagedBytesReference type and cannot be used as a generic releasable bytes reference that delegates to any reference type. This commit makes BytesReference an interface and introduces an AbstractBytesReference for common functionality.
1 parent 1ef8dc4 commit 458de91

File tree

24 files changed

+586
-347
lines changed

24 files changed

+586
-347
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/enrich/NamedPolicy.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import org.elasticsearch.common.ParseField;
2222
import org.elasticsearch.common.ParsingException;
23-
import org.elasticsearch.common.bytes.BytesArray;
2423
import org.elasticsearch.common.bytes.BytesReference;
2524
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
2625
import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -60,7 +59,7 @@ private static void declareParserOptions(ConstructingObjectParser<?, ?> parser)
6059
parser.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> {
6160
XContentBuilder builder = XContentBuilder.builder(p.contentType().xContent());
6261
builder.copyCurrentStructure(p);
63-
return BytesArray.bytes(builder);
62+
return BytesReference.bytes(builder);
6463
}, QUERY_FIELD);
6564
parser.declareStringArray(ConstructingObjectParser.constructorArg(), INDICES_FIELD);
6665
parser.declareString(ConstructingObjectParser.constructorArg(), MATCH_FIELD_FIELD);

client/rest-high-level/src/main/java/org/elasticsearch/client/ilm/IndexLifecycleExplainResponse.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import org.elasticsearch.common.ParseField;
2323
import org.elasticsearch.common.Strings;
24-
import org.elasticsearch.common.bytes.BytesArray;
2524
import org.elasticsearch.common.bytes.BytesReference;
2625
import org.elasticsearch.common.unit.TimeValue;
2726
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
@@ -86,7 +85,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject {
8685
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> {
8786
XContentBuilder builder = JsonXContent.contentBuilder();
8887
builder.copyCurrentStructure(p);
89-
return BytesArray.bytes(builder);
88+
return BytesReference.bytes(builder);
9089
}, STEP_INFO_FIELD);
9190
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> PhaseExecutionInfo.parse(p, ""),
9291
PHASE_EXECUTION_INFO);

client/rest-high-level/src/test/java/org/elasticsearch/client/enrich/GetPolicyResponseTests.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
package org.elasticsearch.client.enrich;
2020

2121
import org.elasticsearch.client.AbstractResponseTestCase;
22-
import org.elasticsearch.common.bytes.BytesArray;
2322
import org.elasticsearch.common.bytes.BytesReference;
2423
import org.elasticsearch.common.xcontent.XContentBuilder;
2524
import org.elasticsearch.common.xcontent.XContentParser;
@@ -80,7 +79,7 @@ private static EnrichPolicy createRandomEnrichPolicy(XContentType xContentType){
8079
try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
8180
builder.startObject();
8281
builder.endObject();
83-
BytesReference querySource = BytesArray.bytes(builder);
82+
BytesReference querySource = BytesReference.bytes(builder);
8483
return new EnrichPolicy(
8584
randomAlphaOfLength(4),
8685
randomBoolean() ? new EnrichPolicy.QuerySource(querySource, xContentType) : null,

modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/ByteBufBytesReference.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020

2121
import io.netty.buffer.ByteBuf;
2222
import org.apache.lucene.util.BytesRef;
23+
import org.elasticsearch.common.bytes.AbstractBytesReference;
2324
import org.elasticsearch.common.bytes.BytesReference;
2425
import org.elasticsearch.common.io.stream.StreamInput;
2526

2627
import java.io.IOException;
2728
import java.io.OutputStream;
2829
import java.nio.charset.StandardCharsets;
2930

30-
final class ByteBufBytesReference extends BytesReference {
31+
final class ByteBufBytesReference extends AbstractBytesReference {
3132

3233
private final ByteBuf buffer;
3334
private final int length;

plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/ByteBufUtils.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.netty.buffer.Unpooled;
2424
import org.apache.lucene.util.BytesRef;
2525
import org.apache.lucene.util.BytesRefIterator;
26+
import org.elasticsearch.common.bytes.AbstractBytesReference;
2627
import org.elasticsearch.common.bytes.BytesReference;
2728
import org.elasticsearch.common.io.stream.StreamInput;
2829

@@ -72,7 +73,7 @@ static BytesReference toBytesReference(final ByteBuf buffer) {
7273
return new ByteBufBytesReference(buffer, buffer.readableBytes());
7374
}
7475

75-
private static class ByteBufBytesReference extends BytesReference {
76+
private static class ByteBufBytesReference extends AbstractBytesReference {
7677

7778
private final ByteBuf buffer;
7879
private final int length;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.common.bytes;
20+
21+
import org.apache.lucene.util.BytesRef;
22+
import org.apache.lucene.util.BytesRefIterator;
23+
import org.elasticsearch.common.io.stream.StreamInput;
24+
import org.elasticsearch.common.xcontent.XContentBuilder;
25+
26+
import java.io.EOFException;
27+
import java.io.IOException;
28+
import java.io.InputStream;
29+
import java.io.OutputStream;
30+
import java.util.function.ToIntBiFunction;
31+
32+
public abstract class AbstractBytesReference implements BytesReference {
33+
34+
private Integer hash = null; // we cache the hash of this reference since it can be quite costly to re-calculated it
35+
36+
@Override
37+
public int getInt(int index) {
38+
return (get(index) & 0xFF) << 24 | (get(index + 1) & 0xFF) << 16 | (get(index + 2) & 0xFF) << 8 | get(index + 3) & 0xFF;
39+
}
40+
41+
@Override
42+
public int indexOf(byte marker, int from) {
43+
final int to = length();
44+
for (int i = from; i < to; i++) {
45+
if (get(i) == marker) {
46+
return i;
47+
}
48+
}
49+
return -1;
50+
}
51+
52+
@Override
53+
public StreamInput streamInput() throws IOException {
54+
return new MarkSupportingStreamInputWrapper(this);
55+
}
56+
57+
@Override
58+
public void writeTo(OutputStream os) throws IOException {
59+
final BytesRefIterator iterator = iterator();
60+
BytesRef ref;
61+
while ((ref = iterator.next()) != null) {
62+
os.write(ref.bytes, ref.offset, ref.length);
63+
}
64+
}
65+
66+
@Override
67+
public String utf8ToString() {
68+
return toBytesRef().utf8ToString();
69+
}
70+
71+
@Override
72+
public BytesRefIterator iterator() {
73+
return new BytesRefIterator() {
74+
BytesRef ref = length() == 0 ? null : toBytesRef();
75+
@Override
76+
public BytesRef next() throws IOException {
77+
BytesRef r = ref;
78+
ref = null; // only return it once...
79+
return r;
80+
}
81+
};
82+
}
83+
84+
@Override
85+
public boolean equals(Object other) {
86+
if (this == other) {
87+
return true;
88+
}
89+
if (other instanceof BytesReference) {
90+
final BytesReference otherRef = (BytesReference) other;
91+
if (length() != otherRef.length()) {
92+
return false;
93+
}
94+
return compareIterators(this, otherRef, (a, b) ->
95+
a.bytesEquals(b) ? 0 : 1 // this is a call to BytesRef#bytesEquals - this method is the hot one in the comparison
96+
) == 0;
97+
}
98+
return false;
99+
}
100+
101+
@Override
102+
public int hashCode() {
103+
if (hash == null) {
104+
final BytesRefIterator iterator = iterator();
105+
BytesRef ref;
106+
int result = 1;
107+
try {
108+
while ((ref = iterator.next()) != null) {
109+
for (int i = 0; i < ref.length; i++) {
110+
result = 31 * result + ref.bytes[ref.offset + i];
111+
}
112+
}
113+
} catch (IOException ex) {
114+
throw new AssertionError("wont happen", ex);
115+
}
116+
return hash = result;
117+
} else {
118+
return hash.intValue();
119+
}
120+
}
121+
122+
@Override
123+
public int compareTo(final BytesReference other) {
124+
return compareIterators(this, other, BytesRef::compareTo);
125+
}
126+
127+
/**
128+
* Compares the two references using the given int function.
129+
*/
130+
private static int compareIterators(final BytesReference a, final BytesReference b, final ToIntBiFunction<BytesRef, BytesRef> f) {
131+
try {
132+
// we use the iterators since it's a 0-copy comparison where possible!
133+
final long lengthToCompare = Math.min(a.length(), b.length());
134+
final BytesRefIterator aIter = a.iterator();
135+
final BytesRefIterator bIter = b.iterator();
136+
BytesRef aRef = aIter.next();
137+
BytesRef bRef = bIter.next();
138+
if (aRef != null && bRef != null) { // do we have any data?
139+
aRef = aRef.clone(); // we clone since we modify the offsets and length in the iteration below
140+
bRef = bRef.clone();
141+
if (aRef.length == a.length() && bRef.length == b.length()) { // is it only one array slice we are comparing?
142+
return f.applyAsInt(aRef, bRef);
143+
} else {
144+
for (int i = 0; i < lengthToCompare;) {
145+
if (aRef.length == 0) {
146+
aRef = aIter.next().clone(); // must be non null otherwise we have a bug
147+
}
148+
if (bRef.length == 0) {
149+
bRef = bIter.next().clone(); // must be non null otherwise we have a bug
150+
}
151+
final int aLength = aRef.length;
152+
final int bLength = bRef.length;
153+
final int length = Math.min(aLength, bLength); // shrink to the same length and use the fast compare in lucene
154+
aRef.length = bRef.length = length;
155+
// now we move to the fast comparison - this is the hot part of the loop
156+
int diff = f.applyAsInt(aRef, bRef);
157+
aRef.length = aLength;
158+
bRef.length = bLength;
159+
160+
if (diff != 0) {
161+
return diff;
162+
}
163+
advance(aRef, length);
164+
advance(bRef, length);
165+
i += length;
166+
}
167+
}
168+
}
169+
// One is a prefix of the other, or, they are equal:
170+
return a.length() - b.length();
171+
} catch (IOException ex) {
172+
throw new AssertionError("can not happen", ex);
173+
}
174+
}
175+
176+
private static void advance(final BytesRef ref, final int length) {
177+
assert ref.length >= length : " ref.length: " + ref.length + " length: " + length;
178+
assert ref.offset+length < ref.bytes.length || (ref.offset+length == ref.bytes.length && ref.length-length == 0)
179+
: "offset: " + ref.offset + " ref.bytes.length: " + ref.bytes.length + " length: " + length + " ref.length: " + ref.length;
180+
ref.length -= length;
181+
ref.offset += length;
182+
}
183+
184+
/**
185+
* Instead of adding the complexity of {@link InputStream#reset()} etc to the actual impl
186+
* this wrapper builds it on top of the BytesReferenceStreamInput which is much simpler
187+
* that way.
188+
*/
189+
private static final class MarkSupportingStreamInputWrapper extends StreamInput {
190+
// can't use FilterStreamInput it needs to reset the delegate
191+
private final BytesReference reference;
192+
private BytesReferenceStreamInput input;
193+
private int mark = 0;
194+
195+
private MarkSupportingStreamInputWrapper(BytesReference reference) throws IOException {
196+
this.reference = reference;
197+
this.input = new BytesReferenceStreamInput(reference.iterator(), reference.length());
198+
}
199+
200+
@Override
201+
public byte readByte() throws IOException {
202+
return input.readByte();
203+
}
204+
205+
@Override
206+
public void readBytes(byte[] b, int offset, int len) throws IOException {
207+
input.readBytes(b, offset, len);
208+
}
209+
210+
@Override
211+
public int read(byte[] b, int off, int len) throws IOException {
212+
return input.read(b, off, len);
213+
}
214+
215+
@Override
216+
public void close() throws IOException {
217+
input.close();
218+
}
219+
220+
@Override
221+
public int read() throws IOException {
222+
return input.read();
223+
}
224+
225+
@Override
226+
public int available() throws IOException {
227+
return input.available();
228+
}
229+
230+
@Override
231+
protected void ensureCanReadBytes(int length) throws EOFException {
232+
input.ensureCanReadBytes(length);
233+
}
234+
235+
@Override
236+
public void reset() throws IOException {
237+
input = new BytesReferenceStreamInput(reference.iterator(), reference.length());
238+
input.skip(mark);
239+
}
240+
241+
@Override
242+
public boolean markSupported() {
243+
return true;
244+
}
245+
246+
@Override
247+
public void mark(int readLimit) {
248+
// readLimit is optional it only guarantees that the stream remembers data upto this limit but it can remember more
249+
// which we do in our case
250+
this.mark = input.getOffset();
251+
}
252+
253+
@Override
254+
public long skip(long n) throws IOException {
255+
return input.skip(n);
256+
}
257+
}
258+
259+
@Override
260+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
261+
BytesRef bytes = toBytesRef();
262+
return builder.value(bytes.bytes, bytes.offset, bytes.length);
263+
}
264+
}

server/src/main/java/org/elasticsearch/common/bytes/ByteBufferReference.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
* changed, those changes will not be reflected in this reference. Any changes to the underlying data in the
3232
* byte buffer will be reflected in this reference.
3333
*/
34-
public class ByteBufferReference extends BytesReference {
34+
public class ByteBufferReference extends AbstractBytesReference {
3535

3636
private final ByteBuffer buffer;
3737
private final int length;

server/src/main/java/org/elasticsearch/common/bytes/BytesArray.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import java.util.Objects;
2525

26-
public final class BytesArray extends BytesReference {
26+
public final class BytesArray extends AbstractBytesReference {
2727

2828
public static final BytesArray EMPTY = new BytesArray(BytesRef.EMPTY_BYTES, 0, 0);
2929
private final byte[] bytes;

0 commit comments

Comments
 (0)