From 84dc97a63f6f751464fcef7853dbc375c4a10439 Mon Sep 17 00:00:00 2001 From: Techcable Date: Mon, 14 Mar 2016 15:40:44 -0700 Subject: [PATCH] Optimize uuid conversions Optimizes converting to and from mojang format. Manually decode uuids to and from hex. diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/utils/Hex.java b/api/src/main/java/io/github/waterfallmc/waterfall/utils/Hex.java new file mode 100644 index 0000000..ece5f79 --- /dev/null +++ b/api/src/main/java/io/github/waterfallmc/waterfall/utils/Hex.java @@ -0,0 +1,113 @@ +package io.github.waterfallmc.waterfall.utils; + +import java.util.Arrays; +import java.util.Objects; + +public class Hex { + + public static byte[] decode(CharSequence chars) { + byte[] bytes = new byte[chars.length() >> 1]; + decode(chars, 0, bytes, 0, bytes.length); + return bytes; + } + + public static void decode(char[] chars, int charOffset, byte[] dest, int offset, int length) { + decode(new CharSequence() { + @Override + public int length() { + return chars.length; + } + + @Override + public char charAt(int index) { + return chars[index]; + } + + @Override + public CharSequence subSequence(int start, int end) { + return toString().substring(start, end); + } + + @Override + public String toString() { + return new String(chars, charOffset, chars.length); + } + }); + } + + public static void decode(CharSequence chars, int charOffset, byte[] dest, int offset, int length) { + Objects.requireNonNull(chars, "Null chars"); + Objects.requireNonNull(chars, "Null destination"); + final int numChars = chars.length(); + if ((numChars & 0x01) != 0) { + throw new IllegalArgumentException("Odd number of characters: " + numChars); + } else if (length < (numChars - charOffset) >> 1) { + throw new IllegalArgumentException("Too many bytes to fill with " + numChars + " characters: " + length); + } else if (offset < 0 || charOffset < 0 || length < 0 || length * 2 > numChars - charOffset || length > dest.length - offset) { + throw new IndexOutOfBoundsException(); + } + for (int i = 0, charIndex = charOffset; i < length; i++) { + char first = chars.charAt(charIndex++); + char second = chars.charAt(charIndex++); + dest[i + offset] = (byte) ((toDigit(first) << 4) | (toDigit(second))); + } + } + + public static String encodeString(byte[] bytes) { + return new String(encode(bytes)); + } + + public static char[] encode(byte[] bytes) { + char[] chars = new char[bytes.length << 1]; + encode(chars, 0, bytes, 0, bytes.length); + return chars; + } + + public static void encode(char[] chars, int charOffset, byte[] source, int offset, int length) { + Objects.requireNonNull(chars, "Null chars"); + Objects.requireNonNull(chars, "Null bytes"); + if (offset < 0 || charOffset < 0 || length < 0 || length * 2 > chars.length - charOffset || length > source.length - offset) { + throw new IndexOutOfBoundsException(); + } else if (length == 0) { + return; + } + for (int i = 0, charIndex = charOffset; i < length; i++) { + byte b = source[i + offset]; + chars[charIndex++] = fromDigit((byte) ((b >> 4) & 0xF)); + chars[charIndex++] = fromDigit((byte) (b & 0xF)); + } + } + private static final char[] ENCODE_TABLE = new char[]{ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + private static final byte[] DECODE_TABLE = new byte[128]; + + static { + Arrays.fill(DECODE_TABLE, (byte) -1); + for (int value = 0; value < ENCODE_TABLE.length; value++) { + char c = ENCODE_TABLE[value]; + DECODE_TABLE[c] = (byte) value; + char upper; + if ((upper = Character.toUpperCase(c)) != c) { + DECODE_TABLE[upper] = (byte) value; + } + } + } + + public static byte toDigit(char c) { + byte value; + if (c < DECODE_TABLE.length) { + value = DECODE_TABLE[c]; + } else { + value = -1; + } + if (value < 0) throw new IllegalArgumentException("Invalid character " + c); + return value; + } + + private static char fromDigit(byte b) { + assert (b & 0xF) == b : "Out of range " + b; + return ENCODE_TABLE[b]; + } +} \ No newline at end of file diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/utils/UUIDUtils.java b/api/src/main/java/io/github/waterfallmc/waterfall/utils/UUIDUtils.java new file mode 100644 index 0000000..7258a26 --- /dev/null +++ b/api/src/main/java/io/github/waterfallmc/waterfall/utils/UUIDUtils.java @@ -0,0 +1,70 @@ +package io.github.waterfallmc.waterfall.utils; +import java.util.UUID; + +import com.google.common.base.Preconditions; +import com.google.common.primitives.Longs; + +public class UUIDUtils { + private UUIDUtils() {} + + public static String toMojangString(UUID id) { + Preconditions.checkNotNull(id, "Null id"); + return Hex.encodeString(toBytes(id)); + } + + public static UUID fromString(String s) { + Preconditions.checkNotNull(s, "Null string"); + if (s.length() == 36) { // UUID.toString() uuid + s = s.replaceAll("-", ""); + } else if (s.length() != 32) { + throw new IllegalArgumentException("Invalid UUID: " + s); + } + return fromMojangString0(s); + } + + public static UUID fromMojangString(String s) { + Preconditions.checkNotNull(s, "Null string"); + if (s.length() != 32) { + throw new IllegalArgumentException("UUID not in mojang format: " + s); + } + return fromMojangString0(s); + } + + private static UUID fromMojangString0(String s) { + assert s != null : "Null string"; + assert s.length() == 32 : "invalid length: " + s; + try { + return fromBytes(Hex.decode(s)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid UUID: " + s); + } + } + + public static byte[] toBytes(UUID id) { + Preconditions.checkNotNull(id, "Null id"); + byte[] result = new byte[16]; + long lsb = id.getLeastSignificantBits(); + for (int i = 15; i >= 8; i--) { + result[i] = (byte) (lsb & 0xffL); + lsb >>= 8; + } + long msb = id.getMostSignificantBits(); + for (int i = 7; i >= 0; i--) { + result[i] = (byte) (msb & 0xffL); + msb >>= 8; + } + return result; + } + + public static UUID fromBytes(byte[] bytes) { + Preconditions.checkNotNull(bytes, "Null bytes"); + if (bytes.length != 16) { + throw new IllegalArgumentException("Invalid length: " + bytes.length); + } + long msb = Longs.fromBytes(bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7]); + long lsb = Longs.fromBytes(bytes[8], bytes[9], bytes[10], bytes[11], + bytes[12], bytes[13], bytes[14], bytes[15]); + return new UUID(msb, lsb); + } +} \ No newline at end of file diff --git a/api/src/main/java/net/md_5/bungee/Util.java b/api/src/main/java/net/md_5/bungee/Util.java index 37e12e9..b268bef 100644 --- a/api/src/main/java/net/md_5/bungee/Util.java +++ b/api/src/main/java/net/md_5/bungee/Util.java @@ -1,11 +1,15 @@ package net.md_5.bungee; import com.google.common.base.Joiner; +import com.google.common.primitives.Ints; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.UUID; +import io.github.waterfallmc.waterfall.utils.Hex; +import io.github.waterfallmc.waterfall.utils.UUIDUtils; + /** * Series of utility classes to perform various operations. */ @@ -42,7 +46,7 @@ public class Util */ public static String hex(int i) { - return String.format( "0x%02X", i ); + return Hex.encodeString(Ints.toByteArray(i)); } /** @@ -78,10 +82,17 @@ public class Util */ public static UUID getUUID(String uuid) { - return UUID.fromString( uuid.substring( 0, 8 ) + "-" + uuid.substring( 8, 12 ) + "-" + uuid.substring( 12, 16 ) + "-" + uuid.substring( 16, 20 ) + "-" + uuid.substring( 20, 32 ) ); + return UUIDUtils.fromString(uuid); } - public static String getMojangUUID(UUID uuid) { - return uuid.toString().replace( "-", "" ); + /** + * Converts a UUID to a Mojang UUID + * + * @param uuid The string to be converted + * @return The result + */ + public static String getMojangUUID(UUID uuid) + { + return UUIDUtils.toMojangString(uuid); } } -- 2.8.3