-
-
Notifications
You must be signed in to change notification settings - Fork 974
Improve hot path virtual-thread and codec performance #15828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 8.0.x
Are you sure you want to change the base?
Changes from all commits
fe0dd69
211a1ff
0aa3569
e185ca0
565aa0d
25732a6
6cfb084
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,38 +18,42 @@ | |
| */ | ||
| package org.grails.plugins.codecs | ||
|
|
||
| import java.nio.charset.Charset | ||
| import java.nio.charset.StandardCharsets | ||
|
|
||
| import org.codehaus.groovy.runtime.NullObject | ||
|
|
||
| import org.apache.commons.codec.binary.Base64 | ||
|
|
||
| import groovy.transform.CompileStatic | ||
|
|
||
| /** | ||
| * A codec that encodes and decodes Objects using Base64 encoding. | ||
| * | ||
| * @author Drew Varner | ||
| */ | ||
| @CompileStatic | ||
| class Base64CodecExtensionMethods { | ||
|
|
||
| static encodeAsBase64(theTarget) { | ||
| static Object encodeAsBase64(Object theTarget, Charset charset = StandardCharsets.UTF_8) { | ||
| if (theTarget == null || theTarget instanceof NullObject) { | ||
| return null | ||
| } | ||
|
|
||
| if (theTarget instanceof Byte[] || theTarget instanceof byte[]) { | ||
| return new String(Base64.encodeBase64(theTarget)) | ||
| return new String(Base64.encodeBase64(DigestUtils.toByteArray(theTarget)), StandardCharsets.UTF_8) | ||
| } | ||
|
|
||
| return new String(Base64.encodeBase64(theTarget.toString().getBytes(StandardCharsets.UTF_8))) | ||
| return new String(Base64.encodeBase64(theTarget.toString().getBytes(charset)), StandardCharsets.UTF_8) | ||
| } | ||
|
|
||
| static decodeBase64(theTarget) { | ||
| static Object decodeBase64(Object theTarget, Charset charset = StandardCharsets.UTF_8) { | ||
| if (theTarget == null || theTarget instanceof NullObject) { | ||
| return null | ||
| } | ||
|
|
||
| if (theTarget instanceof Byte[] || theTarget instanceof byte[]) { | ||
| return Base64.decodeBase64(theTarget) | ||
| return Base64.decodeBase64(DigestUtils.toByteArray(theTarget)) | ||
| } | ||
|
|
||
| return Base64.decodeBase64(theTarget.toString().getBytes(StandardCharsets.UTF_8)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should take the opportunity to make UTF_8 an added method argument that's defaulted |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,30 +18,89 @@ | |
| */ | ||
| package org.grails.plugins.codecs | ||
|
|
||
| import java.lang.reflect.Array | ||
| import java.nio.charset.StandardCharsets | ||
| import java.security.MessageDigest | ||
|
|
||
| import groovy.transform.CompileStatic | ||
| import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation | ||
|
|
||
| @CompileStatic | ||
| abstract class DigestUtils { | ||
|
|
||
| // Digest byte[], any list/array or string into a byte[] | ||
| static digest(String algorithm, data) { | ||
| static Object digest(String algorithm, Object data) { | ||
| if (data == null) { | ||
| return null | ||
| } | ||
|
|
||
| def md = MessageDigest.getInstance(algorithm) | ||
| def src | ||
| if (data instanceof Byte[] || data instanceof byte[]) { | ||
| src = data | ||
| MessageDigest md = MessageDigest.getInstance(algorithm) | ||
| byte[] src = toByteArray(data) | ||
| md.update(src) // This probably needs to use the thread's Locale encoding | ||
| return md.digest() | ||
| } | ||
|
|
||
| protected static byte[] toByteArray(Object data) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
| if (data instanceof byte[]) { | ||
| return (byte[]) data | ||
| } | ||
| else if (data instanceof List || data.getClass().isArray()) { | ||
| src = new byte[data.size()] | ||
| data.eachWithIndex { v, i -> src[i] = v } | ||
| if (data instanceof Byte[]) { | ||
| return toByteArrayFromWrapper((Byte[]) data) | ||
| } | ||
| else { | ||
| src = data.toString().getBytes(StandardCharsets.UTF_8) | ||
| if (data instanceof Iterable) { | ||
| return toByteArrayFromIterable((Iterable<?>) data) | ||
| } | ||
| md.update(src) // This probably needs to use the thread's Locale encoding | ||
| return md.digest() | ||
| if (data instanceof Iterator) { | ||
| return toByteArrayFromIterator((Iterator<?>) data) | ||
| } | ||
| if (data.getClass().isArray()) { | ||
| return toByteArrayFromArray(data, Array.getLength(data)) | ||
| } | ||
|
|
||
| return data.toString().getBytes(StandardCharsets.UTF_8) | ||
| } | ||
|
|
||
| private static byte[] toByteArrayFromWrapper(Byte[] data) { | ||
| byte[] result = new byte[data.length] | ||
| for (int i = 0; i < data.length; i++) { | ||
| result[i] = data[i].byteValue() | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| private static byte[] toByteArrayFromIterable(Iterable<?> data) { | ||
| List<Byte> bytes = new ArrayList<Byte>() | ||
| for (Object value : data) { | ||
| bytes.add(toByte(value)) | ||
| } | ||
| return toByteArrayFromList(bytes) | ||
| } | ||
|
|
||
| private static byte[] toByteArrayFromIterator(Iterator<?> data) { | ||
| List<Byte> bytes = new ArrayList<Byte>() | ||
| while (data.hasNext()) { | ||
| bytes.add(toByte(data.next())) | ||
| } | ||
| return toByteArrayFromList(bytes) | ||
| } | ||
|
|
||
| private static byte[] toByteArrayFromList(List<Byte> data) { | ||
| byte[] result = new byte[data.size()] | ||
| for (int i = 0; i < data.size(); i++) { | ||
| result[i] = data.get(i) | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| private static byte[] toByteArrayFromArray(Object data, int length) { | ||
| byte[] result = new byte[length] | ||
| for (int i = 0; i < length; i++) { | ||
| result[i] = toByte(Array.get(data, i)) | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| private static byte toByte(Object value) { | ||
| DefaultTypeTransformation.castToNumber(value).byteValue() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,51 +21,48 @@ package org.grails.plugins.codecs | |
| import java.nio.charset.StandardCharsets | ||
|
|
||
| import org.codehaus.groovy.runtime.NullObject | ||
| import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation | ||
|
|
||
| import groovy.transform.CompileStatic | ||
|
|
||
| @CompileStatic | ||
| class HexCodecExtensionMethods { | ||
|
|
||
| static HEXDIGITS = '0123456789abcdef' | ||
| static Object HEXDIGITS = '0123456789abcdef' | ||
|
|
||
| // Expects an array/list of numbers | ||
| static encodeAsHex(theTarget) { | ||
| static Object encodeAsHex(Object theTarget) { | ||
| if (theTarget == null || theTarget instanceof NullObject) { | ||
| return null | ||
| } | ||
|
|
||
| def result = new StringBuilder() | ||
| if (theTarget instanceof String) { | ||
| theTarget = theTarget.getBytes(StandardCharsets.UTF_8) | ||
| } | ||
| theTarget.each() { | ||
| result << HexCodecExtensionMethods.HEXDIGITS[(it & 0xF0) >> 4] | ||
| result << HexCodecExtensionMethods.HEXDIGITS[it & 0x0F] | ||
| byte[] bytes = theTarget instanceof String ? ((String) theTarget).getBytes(StandardCharsets.UTF_8) : DigestUtils.toByteArray(theTarget) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Behavior change for iterable targets that aren't |
||
| StringBuilder result = new StringBuilder(bytes.length * 2) | ||
| String hexDigits = (String) HEXDIGITS | ||
| for (byte value : bytes) { | ||
| int unsignedValue = value & 0xFF | ||
| result.append(hexDigits.charAt((unsignedValue & 0xF0) >> 4)) | ||
| result.append(hexDigits.charAt(unsignedValue & 0x0F)) | ||
| } | ||
| return result.toString() | ||
| } | ||
|
|
||
| static decodeHex(theTarget) { | ||
| if (!theTarget) return null | ||
| static Object decodeHex(Object theTarget) { | ||
| if (theTarget instanceof CharSequence && theTarget.length() == 0) return new byte[0] | ||
| if (!DefaultTypeTransformation.castToBoolean(theTarget)) return null | ||
|
|
||
| def output = [] | ||
|
|
||
| def str = theTarget.toString().toLowerCase() | ||
| if (str.size() % 2) { | ||
| String str = theTarget.toString().toLowerCase() | ||
| if (str.length() % 2 != 0) { | ||
| throw new UnsupportedOperationException('Decode of hex strings requires strings of even length') | ||
| } | ||
|
|
||
| def currentByte | ||
| str.eachWithIndex { val, idx -> | ||
| if (!(idx % 2)) { | ||
| currentByte = HEXDIGITS.indexOf(val) << 4 | ||
| } | ||
| else { | ||
| output << (currentByte | HEXDIGITS.indexOf(val)) | ||
| currentByte = 0 | ||
| } | ||
| byte[] result = new byte[str.length() >> 1] | ||
| String hexDigits = (String) HEXDIGITS | ||
| for (int i = 0; i < str.length(); i += 2) { | ||
| int high = hexDigits.indexOf((int) str.charAt(i)) | ||
| int low = hexDigits.indexOf((int) str.charAt(i + 1)) | ||
| result[i >> 1] = (byte) ((high << 4) | low) | ||
| } | ||
|
|
||
| def result = new byte[output.size()] | ||
| output.eachWithIndex { v, i -> result[i] = v } | ||
| return result | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,15 +18,19 @@ | |
| */ | ||
| package org.grails.plugins.codecs | ||
|
|
||
| import groovy.transform.CompileStatic | ||
|
|
||
| @CompileStatic | ||
| class MD5CodecExtensionMethods { | ||
|
|
||
| // Returns the byte[] of the digest, taken from UTF-8 of the string representation | ||
| // or the raw data coerced to bytes | ||
| static encodeAsMD5(theTarget) { | ||
| theTarget.encodeAsMD5Bytes()?.encodeAsHex() | ||
| static Object encodeAsMD5(Object theTarget) { | ||
| byte[] digest = (byte[]) MD5BytesCodecExtensionMethods.encodeAsMD5Bytes(theTarget) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just confirming this is intended: the old |
||
| return digest == null ? null : HexCodecExtensionMethods.encodeAsHex(digest) | ||
| } | ||
|
|
||
| static decodeMD5(theTarget) { | ||
| static Object decodeMD5(Object theTarget) { | ||
| throw new UnsupportedOperationException('Cannot decode MD5 hashes') | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.