-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Base58 Base85 Base122 Functional utils Object utils extension Random utils extension Date utils extension
- Loading branch information
1 parent
c5fd890
commit d48ded8
Showing
22 changed files
with
934 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
src/main/java/com/hijackermax/utils/encoders/Base122.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package com.hijackermax.utils.encoders; | ||
|
||
import com.hijackermax.utils.entities.Tuple; | ||
import com.hijackermax.utils.lang.StringUtils; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Arrays; | ||
import java.util.Objects; | ||
import java.util.function.Consumer; | ||
|
||
/** | ||
* Binary-to-text encoding that is more space-efficient than base64 | ||
* | ||
* @since 0.0.6 | ||
*/ | ||
public final class Base122 { | ||
private static final byte[] INVALID_CHARS = {0x00, 0x0A, 0x0D, 0x22, 0x26, 0x5C}; | ||
private static final byte K_SHORTENED = 0x07; | ||
private static final byte STOP_BYTE = (byte) 0x80; | ||
|
||
private Base122() { | ||
} | ||
|
||
/** | ||
* Encodes provided byte array to Base122 UTF-8 {@link String} | ||
* | ||
* @param source byte array | ||
* @return encoded UTF-8 {@link String} | ||
* @since 0.0.6 | ||
*/ | ||
public static String encode(byte[] source) { | ||
int dataLength = Objects.requireNonNull(source).length; | ||
ByteArrayOutputStream result = new ByteArrayOutputStream(calculateEncodeBufferSize(dataLength)); | ||
Tuple<Integer, Integer> dataPointers = new Tuple<>(0, 0); | ||
for (byte partition = nextPartition(source, dataLength, dataPointers); STOP_BYTE != partition; partition = nextPartition(source, dataLength, dataPointers)) { | ||
int illegalIndex = Arrays.binarySearch(INVALID_CHARS, partition); | ||
if (0 > illegalIndex) { | ||
result.write(partition); | ||
continue; | ||
} | ||
byte nextSevenBits = nextPartition(source, dataLength, dataPointers); | ||
byte firstByte = (byte) (0xC2 | (0x07 & (STOP_BYTE == nextSevenBits ? K_SHORTENED : illegalIndex)) << 2); | ||
if (STOP_BYTE == nextSevenBits) { | ||
nextSevenBits = partition; | ||
} | ||
result.write(firstByte | ((nextSevenBits & 0x40) > 0 ? 1 : 0)); | ||
result.write(0x80 | (nextSevenBits & 0x3F)); | ||
} | ||
return result.toString(StandardCharsets.UTF_8); | ||
} | ||
|
||
/** | ||
* Decodes provided UTF-8 {@link String} to byte array | ||
* | ||
* @param source UTF-8 {@link String} | ||
* @return decoded byte array | ||
* @since 0.0.6 | ||
*/ | ||
public static byte[] decode(String source) { | ||
if (StringUtils.isBlank(source)) { | ||
return new byte[0]; | ||
} | ||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(calculateDecodedBufferSize(source.length())); | ||
Tuple<Byte, Byte> dataPointers = new Tuple<>((byte) 0, (byte) 0); | ||
for (char nextChar : source.toCharArray()) { | ||
if (nextChar > 0x7F) { | ||
int illegalIndex = (nextChar >>> 8) & 0x07; | ||
if (illegalIndex != K_SHORTENED) { | ||
pushPartition(INVALID_CHARS[illegalIndex] << 1, dataPointers, outputStream::write); | ||
} | ||
pushPartition((nextChar & 0x7F) << 1, dataPointers, outputStream::write); | ||
} else { | ||
pushPartition(nextChar << 1, dataPointers, outputStream::write); | ||
} | ||
} | ||
return outputStream.toByteArray(); | ||
} | ||
|
||
private static byte nextPartition(byte[] data, int length, Tuple<Integer, Integer> dataPointers) { | ||
if (dataPointers.getKey() >= length) { | ||
return STOP_BYTE; | ||
} | ||
int firstPart = (((0xFE >>> dataPointers.getValue()) & (data[dataPointers.getKey()] & 0xFF)) | ||
<< dataPointers.getValue()) >>> 1; | ||
dataPointers.modifyValue(v -> v + 7); | ||
if (dataPointers.getValue() < 8) { | ||
return (byte) firstPart; | ||
} | ||
dataPointers.modifyValue(v -> v - 8); | ||
dataPointers.modifyKey(k -> k + 1); | ||
if (dataPointers.getKey() >= length) { | ||
return (byte) firstPart; | ||
} | ||
int secondPart = (((0xFF00 >>> dataPointers.getValue()) & (data[dataPointers.getKey()] & 0xFF)) & 0xFF) | ||
>>> 8 - dataPointers.getValue(); | ||
return (byte) (firstPart | secondPart); | ||
} | ||
|
||
private static void pushPartition(int nextValue, Tuple<Byte, Byte> dataPointers, Consumer<Byte> valueConsumer) { | ||
dataPointers.modifyKey(k -> (byte) (k | (nextValue >>> dataPointers.getValue()))); | ||
dataPointers.modifyValue(v -> (byte) (v + 7)); | ||
if (dataPointers.getValue() >= 8) { | ||
valueConsumer.accept(dataPointers.getKey()); | ||
dataPointers.modifyValue(v -> (byte) (v - 8)); | ||
dataPointers.setKey((byte) (nextValue << (7 - dataPointers.getValue()) & 0xFF)); | ||
} | ||
} | ||
|
||
private static int calculateEncodeBufferSize(int byteArrayLength) { | ||
return byteArrayLength + (byteArrayLength / 7); | ||
} | ||
|
||
private static int calculateDecodedBufferSize(int stringLength) { | ||
return (stringLength * 7) >> 3; | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
src/main/java/com/hijackermax/utils/encoders/Base58.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package com.hijackermax.utils.encoders; | ||
|
||
import com.hijackermax.utils.lang.StringUtils; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.math.BigInteger; | ||
import java.util.Arrays; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Binary-to-text encoding | ||
* | ||
* @since 0.0.6 | ||
*/ | ||
public final class Base58 { | ||
private static final char[] CHARSET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); | ||
private static final BigInteger FIFTY_EIGHT = BigInteger.valueOf(58L); | ||
|
||
private Base58() { | ||
} | ||
|
||
/** | ||
* Encodes provided byte array to Base58 ASCII {@link String} | ||
* | ||
* @param source byte array | ||
* @return encoded ASCII {@link String} | ||
* @since 0.0.6 | ||
*/ | ||
public static String encode(byte[] source) { | ||
int dataLength = Objects.requireNonNull(source).length; | ||
if (0 == dataLength) { | ||
return StringUtils.EMPTY; | ||
} | ||
ByteArrayOutputStream bufferStream = new ByteArrayOutputStream(calculateEncodeBufferSize(dataLength)); | ||
BigInteger reminder = new BigInteger(1, source); | ||
while (reminder.compareTo(BigInteger.ZERO) > 0) { | ||
int idx = reminder.mod(FIFTY_EIGHT).intValue(); | ||
reminder = reminder.divide(FIFTY_EIGHT); | ||
bufferStream.write(CHARSET[idx]); | ||
} | ||
byte[] buffer = bufferStream.toByteArray(); | ||
StringBuilder result = new StringBuilder(); | ||
boolean isLeadingZero = true; | ||
for (int idx = buffer.length - 1; idx >= 0; idx--) { | ||
byte nextByte = buffer[idx]; | ||
if (isLeadingZero) { | ||
boolean isByteZero = 0 == nextByte; | ||
if (isByteZero) { | ||
result.insert(0, (char) 0x49); | ||
} | ||
isLeadingZero = isByteZero; | ||
} | ||
result.append((char) nextByte); | ||
} | ||
return result.toString(); | ||
} | ||
|
||
|
||
/** | ||
* Decodes provided ASCII {@link String} to byte array | ||
* | ||
* @param source ASCII {@link String} | ||
* @return decoded byte array | ||
* @throws IllegalArgumentException if provided string is not base85 encoded | ||
* @since 0.0.6 | ||
*/ | ||
public static byte[] decode(String source) { | ||
if (StringUtils.isBlank(source)) { | ||
return new byte[0]; | ||
} | ||
ByteArrayOutputStream result = new ByteArrayOutputStream(calculateDecodedBufferSize(source.length())); | ||
BigInteger buffer = BigInteger.ZERO; | ||
boolean isLeadingOne = true; | ||
for (char nextChar : source.toCharArray()) { | ||
int idx = Arrays.binarySearch(CHARSET, nextChar); | ||
if (idx < 0) { | ||
throw new IllegalArgumentException("Provided source string is not Base58 encoded"); | ||
} | ||
if (isLeadingOne) { | ||
boolean isCharOne = 0x49 == nextChar; | ||
if (isCharOne) { | ||
result.write(0x00); | ||
} | ||
isLeadingOne = isCharOne; | ||
} | ||
buffer = buffer.multiply(FIFTY_EIGHT).add(BigInteger.valueOf(idx)); | ||
} | ||
byte[] bufferBytes = buffer.toByteArray(); | ||
int offset = 0x00 == bufferBytes[0] ? 1 : 0; | ||
result.write(bufferBytes, offset, bufferBytes.length - offset); | ||
return result.toByteArray(); | ||
} | ||
|
||
private static int calculateEncodeBufferSize(int byteArrayLength) { | ||
return byteArrayLength * 7 / 5; | ||
} | ||
|
||
private static int calculateDecodedBufferSize(int stringLength) { | ||
return stringLength * 5 / 7; | ||
} | ||
} |
Oops, something went wrong.