From a39e6eb47e84b7cb82aebcece80990ea8eade1e4 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 26 Nov 2017 19:56:23 +0300 Subject: [PATCH] Get rid of Base64 class in TeaVM compiler --- .../java/org/teavm/classlib/impl/Base64.java | 87 ---------- .../org/teavm/classlib/impl/Base64Impl.java | 153 ++++++++++++++++++ .../java/lang/ClassLoaderNativeGenerator.java | 10 +- .../classlib/java/lang/TClassLoader.java | 8 +- .../org/teavm/classlib/impl/Base64Test.java | 44 ++++- 5 files changed, 209 insertions(+), 93 deletions(-) delete mode 100644 classlib/src/main/java/org/teavm/classlib/impl/Base64.java create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/Base64Impl.java diff --git a/classlib/src/main/java/org/teavm/classlib/impl/Base64.java b/classlib/src/main/java/org/teavm/classlib/impl/Base64.java deleted file mode 100644 index 873d91331..000000000 --- a/classlib/src/main/java/org/teavm/classlib/impl/Base64.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.classlib.impl; - -import java.util.Arrays; - -public final class Base64 { - private static char[] alphabet = new char[64]; - private static int[] reverse = new int[256]; - - static { - int i = 0; - for (char c = 'A'; c <= 'Z'; ++c) { - alphabet[i++] = c; - } - for (char c = 'a'; c <= 'z'; ++c) { - alphabet[i++] = c; - } - for (char c = '0'; c <= '9'; ++c) { - alphabet[i++] = c; - } - alphabet[i++] = '+'; - alphabet[i++] = '/'; - - Arrays.fill(reverse, -1); - for (i = 0; i < alphabet.length; ++i) { - reverse[alphabet[i]] = i; - } - } - - private Base64() { - } - - public static byte[] decode(String text) { - int outputSize = ((text.length() - 1) / 4 + 1) * 3; - int i; - int j; - for (i = text.length() - 1; i >= 0 && text.charAt(i) == '='; --i) { - --outputSize; - } - byte[] output = new byte[outputSize]; - - int triples = (outputSize / 3) * 3; - i = 0; - for (j = 0; j < triples;) { - int a = decode(text.charAt(i++)); - int b = decode(text.charAt(i++)); - int c = decode(text.charAt(i++)); - int d = decode(text.charAt(i++)); - int out = (a << 18) | (b << 12) | (c << 6) | d; - output[j++] = (byte) (out >>> 16); - output[j++] = (byte) (out >>> 8); - output[j++] = (byte) out; - } - int rem = output.length - j; - if (rem == 1) { - int a = decode(text.charAt(i)); - int b = decode(text.charAt(i + 1)); - output[j] = (byte) ((a << 2) | (b >>> 4)); - } else if (rem == 2) { - int a = decode(text.charAt(i)); - int b = decode(text.charAt(i + 1)); - int c = decode(text.charAt(i + 2)); - output[j] = (byte) ((a << 2) | (b >>> 4)); - output[j + 1] = (byte) ((b << 4) | (c >>> 2)); - } - - return output; - } - - private static int decode(char c) { - return c < 256 ? reverse[c] : -1; - } -} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/Base64Impl.java b/classlib/src/main/java/org/teavm/classlib/impl/Base64Impl.java new file mode 100644 index 000000000..32bb7dc2e --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/Base64Impl.java @@ -0,0 +1,153 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl; + +import java.util.Arrays; + +public final class Base64Impl { + private static byte[] alphabet = new byte[64]; + private static int[] reverse = new int[256]; + + static { + int i = 0; + for (char c = 'A'; c <= 'Z'; ++c) { + alphabet[i++] = (byte) c; + } + for (char c = 'a'; c <= 'z'; ++c) { + alphabet[i++] = (byte) c; + } + for (char c = '0'; c <= '9'; ++c) { + alphabet[i++] = (byte) c; + } + alphabet[i++] = '+'; + alphabet[i++] = '/'; + + Arrays.fill(reverse, -1); + for (i = 0; i < alphabet.length; ++i) { + reverse[alphabet[i]] = i; + } + } + + private Base64Impl() { + } + + public static byte[] decode(byte[] text) { + int outputSize = (text.length / 4) * 3; + int rem = text.length % 4; + if (rem == 2 || rem == 3) { + outputSize += rem - 1; + } + int i; + for (i = text.length - 1; i >= 0 && text[i] == '='; --i) { + --outputSize; + } + byte[] output = new byte[outputSize]; + decode(text, output); + return output; + } + + public static void decode(byte[] text, byte[] output) { + int inputSize = text.length; + int i; + for (i = text.length - 1; i >= 0 && text[i] == '='; --i) { + --inputSize; + } + int triples = (inputSize / 4) * 4; + + i = 0; + int j = 0; + while (i < triples) { + int a = decode(text[i++]); + int b = decode(text[i++]); + int c = decode(text[i++]); + int d = decode(text[i++]); + int out = (a << 18) | (b << 12) | (c << 6) | d; + output[j++] = (byte) (out >>> 16); + output[j++] = (byte) (out >>> 8); + output[j++] = (byte) out; + } + + int rem = inputSize - i; + if (rem == 2) { + int a = decode(text[i]); + int b = decode(text[i + 1]); + output[j] = (byte) ((a << 2) | (b >>> 4)); + } else if (rem == 3) { + int a = decode(text[i]); + int b = decode(text[i + 1]); + int c = decode(text[i + 2]); + output[j] = (byte) ((a << 2) | (b >>> 4)); + output[j + 1] = (byte) ((b << 4) | (c >>> 2)); + } + } + + private static int decode(byte c) { + return reverse[c]; + } + + public static byte[] encode(byte[] data, boolean pad) { + int outputSize = ((data.length + 2) / 3) * 4; + if (!pad) { + int rem = data.length % 3; + if (rem != 0) { + outputSize -= 3 - rem; + } + } + byte[] output = new byte[outputSize]; + encode(data, output, pad); + return output; + } + + public static int encode(byte[] data, byte[] output, boolean pad) { + int triples = (data.length / 3) * 3; + + int i = 0; + int j; + for (j = 0; j < triples;) { + output[i++] = encode((byte) (data[j] >>> 2)); + output[i++] = encode((byte) ((data[j] << 4) | (data[j + 1] >>> 4))); + ++j; + output[i++] = encode((byte) ((data[j] << 2) | (data[j + 1] >>> 6))); + ++j; + output[i++] = encode(data[j]); + ++j; + } + + int rem = data.length - j; + if (rem == 1) { + output[i++] = encode((byte) (data[j] >>> 2)); + output[i++] = encode((byte) (data[j] << 4)); + if (pad) { + output[i++] = '='; + output[i++] = '='; + } + } else if (rem == 2) { + output[i++] = encode((byte) (data[j] >>> 2)); + output[i++] = encode((byte) ((data[j] << 4) | (data[j + 1] >>> 4))); + ++j; + output[i++] = encode((byte) (data[j] << 2)); + if (pad) { + output[i++] = '='; + } + } + + return i; + } + + private static byte encode(byte b) { + return alphabet[b & 63]; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java index b2c10caa4..c43645492 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java @@ -19,7 +19,6 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; -import java.util.Base64; import java.util.HashSet; import java.util.Properties; import java.util.ServiceLoader; @@ -31,6 +30,7 @@ import org.teavm.backend.javascript.spi.Injector; import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.classlib.ResourceSupplier; import org.teavm.classlib.ResourceSupplierContext; +import org.teavm.classlib.impl.Base64Impl; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodReference; @@ -69,10 +69,14 @@ public class ClassLoaderNativeGenerator implements Injector { } first = false; writer.newLine(); - String data = Base64.getEncoder().encodeToString(IOUtils.toByteArray(new BufferedInputStream(input))); + byte[] dataBytes = Base64Impl.encode(IOUtils.toByteArray(new BufferedInputStream(input)), true); + char[] dataChars = new char[dataBytes.length]; + for (int i = 0; i < dataBytes.length; ++i) { + dataChars[i] = (char) dataBytes[i]; + } writer.append("\"").append(RenderingUtil.escapeString(resource)).append("\""); writer.ws().append(':').ws(); - writer.append("\"").append(data).append("\""); + writer.append("\"").append(new String(dataChars)).append("\""); } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java index e23b66e67..9b9f47dd9 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java @@ -18,7 +18,7 @@ package org.teavm.classlib.java.lang; import java.io.ByteArrayInputStream; import java.io.InputStream; import org.teavm.backend.javascript.spi.InjectedBy; -import org.teavm.classlib.impl.Base64; +import org.teavm.classlib.impl.Base64Impl; import org.teavm.jso.JSBody; import org.teavm.jso.JSIndexer; import org.teavm.jso.JSObject; @@ -50,7 +50,11 @@ public abstract class TClassLoader extends TObject { } JSObject data = resources.getResource(name); String dataString = resourceToString(data); - return dataString == null ? null : new ByteArrayInputStream(Base64.decode(dataString)); + byte[] bytes = new byte[dataString.length()]; + for (int i = 0; i < bytes.length; ++i) { + bytes[i] = (byte) dataString.charAt(i); + } + return dataString == null ? null : new ByteArrayInputStream(Base64Impl.decode(bytes)); } public static InputStream getSystemResourceAsStream(String name) { diff --git a/classlib/src/test/java/org/teavm/classlib/impl/Base64Test.java b/classlib/src/test/java/org/teavm/classlib/impl/Base64Test.java index 590374e5b..8eab16440 100644 --- a/classlib/src/test/java/org/teavm/classlib/impl/Base64Test.java +++ b/classlib/src/test/java/org/teavm/classlib/impl/Base64Test.java @@ -23,17 +23,59 @@ public class Base64Test { @Test public void decoderWorks() { assertEquals("q", decode("cQ==")); + assertEquals("q", decode("cQ")); assertEquals("qw", decode("cXc=")); + assertEquals("qw", decode("cXc")); assertEquals("qwe", decode("cXdl")); assertEquals("qwer", decode("cXdlcg==")); + assertEquals("qwer", decode("cXdlcg")); assertEquals("qwert", decode("cXdlcnQ=")); assertEquals("qwerty", decode("cXdlcnR5")); assertEquals("qwertyu", decode("cXdlcnR5dQ==")); + assertEquals("qwertyu", decode("cXdlcnR5dQ")); + } + + @Test + public void encoderWorks() { + assertEquals("cQ==", encode("q")); + assertEquals("cXc=", encode("qw")); + assertEquals("cXdl", encode("qwe")); + assertEquals("cXdlcg==", encode("qwer")); + assertEquals("cXdlcnQ=", encode("qwert")); + assertEquals("cXdlcnR5", encode("qwerty")); + assertEquals("cXdlcnR5dQ==", encode("qwertyu")); + } + + @Test + public void encoderNoPadWorks() { + assertEquals("cQ", encodeNoPad("q")); + assertEquals("cXc", encodeNoPad("qw")); + assertEquals("cXdl", encodeNoPad("qwe")); + assertEquals("cXdlcg", encodeNoPad("qwer")); + assertEquals("cXdlcnQ", encodeNoPad("qwert")); + assertEquals("cXdlcnR5", encodeNoPad("qwerty")); + assertEquals("cXdlcnR5dQ", encodeNoPad("qwertyu")); } private String decode(String text) { try { - return new String(Base64.decode(text), "UTF-8"); + return new String(Base64Impl.decode(text.getBytes("UTF-8")), "UTF-8"); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + private String encode(String text) { + try { + return new String(Base64Impl.encode(text.getBytes("UTF-8"), true), "UTF-8"); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + private String encodeNoPad(String text) { + try { + return new String(Base64Impl.encode(text.getBytes("UTF-8"), false), "UTF-8"); } catch (UnsupportedEncodingException e) { return ""; }