2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
17
package org.apache.solr.common.util;
19
import org.apache.solr.common.SolrDocument;
20
import org.apache.solr.common.SolrDocumentList;
22
import java.io.IOException;
23
import java.io.InputStream;
24
import java.io.OutputStream;
26
import java.nio.ByteBuffer;
29
* The class is designed to optimaly serialize/deserialize any supported types in Solr response. As we know there are only a limited type of
30
* items this class can do it with very minimal amount of payload and code. There are 15 known types and if there is an
31
* object in the object tree which does not fall into these types, It must be converted to one of these. Implement an
32
* ObjectResolver and pass it over It is expected that this class is used on both end of the pipes. The class has one
33
* read method and one write method for each of the datatypes
35
* Note -- Never re-use an instance of this class for more than one marshal or unmarshall operation. Always create a new
38
public class JavaBinCodec {
40
public static final byte
57
* this is a special tag signals an end. No value is associated with it
61
// types that combine tag + length (or other info) in a single byte
62
TAG_AND_LEN = (byte) (1 << 5),
63
STR = (byte) (1 << 5),
64
SINT = (byte) (2 << 5),
65
SLONG = (byte) (3 << 5),
66
ARR = (byte) (4 << 5), //
67
ORDERED_MAP = (byte) (5 << 5), // SimpleOrderedMap (a NamedList subclass, and more common)
68
NAMED_LST = (byte) (6 << 5), // NamedList
69
EXTERN_STRING = (byte) (7 << 5);
72
private static byte VERSION = 2;
73
private ObjectResolver resolver;
74
protected FastOutputStream daos;
76
public JavaBinCodec() {
79
public JavaBinCodec(ObjectResolver resolver) {
80
this.resolver = resolver;
83
public void marshal(Object nl, OutputStream os) throws IOException {
84
daos = FastOutputStream.wrap(os);
86
daos.writeByte(VERSION);
95
public Object unmarshal(InputStream is) throws IOException {
96
FastInputStream dis = FastInputStream.wrap(is);
97
version = dis.readByte();
98
if (version != VERSION) {
99
throw new RuntimeException("Invalid version (expected " + VERSION +
100
", but " + version + ") or the data in not in 'javabin' format");
106
public SimpleOrderedMap readOrderedMap(FastInputStream dis) throws IOException {
107
int sz = readSize(dis);
108
SimpleOrderedMap nl = new SimpleOrderedMap();
109
for (int i = 0; i < sz; i++) {
110
String name = (String) readVal(dis);
111
Object val = readVal(dis);
117
public NamedList readNamedList(FastInputStream dis) throws IOException {
118
int sz = readSize(dis);
119
NamedList nl = new NamedList();
120
for (int i = 0; i < sz; i++) {
121
String name = (String) readVal(dis);
122
Object val = readVal(dis);
128
public void writeNamedList(NamedList nl) throws IOException {
129
writeTag(nl instanceof SimpleOrderedMap ? ORDERED_MAP : NAMED_LST, nl.size());
130
for (int i = 0; i < nl.size(); i++) {
131
String name = nl.getName(i);
132
writeExternString(name);
133
Object val = nl.getVal(i);
138
public void writeVal(Object val) throws IOException {
139
if (writeKnownType(val)) {
143
if (resolver != null) {
144
tmpVal = resolver.resolve(val, this);
145
if (tmpVal == null) return; // null means the resolver took care of it fully
146
if (writeKnownType(tmpVal)) return;
150
writeVal(val.getClass().getName() + ':' + val.toString());
153
protected static final Object END_OBJ = new Object();
157
public Object readVal(FastInputStream dis) throws IOException {
158
tagByte = dis.readByte();
160
// if ((tagByte & 0xe0) == 0) {
161
// if top 3 bits are clear, this is a normal tag
163
// OK, try type + size in single byte
164
switch (tagByte >>> 5) {
168
return readSmallInt(dis);
170
return readSmallLong(dis);
172
return readArray(dis);
173
case ORDERED_MAP >>> 5:
174
return readOrderedMap(dis);
175
case NAMED_LST >>> 5:
176
return readNamedList(dis);
177
case EXTERN_STRING >>> 5:
178
return readExternString(dis);
185
return new Date(dis.readLong());
187
return dis.readInt();
191
return Boolean.FALSE;
193
return dis.readFloat();
195
return dis.readDouble();
197
return dis.readLong();
199
return dis.readByte();
201
return dis.readShort();
205
return readSolrDocument(dis);
207
return readSolrDocumentList(dis);
209
return readByteArray(dis);
211
return readIterator(dis);
216
throw new RuntimeException("Unknown type " + tagByte);
219
public boolean writeKnownType(Object val) throws IOException {
220
if (writePrimitive(val)) return true;
221
if (val instanceof NamedList) {
222
writeNamedList((NamedList) val);
225
if (val instanceof SolrDocumentList) { // SolrDocumentList is a List, so must come before List check
226
writeSolrDocumentList((SolrDocumentList) val);
229
if (val instanceof Collection) {
230
writeArray((Collection) val);
233
if (val instanceof Object[]) {
234
writeArray((Object[]) val);
237
if (val instanceof SolrDocument) {
238
//this needs special treatment to know which fields are to be written
239
if (resolver == null) {
240
writeSolrDocument((SolrDocument) val);
242
Object retVal = resolver.resolve(val, this);
243
if (retVal != null) {
244
if (retVal instanceof SolrDocument) {
245
writeSolrDocument((SolrDocument) retVal);
253
if (val instanceof Map) {
257
if (val instanceof Iterator) {
258
writeIterator((Iterator) val);
261
if (val instanceof Iterable) {
262
writeIterator(((Iterable) val).iterator());
268
public void writeTag(byte tag) throws IOException {
272
public void writeTag(byte tag, int size) throws IOException {
273
if ((tag & 0xe0) != 0) {
275
daos.writeByte(tag | size);
277
daos.writeByte(tag | 0x1f);
278
writeVInt(size - 0x1f, daos);
282
writeVInt(size, daos);
286
public void writeByteArray(byte[] arr, int offset, int len) throws IOException {
287
writeTag(BYTEARR, len);
288
daos.write(arr, offset, len);
291
public byte[] readByteArray(FastInputStream dis) throws IOException {
292
byte[] arr = new byte[readVInt(dis)];
297
public void writeSolrDocument(SolrDocument doc) throws IOException {
298
writeSolrDocument(doc, null);
301
public void writeSolrDocument(SolrDocument doc, Set<String> fields) throws IOException {
303
if (fields == null) {
304
count = doc.getFieldNames().size();
306
for (Map.Entry<String, Object> entry : doc) {
307
if (fields.contains(entry.getKey())) count++;
311
writeTag(ORDERED_MAP, count);
312
for (Map.Entry<String, Object> entry : doc) {
313
if (fields == null || fields.contains(entry.getKey())) {
314
String name = entry.getKey();
315
writeExternString(name);
316
Object val = entry.getValue();
322
public SolrDocument readSolrDocument(FastInputStream dis) throws IOException {
323
NamedList nl = (NamedList) readVal(dis);
324
SolrDocument doc = new SolrDocument();
325
for (int i = 0; i < nl.size(); i++) {
326
String name = nl.getName(i);
327
Object val = nl.getVal(i);
328
doc.setField(name, val);
333
public SolrDocumentList readSolrDocumentList(FastInputStream dis) throws IOException {
334
SolrDocumentList solrDocs = new SolrDocumentList();
335
List list = (List) readVal(dis);
336
solrDocs.setNumFound((Long) list.get(0));
337
solrDocs.setStart((Long) list.get(1));
338
solrDocs.setMaxScore((Float) list.get(2));
340
List l = (List) readVal(dis);
345
public void writeSolrDocumentList(SolrDocumentList docs)
347
writeTag(SOLRDOCLST);
348
List l = new ArrayList(3);
349
l.add(docs.getNumFound());
350
l.add(docs.getStart());
351
l.add(docs.getMaxScore());
356
public Map readMap(FastInputStream dis)
358
int sz = readVInt(dis);
359
Map m = new LinkedHashMap();
360
for (int i = 0; i < sz; i++) {
361
Object key = readVal(dis);
362
Object val = readVal(dis);
369
public void writeIterator(Iterator iter) throws IOException {
371
while (iter.hasNext()) {
372
writeVal(iter.next());
377
public List readIterator(FastInputStream fis) throws IOException {
378
ArrayList l = new ArrayList();
380
Object o = readVal(fis);
381
if (o == END_OBJ) break;
387
public void writeArray(List l) throws IOException {
388
writeTag(ARR, l.size());
389
for (int i = 0; i < l.size(); i++) {
394
public void writeArray(Collection coll) throws IOException {
395
writeTag(ARR, coll.size());
396
for (Object o : coll) {
402
public void writeArray(Object[] arr) throws IOException {
403
writeTag(ARR, arr.length);
404
for (int i = 0; i < arr.length; i++) {
410
public List readArray(FastInputStream dis) throws IOException {
411
int sz = readSize(dis);
412
ArrayList l = new ArrayList(sz);
413
for (int i = 0; i < sz; i++) {
420
* write the string as tag+length, with length being the number of UTF-8 bytes
422
public void writeStr(String s) throws IOException {
427
int end = s.length();
428
int maxSize = end * 4;
429
if (bytes == null || bytes.length < maxSize) bytes = new byte[maxSize];
431
for(int i=0;i<end;i++) {
432
final int code = (int) s.charAt(i);
435
bytes[upto++] = (byte) code;
436
else if (code < 0x800) {
437
bytes[upto++] = (byte) (0xC0 | (code >> 6));
438
bytes[upto++] = (byte)(0x80 | (code & 0x3F));
439
} else if (code < 0xD800 || code > 0xDFFF) {
440
bytes[upto++] = (byte)(0xE0 | (code >> 12));
441
bytes[upto++] = (byte)(0x80 | ((code >> 6) & 0x3F));
442
bytes[upto++] = (byte)(0x80 | (code & 0x3F));
445
// confirm valid high surrogate
446
if (code < 0xDC00 && (i < end-1)) {
447
int utf32 = (int) s.charAt(i+1);
448
// confirm valid low surrogate and write pair
449
if (utf32 >= 0xDC00 && utf32 <= 0xDFFF) {
450
utf32 = ((code - 0xD7C0) << 10) + (utf32 & 0x3FF);
452
bytes[upto++] = (byte)(0xF0 | (utf32 >> 18));
453
bytes[upto++] = (byte)(0x80 | ((utf32 >> 12) & 0x3F));
454
bytes[upto++] = (byte)(0x80 | ((utf32 >> 6) & 0x3F));
455
bytes[upto++] = (byte)(0x80 | (utf32 & 0x3F));
459
// replace unpaired surrogate or out-of-order low surrogate
460
// with substitution character
461
bytes[upto++] = (byte) 0xEF;
462
bytes[upto++] = (byte) 0xBF;
463
bytes[upto++] = (byte) 0xBD;
467
daos.write(bytes, 0, upto);
473
public String readStr(FastInputStream dis) throws IOException {
474
int sz = readSize(dis);
475
if (chars == null || chars.length < sz) chars = new char[sz];
476
if (bytes == null || bytes.length < sz) bytes = new byte[sz];
477
dis.readFully(bytes, 0, sz);
479
for (int i = 0; i < sz;) {
480
final int b = bytes[i++]&0xff;
485
} else if (b < 0xe0) {
486
ch = ((b&0x1f)<<6) + (bytes[i++]&0x3f);
487
} else if (b < 0xf0) {
488
ch = ((b&0xf)<<12) + ((bytes[i++]&0x3f)<<6) + (bytes[i++]&0x3f);
491
ch = ((b&0x7)<<18) + ((bytes[i++]&0x3f)<<12) + ((bytes[i++]&0x3f)<<6) + (bytes[i++]&0x3f);
494
// target is a character <= 0xFFFF
495
chars[outUpto++] = (char) ch;
497
// target is a character in range 0xFFFF - 0x10FFFF
498
final int chHalf = ch - 0x10000;
499
chars[outUpto++] = (char) ((chHalf >> 0xA) + 0xD800);
500
chars[outUpto++] = (char) ((chHalf & 0x3FF) + 0xDC00);
503
return new String(chars, 0, outUpto);
506
public void writeInt(int val) throws IOException {
508
int b = SINT | (val & 0x0f);
513
writeVInt(val >>> 4, daos);
524
public int readSmallInt(FastInputStream dis) throws IOException {
525
int v = tagByte & 0x0F;
526
if ((tagByte & 0x10) != 0)
527
v = (readVInt(dis) << 4) | v;
532
public void writeLong(long val) throws IOException {
533
if ((val & 0xff00000000000000L) == 0) {
534
int b = SLONG | ((int) val & 0x0f);
538
writeVLong(val >>> 4, daos);
543
daos.writeByte(LONG);
548
public long readSmallLong(FastInputStream dis) throws IOException {
549
long v = tagByte & 0x0F;
550
if ((tagByte & 0x10) != 0)
551
v = (readVLong(dis) << 4) | v;
555
public boolean writePrimitive(Object val) throws IOException {
557
daos.writeByte(NULL);
559
} else if (val instanceof String) {
560
writeStr((String) val);
562
} else if (val instanceof Integer) {
563
writeInt(((Integer) val).intValue());
565
} else if (val instanceof Long) {
566
writeLong(((Long) val).longValue());
568
} else if (val instanceof Float) {
569
daos.writeByte(FLOAT);
570
daos.writeFloat(((Float) val).floatValue());
572
} else if (val instanceof Date) {
573
daos.writeByte(DATE);
574
daos.writeLong(((Date) val).getTime());
576
} else if (val instanceof Boolean) {
577
if ((Boolean) val) daos.writeByte(BOOL_TRUE);
578
else daos.writeByte(BOOL_FALSE);
580
} else if (val instanceof Double) {
581
daos.writeByte(DOUBLE);
582
daos.writeDouble(((Double) val).doubleValue());
584
} else if (val instanceof Byte) {
585
daos.writeByte(BYTE);
586
daos.writeByte(((Byte) val).intValue());
588
} else if (val instanceof Short) {
589
daos.writeByte(SHORT);
590
daos.writeShort(((Short) val).intValue());
592
} else if (val instanceof byte[]) {
593
writeByteArray((byte[]) val, 0, ((byte[]) val).length);
595
}else if (val instanceof ByteBuffer) {
596
ByteBuffer buf = (ByteBuffer) val;
597
writeByteArray(buf.array(),buf.position(),buf.limit() - buf.position());
599
} else if (val == END_OBJ) {
607
public void writeMap(Map val)
609
writeTag(MAP, val.size());
610
for (Map.Entry entry : (Set<Map.Entry>) val.entrySet()) {
611
Object key = entry.getKey();
612
if (key instanceof String) {
613
writeExternString((String) key);
617
writeVal(entry.getValue());
622
public int readSize(FastInputStream in) throws IOException {
623
int sz = tagByte & 0x1f;
624
if (sz == 0x1f) sz += readVInt(in);
630
* Special method for variable length int (copied from lucene). Usually used for writing the length of a
631
* collection/array/map In most of the cases the length can be represented in one byte (length < 127) so it saves 3
637
* @throws IOException
639
public static void writeVInt(int i, FastOutputStream out) throws IOException {
640
while ((i & ~0x7F) != 0) {
641
out.writeByte((byte) ((i & 0x7f) | 0x80));
644
out.writeByte((byte) i);
648
* The counterpart for the above
652
* @return the int value
654
* @throws IOException
656
public static int readVInt(FastInputStream in) throws IOException {
657
byte b = in.readByte();
659
for (int shift = 7; (b & 0x80) != 0; shift += 7) {
661
i |= (b & 0x7F) << shift;
667
public static void writeVLong(long i, FastOutputStream out) throws IOException {
668
while ((i & ~0x7F) != 0) {
669
out.writeByte((byte) ((i & 0x7f) | 0x80));
672
out.writeByte((byte) i);
675
public static long readVLong(FastInputStream in) throws IOException {
676
byte b = in.readByte();
678
for (int shift = 7; (b & 0x80) != 0; shift += 7) {
680
i |= (long) (b & 0x7F) << shift;
685
private int stringsCount = 0;
686
private Map<String, Integer> stringsMap;
687
private List<String> stringsList;
689
public void writeExternString(String s) throws IOException {
694
Integer idx = stringsMap == null ? null : stringsMap.get(s);
695
if (idx == null) idx = 0;
696
writeTag(EXTERN_STRING, idx);
699
if (stringsMap == null) stringsMap = new HashMap<String, Integer>();
700
stringsMap.put(s, ++stringsCount);
705
public String readExternString(FastInputStream fis) throws IOException {
706
int idx = readSize(fis);
707
if (idx != 0) {// idx != 0 is the index of the extern string
708
return stringsList.get(idx - 1);
709
} else {// idx == 0 means it has a string value
710
String s = (String) readVal(fis);
711
if (stringsList == null) stringsList = new ArrayList<String>();
718
public static interface ObjectResolver {
719
public Object resolve(Object o, JavaBinCodec codec) throws IOException;