Skip to content

Commit 6db0c32

Browse files
committed
support base64
1 parent 0a64b41 commit 6db0c32

8 files changed

Lines changed: 292 additions & 78 deletions

File tree

src/main/java/com/jsoniter/IterImplString.java

Lines changed: 1 addition & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,11 @@
11
package com.jsoniter;
22

3-
import java.io.ByteArrayOutputStream;
43
import java.io.IOException;
54

6-
import static java.lang.Character.MIN_HIGH_SURROGATE;
7-
import static java.lang.Character.MIN_LOW_SURROGATE;
8-
import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
5+
import static java.lang.Character.*;
96

107
class IterImplString {
118

12-
static int[] base64Tbl = {
13-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
14-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
15-
-1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
16-
55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2,
17-
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
18-
20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30,
19-
31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
20-
48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
21-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
22-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
23-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
24-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
25-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
26-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
27-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
28-
299
public static final String readString(JsonIterator iter) throws IOException {
3010
byte c = IterImpl.nextToken(iter);
3111
if (c == '"') {
@@ -156,50 +136,6 @@ private static char lowSurrogate(int codePoint) {
156136
return (char) ((codePoint & 0x3ff) + MIN_LOW_SURROGATE);
157137
}
158138

159-
public static final byte[] readBase64(JsonIterator iter) throws IOException {
160-
// from https://gist.github.com/EmilHernvall/953733
161-
Slice slice = IterImpl.readSlice(iter);
162-
if (slice == null) {
163-
return null;
164-
}
165-
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
166-
int end = slice.tail();
167-
for (int i = slice.head(); i < end; i++) {
168-
int b = 0;
169-
if (base64Tbl[slice.data()[i]] != -1) {
170-
b = (base64Tbl[slice.data()[i]] & 0xFF) << 18;
171-
}
172-
// skip unknown characters
173-
else {
174-
i++;
175-
continue;
176-
}
177-
178-
int num = 0;
179-
if (i + 1 < end && base64Tbl[slice.data()[i + 1]] != -1) {
180-
b = b | ((base64Tbl[slice.data()[i + 1]] & 0xFF) << 12);
181-
num++;
182-
}
183-
if (i + 2 < end && base64Tbl[slice.data()[i + 2]] != -1) {
184-
b = b | ((base64Tbl[slice.data()[i + 2]] & 0xFF) << 6);
185-
num++;
186-
}
187-
if (i + 3 < end && base64Tbl[slice.data()[i + 3]] != -1) {
188-
b = b | (base64Tbl[slice.data()[i + 3]] & 0xFF);
189-
num++;
190-
}
191-
192-
while (num > 0) {
193-
int c = (b & 0xFF0000) >> 16;
194-
buffer.write((char) c);
195-
b <<= 8;
196-
num--;
197-
}
198-
i += 4;
199-
}
200-
return buffer.toByteArray();
201-
}
202-
203139
// slice does not allow escape
204140
final static int findSliceEnd(JsonIterator iter) {
205141
for (int i = iter.head; i < iter.tail; i++) {

src/main/java/com/jsoniter/JsonIterator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ public final String readString() throws IOException {
206206
return IterImplString.readString(this);
207207
}
208208

209-
public final byte[] readBase64() throws IOException {
210-
return IterImplString.readBase64(this);
209+
public final Slice readStringAsSlice() throws IOException {
210+
return IterImpl.readSlice(this);
211211
}
212212

213213
public final String readObject() throws IOException {
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package com.jsoniter.extra;
2+
3+
import com.jsoniter.output.JsonStream;
4+
5+
import java.io.IOException;
6+
import java.util.Arrays;
7+
8+
/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance
9+
* with RFC 2045.<br><br>
10+
* On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster
11+
* on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes)
12+
* compared to <code>sun.misc.Encoder()/Decoder()</code>.<br><br>
13+
*
14+
* On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and
15+
* about 50% faster for decoding large arrays. This implementation is about twice as fast on very small
16+
* arrays (&lt 30 bytes). If source/destination is a <code>String</code> this
17+
* version is about three times as fast due to the fact that the Commons Codec result has to be recoded
18+
* to a <code>String</code> from <code>byte[]</code>, which is very expensive.<br><br>
19+
*
20+
* This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
21+
* allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
22+
* as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown
23+
* whether Sun's <code>sun.misc.Encoder()/Decoder()</code> produce temporary arrays but since performance
24+
* is quite low it probably does.<br><br>
25+
*
26+
* The encoder produces the same output as the Sun one except that the Sun's encoder appends
27+
* a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the
28+
* length and is probably a side effect. Both are in conformance with RFC 2045 though.<br>
29+
* Commons codec seem to always att a trailing line separator.<br><br>
30+
*
31+
* <b>Note!</b>
32+
* The encode/decode method pairs (types) come in three versions with the <b>exact</b> same algorithm and
33+
* thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different
34+
* format types. The methods not used can simply be commented out.<br><br>
35+
*
36+
* There is also a "fast" version of all decode methods that works the same way as the normal ones, but
37+
* har a few demands on the decoded input. Normally though, these fast verions should be used if the source if
38+
* the input is known and it hasn't bee tampered with.<br><br>
39+
*
40+
* If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com.
41+
*
42+
* Licence (BSD):
43+
* ==============
44+
*
45+
* Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
46+
* All rights reserved.
47+
*
48+
* Redistribution and use in source and binary forms, with or without modification,
49+
* are permitted provided that the following conditions are met:
50+
* Redistributions of source code must retain the above copyright notice, this list
51+
* of conditions and the following disclaimer.
52+
* Redistributions in binary form must reproduce the above copyright notice, this
53+
* list of conditions and the following disclaimer in the documentation and/or other
54+
* materials provided with the distribution.
55+
* Neither the name of the MiG InfoCom AB nor the names of its contributors may be
56+
* used to endorse or promote products derived from this software without specific
57+
* prior written permission.
58+
*
59+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
60+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
61+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
62+
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
63+
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
64+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
65+
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
66+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
67+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
68+
* OF SUCH DAMAGE.
69+
*
70+
* @version 2.2
71+
* @author Mikael Grev
72+
* Date: 2004-aug-02
73+
* Time: 11:31:11
74+
*/
75+
76+
abstract class Base64 {
77+
private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
78+
private static final byte[] BA;
79+
private static final int[] IA = new int[256];
80+
static {
81+
Arrays.fill(IA, -1);
82+
for (int i = 0, iS = CA.length; i < iS; i++) {
83+
IA[CA[i]] = i;
84+
}
85+
IA['='] = 0;
86+
BA = new byte[CA.length];
87+
for (int i = 0; i < CA.length; i++) {
88+
BA[i] = (byte)CA[i];
89+
}
90+
}
91+
92+
static int encodeToChar(byte[] sArr, char[] dArr, final int start) {
93+
final int sLen = sArr.length;
94+
95+
final int eLen = (sLen / 3) * 3; // Length of even 24-bits.
96+
final int dLen = ((sLen - 1) / 3 + 1) << 2; // Returned character count
97+
98+
// Encode even 24-bits
99+
for (int s = 0, d = start; s < eLen;) {
100+
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
101+
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
102+
103+
// Encode the int into four chars
104+
dArr[d++] = CA[(i >>> 18) & 0x3f];
105+
dArr[d++] = CA[(i >>> 12) & 0x3f];
106+
dArr[d++] = CA[(i >>> 6) & 0x3f];
107+
dArr[d++] = CA[i & 0x3f];
108+
}
109+
110+
// Pad and encode last bits if source isn't even 24 bits.
111+
int left = sLen - eLen; // 0 - 2.
112+
if (left > 0) {
113+
// Prepare the int
114+
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
115+
116+
// Set last four chars
117+
dArr[start + dLen - 4] = CA[i >> 12];
118+
dArr[start + dLen - 3] = CA[(i >>> 6) & 0x3f];
119+
dArr[start + dLen - 2] = left == 2 ? CA[i & 0x3f] : '=';
120+
dArr[start + dLen - 1] = '=';
121+
}
122+
123+
return dLen;
124+
}
125+
126+
static int encodeToBytes(byte[] sArr, JsonStream stream) throws IOException {
127+
final int sLen = sArr.length;
128+
129+
final int eLen = (sLen / 3) * 3; // Length of even 24-bits.
130+
final int dLen = ((sLen - 1) / 3 + 1) << 2; // Returned character count
131+
132+
// Encode even 24-bits
133+
for (int s = 0; s < eLen;) {
134+
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
135+
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
136+
137+
// Encode the int into four chars
138+
stream.write(BA[(i >>> 18) & 0x3f], BA[(i >>> 12) & 0x3f], BA[(i >>> 6) & 0x3f], BA[i & 0x3f]);
139+
}
140+
141+
// Pad and encode last bits if source isn't even 24 bits.
142+
int left = sLen - eLen; // 0 - 2.
143+
if (left > 0) {
144+
// Prepare the int
145+
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
146+
147+
// Set last four chars
148+
stream.write(BA[i >> 12], BA[(i >>> 6) & 0x3f], left == 2 ? BA[i & 0x3f] : (byte)'=', '=');
149+
}
150+
151+
return dLen;
152+
}
153+
154+
static int findEnd(final byte[] sArr, final int start) {
155+
for (int i = start; i < sArr.length; i++)
156+
if (IA[sArr[i] & 0xff] < 0)
157+
return i;
158+
return sArr.length;
159+
}
160+
161+
private final static byte[] EMPTY_ARRAY = new byte[0];
162+
163+
static byte[] decodeFast(final byte[] sArr, final int start, final int end) {
164+
// Check special case
165+
int sLen = end - start;
166+
if (sLen == 0)
167+
return EMPTY_ARRAY;
168+
169+
int sIx = start, eIx = end - 1; // Start and end index after trimming.
170+
171+
// Trim illegal chars from start
172+
while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0)
173+
sIx++;
174+
175+
// Trim illegal chars from end
176+
while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0)
177+
eIx--;
178+
179+
// get the padding count (=) (0, 1 or 2)
180+
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
181+
int cCnt = eIx - sIx + 1; // Content count including possible separators
182+
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
183+
184+
int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
185+
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
186+
187+
// Decode all but the last 0 - 2 bytes.
188+
int d = 0;
189+
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
190+
// Assemble three bytes into an int from four "valid" characters.
191+
int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
192+
193+
// Add the bytes
194+
dArr[d++] = (byte) (i >> 16);
195+
dArr[d++] = (byte) (i >> 8);
196+
dArr[d++] = (byte) i;
197+
198+
// If line separator, jump over it.
199+
if (sepCnt > 0 && ++cc == 19) {
200+
sIx += 2;
201+
cc = 0;
202+
}
203+
}
204+
205+
if (d < len) {
206+
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
207+
int i = 0;
208+
for (int j = 0; sIx <= eIx - pad; j++)
209+
i |= IA[sArr[sIx++]] << (18 - j * 6);
210+
211+
for (int r = 16; d < len; r -= 8)
212+
dArr[d++] = (byte) (i >> r);
213+
}
214+
215+
return dArr;
216+
}
217+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.jsoniter.extra;
2+
3+
import com.jsoniter.JsonIterator;
4+
import com.jsoniter.Slice;
5+
import com.jsoniter.any.Any;
6+
import com.jsoniter.output.JsonStream;
7+
import com.jsoniter.spi.Decoder;
8+
import com.jsoniter.spi.Encoder;
9+
import com.jsoniter.spi.JsonException;
10+
import com.jsoniter.spi.JsoniterSpi;
11+
12+
import java.io.IOException;
13+
14+
/**
15+
* byte[] <=> base64
16+
*/
17+
public class Base64Support {
18+
private static boolean enabled;
19+
public static synchronized void enable() {
20+
if (enabled) {
21+
throw new JsonException("Base64Support.enable can only be called once");
22+
}
23+
enabled = true;
24+
JsoniterSpi.registerTypeDecoder(byte[].class, new Decoder() {
25+
@Override
26+
public Object decode(JsonIterator iter) throws IOException {
27+
Slice slice = iter.readStringAsSlice();
28+
return Base64.decodeFast(slice.data(), slice.head(), slice.tail());
29+
}
30+
});
31+
JsoniterSpi.registerTypeEncoder(byte[].class, new Encoder() {
32+
@Override
33+
public void encode(Object obj, JsonStream stream) throws IOException {
34+
byte[] bytes = (byte[]) obj;
35+
stream.write('"');
36+
Base64.encodeToBytes(bytes, stream);
37+
stream.write('"');
38+
}
39+
40+
@Override
41+
public Any wrap(Object obj) {
42+
return null;
43+
}
44+
});
45+
}
46+
}

src/main/java/com/jsoniter/output/CodegenImplNative.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public Any wrap(Object obj) {
4242
put(byte.class, new Encoder() {
4343
@Override
4444
public void encode(Object obj, JsonStream stream) throws IOException {
45-
stream.writeVal((Short) obj);
45+
stream.writeVal(((Byte) obj).shortValue());
4646
}
4747

4848
@Override
@@ -54,7 +54,7 @@ public Any wrap(Object obj) {
5454
put(Byte.class, new Encoder() {
5555
@Override
5656
public void encode(Object obj, JsonStream stream) throws IOException {
57-
stream.writeVal((Short) obj);
57+
stream.writeVal(((Byte) obj).shortValue());
5858
}
5959

6060
@Override
@@ -114,7 +114,7 @@ public Any wrap(Object obj) {
114114
put(char.class, new Encoder() {
115115
@Override
116116
public void encode(Object obj, JsonStream stream) throws IOException {
117-
stream.writeVal((Integer) obj);
117+
stream.writeVal(((Character) obj).charValue());
118118
}
119119

120120
@Override
@@ -126,7 +126,7 @@ public Any wrap(Object obj) {
126126
put(Character.class, new Encoder() {
127127
@Override
128128
public void encode(Object obj, JsonStream stream) throws IOException {
129-
stream.writeVal((Integer) obj);
129+
stream.writeVal(((Character) obj).charValue());
130130
}
131131

132132
@Override

0 commit comments

Comments
 (0)