@@ -44,7 +44,7 @@ public final class KeyPrefixContainerCodec
4444 private static final String KEY_DELIMITER = "_" ;
4545 private static final byte [] KEY_DELIMITER_BYTES = KEY_DELIMITER .getBytes (UTF_8 );
4646 private static final ByteBuffer KEY_DELIMITER_BUFFER = ByteBuffer .wrap (KEY_DELIMITER_BYTES ).asReadOnlyBuffer ();
47- private static final int LONG_SERIALIZED_SIZE = KEY_DELIMITER_BYTES .length + Long .BYTES ;
47+ public static final int LONG_SERIALIZED_SIZE = KEY_DELIMITER_BYTES .length + Long .BYTES ;
4848
4949 public static Codec <KeyPrefixContainer > get () {
5050 return INSTANCE ;
@@ -102,53 +102,59 @@ public KeyPrefixContainer fromCodecBuffer(@Nonnull CodecBuffer buffer) throws Co
102102 final int startPosition = byteBuffer .position ();
103103 final int delimiterLength = KEY_DELIMITER_BYTES .length ;
104104
105- // Only support decoding when we have both version and containerId
106- if (totalLength >= 2 * (delimiterLength + Long .BYTES )) {
107- // Extract keyPrefix (everything except last 2 delimiters + 2 longs)
108- int keyPrefixLength = totalLength - 2 * delimiterLength - 2 * Long .BYTES ;
109- byteBuffer .position (startPosition );
110- String keyPrefix = decodeStringFromBuffer (byteBuffer , keyPrefixLength );
105+ // We expect: keyPrefix + delimiter + version(8 bytes) + delimiter + containerId(8 bytes)
106+ final int minimumLength = delimiterLength + Long .BYTES + delimiterLength + Long .BYTES ;
111107
112- // Skip delimiter and read version
113- byteBuffer .position (startPosition + keyPrefixLength + delimiterLength );
114- long version = byteBuffer .getLong ();
115-
116- // Skip delimiter and read containerId
117- byteBuffer .position (startPosition + keyPrefixLength + delimiterLength + Long .BYTES + delimiterLength );
118- long containerId = byteBuffer .getLong ();
108+ if (totalLength < minimumLength ) {
109+ throw new CodecException ("Buffer too small to contain all required fields." );
110+ }
119111
120- return KeyPrefixContainer .get (keyPrefix , version , containerId );
112+ int keyPrefixLength = totalLength - 2 * delimiterLength - 2 * Long .BYTES ;
113+ if (keyPrefixLength < 0 ) {
114+ throw new CodecException ("Invalid buffer format: negative key prefix length" );
121115 }
122116
123- // For backwards compatibility during transition, treat anything else as keyPrefix only
124- // This should not be used in production as it cannot handle keys with underscores
125117 byteBuffer .position (startPosition );
126- String keyPrefix = decodeStringFromBuffer (byteBuffer , totalLength );
127- return KeyPrefixContainer .get (keyPrefix , -1 , -1 );
118+ byteBuffer .limit (startPosition + keyPrefixLength );
119+ String keyPrefix = decodeStringFromBuffer (byteBuffer );
120+ byteBuffer .limit (startPosition + totalLength );
121+
122+ byteBuffer .position (startPosition + keyPrefixLength );
123+ for (int i = 0 ; i < delimiterLength ; i ++) {
124+ if (byteBuffer .get () != KEY_DELIMITER_BYTES [i ]) {
125+ throw new CodecException ("Expected delimiter after keyPrefix at position " +
126+ (startPosition + keyPrefixLength ));
127+ }
128+ }
129+ long version = byteBuffer .getLong ();
130+ for (int i = 0 ; i < delimiterLength ; i ++) {
131+ if (byteBuffer .get () != KEY_DELIMITER_BYTES [i ]) {
132+ throw new CodecException ("Expected delimiter after version at position " +
133+ (startPosition + keyPrefixLength + delimiterLength + Long .BYTES ));
134+ }
135+ }
136+ long containerId = byteBuffer .getLong ();
137+
138+ return KeyPrefixContainer .get (keyPrefix , version , containerId );
128139 }
129140
130- /**
131- * Decode string from ByteBuffer without copying to intermediate byte array.
132- * Uses CharsetDecoder for efficient decoding.
133- */
134- private String decodeStringFromBuffer (ByteBuffer buffer , int length ) throws CodecException {
135- if (length == 0 ) {
141+ private static String decodeStringFromBuffer (ByteBuffer buffer ) {
142+ if (buffer .remaining () == 0 ) {
136143 return "" ;
137144 }
138145
139- try {
140- ByteBuffer slice = buffer .duplicate ();
141- slice .limit (slice .position () + length );
142-
146+ final byte [] bytes ;
147+ if (buffer .hasArray ()) {
148+ int offset = buffer .arrayOffset () + buffer .position ();
149+ int length = buffer .remaining ();
150+ bytes = new byte [length ];
151+ System .arraycopy (buffer .array (), offset , bytes , 0 , length );
143152 buffer .position (buffer .position () + length );
144-
145- CharsetDecoder decoder = UTF_8 .newDecoder ();
146- CharBuffer charBuffer = decoder .decode (slice );
147- return charBuffer .toString ();
148-
149- } catch (CharacterCodingException e ) {
150- throw new CodecException ("Failed to decode UTF-8 string from buffer" , e );
153+ } else {
154+ bytes = new byte [buffer .remaining ()];
155+ buffer .get (bytes );
151156 }
157+ return new String (bytes , UTF_8 );
152158 }
153159
154160 @ Override
@@ -157,7 +163,7 @@ public byte[] toPersistedFormat(KeyPrefixContainer keyPrefixContainer) {
157163 "Null object can't be converted to byte array." );
158164 byte [] keyPrefixBytes = keyPrefixContainer .getKeyPrefix ().getBytes (UTF_8 );
159165
160- //Prefix seek can be done only with keyPrefix. In that case, we can
166+ // Prefix seek can be done only with keyPrefix. In that case, we can
161167 // expect the version and the containerId to be undefined.
162168 if (keyPrefixContainer .getKeyVersion () != -1 ) {
163169 keyPrefixBytes = ArrayUtils .addAll (keyPrefixBytes , KEY_DELIMITER_BYTES );
@@ -176,32 +182,23 @@ public byte[] toPersistedFormat(KeyPrefixContainer keyPrefixContainer) {
176182
177183 @ Override
178184 public KeyPrefixContainer fromPersistedFormat (byte [] rawData ) {
179- int totalLength = rawData .length ;
180- int delimiterLength = KEY_DELIMITER_BYTES .length ;
181-
182- // Only support decoding when we have both version and containerId
183- if (totalLength >= 2 * (delimiterLength + Long .BYTES )) {
184- // Extract keyPrefix (everything except last 2 delimiters + 2 longs)
185- int keyPrefixLength = totalLength - 2 * delimiterLength - 2 * Long .BYTES ;
186- String keyPrefix = new String (ArrayUtils .subarray (rawData , 0 , keyPrefixLength ), UTF_8 );
187-
188- // Read version
189- int versionStart = keyPrefixLength + delimiterLength ;
190- byte [] versionBytes = ArrayUtils .subarray (rawData , versionStart , versionStart + Long .BYTES );
191- long version = ByteBuffer .wrap (versionBytes ).getLong ();
192-
193- // Read containerId
194- int containerIdStart = versionStart + Long .BYTES + delimiterLength ;
195- byte [] containerIdBytes = ArrayUtils .subarray (rawData , containerIdStart , containerIdStart + Long .BYTES );
196- long containerId = ByteBuffer .wrap (containerIdBytes ).getLong ();
197-
198- return KeyPrefixContainer .get (keyPrefix , version , containerId );
199- }
200-
201- // For backwards compatibility during transition, treat anything else as keyPrefix only
202- // This should not be used in production as it cannot handle keys with underscores
203- String keyPrefix = new String (rawData , UTF_8 );
204- return KeyPrefixContainer .get (keyPrefix , -1 , -1 );
185+ // When reading from byte[], we can always expect to have the key, version
186+ // and version parts in the byte array.
187+ byte [] keyBytes = ArrayUtils .subarray (rawData ,
188+ 0 , rawData .length - Long .BYTES * 2 - 2 );
189+ String keyPrefix = new String (keyBytes , UTF_8 );
190+
191+ // Second 8 bytes is the key version.
192+ byte [] versionBytes = ArrayUtils .subarray (rawData ,
193+ rawData .length - Long .BYTES * 2 - 1 ,
194+ rawData .length - Long .BYTES - 1 );
195+ long version = ByteBuffer .wrap (versionBytes ).getLong ();
196+
197+ // Last 8 bytes is the containerId.
198+ long containerIdFromDB = ByteBuffer .wrap (ArrayUtils .subarray (rawData ,
199+ rawData .length - Long .BYTES ,
200+ rawData .length )).getLong ();
201+ return KeyPrefixContainer .get (keyPrefix , version , containerIdFromDB );
205202 }
206203
207204 @ Override
0 commit comments