forked from kelindar/bitmap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
codec.go
191 lines (157 loc) · 4.55 KB
/
codec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Copyright (c) Roman Atachiants and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
package bitmap
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"unsafe"
)
// FromBytes reads a bitmap from a byte buffer without copying the buffer.
func FromBytes(buffer []byte) (out Bitmap) {
switch {
case len(buffer) == 0:
return nil
case len(buffer)%8 != 0:
panic(fmt.Sprintf("bitmap: buffer length expected to be multiple of 8, was %d", len(buffer)))
}
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&out))
hdr.Len = len(buffer) >> 3
hdr.Cap = hdr.Len
hdr.Data = uintptr(unsafe.Pointer(&(buffer)[0]))
return out
}
// ToBytes converts the bitmap to binary representation without copying the underlying
// data. The output buffer should not be modified, since it would also change the bitmap.
func (dst *Bitmap) ToBytes() (out []byte) {
if len(*dst) == 0 {
return nil
}
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&out))
hdr.Len = len(*dst) * 8
hdr.Cap = hdr.Len
hdr.Data = uintptr(unsafe.Pointer(&(*dst)[0]))
return out
}
// ReadFrom reads the bitmap from the reader.
func ReadFrom(r io.Reader) (Bitmap, error) {
var output Bitmap
_, err := output.ReadFrom(r)
return output, err
}
// WriteTo writes the bitmap to a specified writer.
func (dst *Bitmap) WriteTo(w io.Writer) (int64, error) {
buffer := dst.ToBytes()
// Write the header into the stream
var header [4]byte
binary.BigEndian.PutUint32(header[:4], uint32(len(buffer)))
n1, err := w.Write(header[:4])
if err != nil {
return int64(n1), err
}
// Write the buffer into the stream
n2, err := w.Write(buffer)
if err != nil {
return int64(n2), err
}
return int64(n1 + n2), err
}
// ReadFrom reads data from r until EOF or error. The return value n is the number of
// bytes read. Any error except EOF encountered during the read is also returned.
func (dst *Bitmap) ReadFrom(r io.Reader) (int64, error) {
var header [4]byte
if n, err := io.ReadFull(r, header[:]); err != nil {
return int64(n), err
}
// If bitmap is too small, create one of the required size
if size := int(binary.BigEndian.Uint32(header[:4])) / 8; size > len(*dst) {
*dst = make(Bitmap, size)
}
// Read into the buffer
buffer := dst.ToBytes()
n, err := io.ReadFull(r, buffer)
return int64(n + 4), err
}
// Clone clones the bitmap. If a destination bitmap is provided, the bitmap will be
// cloned inside, otherwise a new Bitmap will be allocated and returned
func (dst Bitmap) Clone(into *Bitmap) Bitmap {
if into == nil {
newm := make(Bitmap, len(dst))
into = &newm
}
max := maxlen(*into, dst, nil)
into.grow(max - 1)
copy(*into, dst)
return (*into)[:len(dst)]
}
// Clear clears the bitmap and resizes it to zero.
func (dst *Bitmap) Clear() {
for i := range *dst {
(*dst)[i] = 0
}
*dst = (*dst)[:0]
}
// MarshalJSON returns encoded string representation for the bitmap
func (dst Bitmap) MarshalJSON() ([]byte, error) {
var sb strings.Builder
for i := len(dst) - 1; i >= 0; i-- {
// convert each uint64 into 16 * 4-bit hexadecimal character
writeHexdecimal(&sb, dst[i], true)
}
return json.Marshal(sb.String())
}
// writeHexdecimal write the hexdecimal representation for given value in buffer
func writeHexdecimal(sb *strings.Builder, value uint64, pad bool) {
maxLen := 16 // 64 bits / 4
hexadecimal := strings.ToUpper(strconv.FormatUint(value, 16))
hexaLen := len(hexadecimal)
if !pad || hexaLen == maxLen {
sb.WriteString(hexadecimal)
return
}
// Add padding
for i := hexaLen; i < maxLen; i++ {
sb.WriteString("0")
}
sb.WriteString(hexadecimal)
}
// UnmarshalJSON decodes the received bytes and loads it to bitmap object
func (dst *Bitmap) UnmarshalJSON(data []byte) (err error) {
var str string
if data == nil {
*dst = make(Bitmap, 0)
return
}
if err := json.Unmarshal(data, &str); err != nil {
return err
}
mp, err := fromHex(str)
if err != nil {
return err
}
*dst = mp
return nil
}
// fromHex reads a hexadecimal string and converts it to bitmap, character at index 0 is the most significant
func fromHex(hexString string) (Bitmap, error) {
bytes, err := hex.DecodeString(hexString)
switch {
case err != nil:
return nil, err
case len(bytes) == 0:
return nil, nil
}
// reverse bytes to maintain bytes significance order (least significant = hexString tail = list head)
for l, r := 0, len(bytes)-1; l < r; l, r = l+1, r-1 {
bytes[l], bytes[r] = bytes[r], bytes[l]
}
for len(bytes)%8 != 0 {
bytes = append(bytes, 0)
}
return FromBytes(bytes), nil
}