• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Hyshmily / hotkey / 28848089351

07 Jul 2026 07:05AM UTC coverage: 90.138% (-0.3%) from 90.477%
28848089351

push

github

Hyshmily
refactor and test: refactor Lz4CacheCompressor and fix the test

Signed-off-by: Hyshmily <cxm8607@outlook.com>

1315 of 1527 branches covered (86.12%)

Branch coverage included in aggregate %.

17 of 18 new or added lines in 1 file covered. (94.44%)

19 existing lines in 1 file now uncovered.

3721 of 4060 relevant lines covered (91.65%)

4.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.24
/common/src/main/java/io/github/hyshmily/hotkey/cache/codec/Lz4CacheCompressor.java
1
/*
2
 * Copyright 2026 Hyshmily. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package io.github.hyshmily.hotkey.cache.codec;
17

18
import static java.nio.charset.StandardCharsets.UTF_8;
19

20
import io.github.hyshmily.hotkey.Internal;
21
import java.io.IOException;
22
import java.util.Arrays;
23
import net.jpountz.lz4.LZ4Compressor;
24
import net.jpountz.lz4.LZ4Exception;
25
import net.jpountz.lz4.LZ4Factory;
26
import net.jpountz.lz4.LZ4FastDecompressor;
27

28
/**
29
 * LZ4-based {@link CacheCompressor}. Uses the fastest available LZ4 instance.
30
 *
31
 * <p>Format:
32
 * <ul>
33
 *   <li>Flag {@code 0x00} = uncompressed String: {@code [0x00][UTF-8 bytes]}</li>
34
 *   <li>Flag {@code 0x01} = LZ4 compressed String:
35
 *       {@code [0x01][4-byte original-length LE][LZ4 payload]}</li>
36
 *   <li>Flag {@code 0x02} = LZ4 compressed byte[]:
37
 *       {@code [0x02][4-byte original-length LE][LZ4 payload]}</li>
38
 *   <li>Flag {@code 0x03} = uncompressed byte[]: {@code [0x03][raw bytes]}</li>
39
 * </ul>
40
 * Original length is bounds-checked during decompression to prevent resource
41
 * exhaustion attacks.
42
 */
43
@Internal
44
public class Lz4CacheCompressor implements CacheCompressor {
45

46
  private static final byte FLAG_RAW = 0;
47
  private static final byte FLAG_LZ4 = 1;
48
  private static final byte FLAG_LZ4_BYTES = 2;
49
  private static final byte FLAG_RAW_BYTES = 3;
50

51
  private final LZ4Compressor compressor;
52
  private final LZ4FastDecompressor decompressor;
53

54
  public Lz4CacheCompressor() {
2✔
55
    LZ4Factory factory = LZ4Factory.fastestInstance();
2✔
56
    this.compressor = factory.fastCompressor();
4✔
57
    this.decompressor = factory.fastDecompressor();
4✔
58
  }
1✔
59

60
  @Override
61
  public Object wrap(Object value) {
62
    if (value instanceof String s) return wrapString(s);
10✔
63
    if (value instanceof byte[] b) return wrapBytes(b);
10✔
64
    return value;
2✔
65
  }
66

67
  private byte[] wrapString(String s) {
68
    byte[] raw = s.getBytes(UTF_8);
4✔
69
    return process(raw, FLAG_RAW, FLAG_LZ4);
6✔
70
  }
71

72
  private byte[] wrapBytes(byte[] raw) {
73
    return process(raw, FLAG_RAW_BYTES, FLAG_LZ4_BYTES);
6✔
74
  }
75

76
  public byte[] process(byte[] raw, byte flagRawBytes, byte flagLz4Bytes) {
77
    if (raw.length < MIN_COMPRESS_LENGTH) {
4✔
78
      byte[] buf = new byte[raw.length + 1];
6✔
79
      buf[0] = flagRawBytes;
4✔
80
      System.arraycopy(raw, 0, buf, 1, raw.length);
7✔
81
      return buf;
2✔
82
    }
83
    return compress(raw, flagLz4Bytes);
5✔
84
  }
85

86
  private byte[] compress(byte[] raw, byte flag) {
87
    int maxLen = compressor.maxCompressedLength(raw.length);
6✔
88
    byte[] compressed = new byte[maxLen + 5];
5✔
89
    compressed[0] = flag;
4✔
90
    writeLen(compressed, raw.length);
4✔
91
    int len = compressor.compress(raw, 0, raw.length, compressed, 5, maxLen);
11✔
92
    return Arrays.copyOf(compressed, len + 5);
6✔
93
  }
94

95
  @Override
96
  public Object unwrap(Object stored) throws IOException {
97
    if (!(stored instanceof byte[] b)) return stored;
9✔
98
    if (b.length < 1) return stored;
4!
99

100
    return switch (b[0]) {
5!
101
      case FLAG_RAW -> new String(b, 1, b.length - 1, UTF_8);
11✔
102
      case FLAG_LZ4 -> new String(decompress(b), UTF_8);
8✔
103
      case FLAG_RAW_BYTES -> Arrays.copyOfRange(b, 1, b.length);
6✔
104
      case FLAG_LZ4_BYTES -> decompress(b);
4✔
NEW
105
      default -> stored;
×
106
    };
107
  }
108

109
  private byte[] decompress(byte[] compressed) throws IOException {
110
    if (compressed.length < 5) throw new IOException("Truncated LZ4 data");
9✔
111
    int originalLen = readLen(compressed);
3✔
112
    if (originalLen <= 0 || originalLen > 100_000_000) {
5!
113
      throw new IOException("Invalid decompressed length: " + originalLen);
×
114
    }
115
    byte[] restored = new byte[originalLen];
3✔
116
    try {
117
      decompressor.decompress(compressed, 5, restored, 0, originalLen);
9✔
118
    } catch (LZ4Exception e) {
×
119
      throw new IOException("LZ4 decompression failed", e);
×
120
    }
1✔
121
    return restored;
2✔
122
  }
123

124
  private static void writeLen(byte[] buf, int len) {
125
    buf[1] = (byte) len;
5✔
126
    buf[2] = (byte) (len >>> 8);
7✔
127
    buf[3] = (byte) (len >>> 16);
7✔
128
    buf[4] = (byte) (len >>> 24);
7✔
129
  }
1✔
130

131
  private static int readLen(byte[] buf) {
132
    return ((buf[1] & 0xFF) | ((buf[2] & 0xFF) << 8) | ((buf[3] & 0xFF) << 16) | ((buf[4] & 0xFF) << 24));
30✔
133
  }
134
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc