github.com/philpearl/plenc@v0.0.15/plenccodec/string.go (about)

     1  package plenccodec
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  	"unsafe"
     7  
     8  	"github.com/philpearl/plenc/plenccore"
     9  )
    10  
    11  // StringCodec is a codec for an string
    12  type StringCodec struct{}
    13  
    14  // size returns the number of bytes needed to encode a string
    15  func (StringCodec) size(ptr unsafe.Pointer) int {
    16  	return len(*(*string)(ptr))
    17  }
    18  
    19  // append encodes a string
    20  func (StringCodec) append(data []byte, ptr unsafe.Pointer) []byte {
    21  	s := *(*string)(ptr)
    22  	return append(data, s...)
    23  }
    24  
    25  // Read decodes a string
    26  func (StringCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) {
    27  	*(*string)(ptr) = string(data)
    28  	return len(data), nil
    29  }
    30  
    31  // New creates a pointer to a new string header
    32  func (StringCodec) New() unsafe.Pointer {
    33  	return unsafe.Pointer(new(string))
    34  }
    35  
    36  // WireType returns the wire type used to encode this type
    37  func (StringCodec) WireType() plenccore.WireType {
    38  	return plenccore.WTLength
    39  }
    40  
    41  // Omit indicates whether this field should be omitted
    42  func (StringCodec) Omit(ptr unsafe.Pointer) bool {
    43  	return len(*(*string)(ptr)) == 0
    44  }
    45  
    46  func (StringCodec) Descriptor() Descriptor {
    47  	return Descriptor{Type: FieldTypeString}
    48  }
    49  
    50  func (c StringCodec) Size(ptr unsafe.Pointer, tag []byte) int {
    51  	l := c.size(ptr)
    52  	if len(tag) > 0 {
    53  		l += len(tag) + plenccore.SizeVarUint(uint64(l))
    54  	}
    55  	return l
    56  }
    57  
    58  func (c StringCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte {
    59  	if len(tag) != 0 {
    60  		data = append(data, tag...)
    61  		data = plenccore.AppendVarUint(data, uint64(c.size(ptr)))
    62  	}
    63  	return c.append(data, ptr)
    64  }
    65  
    66  // BytesCodec is a codec for a byte slice
    67  type BytesCodec struct{}
    68  
    69  // size returns the number of bytes needed to encode a string
    70  func (BytesCodec) size(ptr unsafe.Pointer) int {
    71  	return len(*(*[]byte)(ptr))
    72  }
    73  
    74  // append encodes a []byte
    75  func (BytesCodec) append(data []byte, ptr unsafe.Pointer) []byte {
    76  	s := *(*[]byte)(ptr)
    77  	return append(data, s...)
    78  }
    79  
    80  // Read decodes a []byte
    81  func (BytesCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) {
    82  	// really must copy this data to be safe from the underlying buffer changing
    83  	// later
    84  	*(*[]byte)(ptr) = append([]byte(nil), data...)
    85  	return len(data), nil
    86  }
    87  
    88  // New creates a pointer to a new bool
    89  func (c BytesCodec) New() unsafe.Pointer {
    90  	return unsafe.Pointer(new([]byte))
    91  }
    92  
    93  // WireType returns the wire type used to encode this type
    94  func (c BytesCodec) WireType() plenccore.WireType {
    95  	return plenccore.WTLength
    96  }
    97  
    98  // Omit indicates whether this field should be omitted
    99  func (c BytesCodec) Omit(ptr unsafe.Pointer) bool {
   100  	return len(*(*[]byte)(ptr)) == 0
   101  }
   102  
   103  func (BytesCodec) Descriptor() Descriptor {
   104  	return Descriptor{Type: FieldTypeString}
   105  }
   106  
   107  func (c BytesCodec) Size(ptr unsafe.Pointer, tag []byte) int {
   108  	l := c.size(ptr)
   109  	if len(tag) != 0 {
   110  		l += len(tag) + plenccore.SizeVarUint(uint64(l))
   111  	}
   112  	return l
   113  }
   114  
   115  func (c BytesCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte {
   116  	if len(tag) != 0 {
   117  		data = append(data, tag...)
   118  		data = plenccore.AppendVarUint(data, uint64(c.size(ptr)))
   119  	}
   120  	return c.append(data, ptr)
   121  }
   122  
   123  type Interner interface {
   124  	WithInterning() Codec
   125  }
   126  
   127  type InternedStringCodec struct {
   128  	sync.Mutex
   129  	strings unsafe.Pointer
   130  	StringCodec
   131  }
   132  
   133  func (c *InternedStringCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) {
   134  	p := atomic.LoadPointer(&c.strings)
   135  	m := *(*map[string]string)((unsafe.Pointer)(&p))
   136  
   137  	s, ok := m[string(data)]
   138  	if !ok {
   139  		s = c.addString(data)
   140  	}
   141  
   142  	*(*string)(ptr) = s
   143  	return len(data), nil
   144  }
   145  
   146  func (c *InternedStringCodec) addString(data []byte) string {
   147  	c.Lock()
   148  	defer c.Unlock()
   149  	p := atomic.LoadPointer(&c.strings)
   150  	m := *(*map[string]string)((unsafe.Pointer)(&p))
   151  
   152  	s, ok := m[string(data)]
   153  	if !ok {
   154  		// We completely replace the map with a new one, so the old one can
   155  		// be read without locks. We're expecting the number of different values
   156  		// to be small, so that this is a reasonable thing to do.
   157  		m2 := make(map[string]string, len(m)+1)
   158  		for k, v := range m {
   159  			m2[k] = v
   160  		}
   161  		s = string(data)
   162  		m2[s] = s
   163  
   164  		atomic.StorePointer(&c.strings, *(*unsafe.Pointer)(unsafe.Pointer(&m2)))
   165  	}
   166  	return s
   167  }
   168  
   169  func (c StringCodec) WithInterning() Codec {
   170  	ic := &InternedStringCodec{}
   171  	m := map[string]string{}
   172  	atomic.StorePointer(&ic.strings, *(*unsafe.Pointer)(unsafe.Pointer(&m)))
   173  	return ic
   174  }