|
5 | 5 | */ |
6 | 6 | package json.ext; |
7 | 7 |
|
| 8 | +import org.jcodings.Encoding; |
| 9 | +import org.jcodings.specific.ASCIIEncoding; |
| 10 | +import org.jcodings.specific.USASCIIEncoding; |
| 11 | +import org.jcodings.specific.UTF8Encoding; |
| 12 | +import org.jruby.RubyException; |
| 13 | +import org.jruby.RubyString; |
8 | 14 | import org.jruby.exceptions.RaiseException; |
9 | 15 | import org.jruby.runtime.ThreadContext; |
10 | 16 | import org.jruby.util.ByteList; |
| 17 | +import org.jruby.util.StringSupport; |
11 | 18 |
|
12 | 19 | import java.io.IOException; |
13 | 20 | import java.io.OutputStream; |
@@ -120,6 +127,57 @@ final class StringEncoder extends ByteListTranscoder { |
120 | 127 | this.scriptSafe = scriptSafe; |
121 | 128 | } |
122 | 129 |
|
| 130 | + // C: generate_json_string |
| 131 | + void generate(ThreadContext context, RubyString object, OutputStream buffer) throws IOException { |
| 132 | + try { |
| 133 | + object = ensureValidEncoding(context, object); |
| 134 | + } catch (RaiseException re) { |
| 135 | + RubyException exc = Utils.buildGeneratorError(context, object, re.getMessage()); |
| 136 | + exc.setCause(re.getException()); |
| 137 | + throw exc.toThrowable(); |
| 138 | + } |
| 139 | + |
| 140 | + ByteList byteList = object.getByteList(); |
| 141 | + init(byteList); |
| 142 | + out = buffer; |
| 143 | + append('"'); |
| 144 | + switch (object.scanForCodeRange()) { |
| 145 | + case StringSupport.CR_7BIT: |
| 146 | + encodeASCII(context, byteList, buffer); |
| 147 | + break; |
| 148 | + case StringSupport.CR_VALID: |
| 149 | + encode(context, byteList, buffer); |
| 150 | + break; |
| 151 | + default: |
| 152 | + throw Utils.buildGeneratorError(context, object, "source sequence is illegal/malformed utf-8").toThrowable(); |
| 153 | + } |
| 154 | + quoteStop(pos); |
| 155 | + append('"'); |
| 156 | + } |
| 157 | + |
| 158 | + static RubyString ensureValidEncoding(ThreadContext context, RubyString str) { |
| 159 | + Encoding encoding = str.getEncoding(); |
| 160 | + RubyString utf8String; |
| 161 | + if (!(encoding == USASCIIEncoding.INSTANCE || encoding == UTF8Encoding.INSTANCE)) { |
| 162 | + if (encoding == ASCIIEncoding.INSTANCE) { |
| 163 | + utf8String = str.strDup(context.runtime); |
| 164 | + utf8String.setEncoding(UTF8Encoding.INSTANCE); |
| 165 | + switch (utf8String.getCodeRange()) { |
| 166 | + case StringSupport.CR_7BIT: |
| 167 | + return utf8String; |
| 168 | + case StringSupport.CR_VALID: |
| 169 | + // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. |
| 170 | + // TODO: Raise in 3.0.0 |
| 171 | + context.runtime.getWarnings().warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); |
| 172 | + return utf8String; |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + str = (RubyString) str.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); |
| 177 | + } |
| 178 | + return str; |
| 179 | + } |
| 180 | + |
123 | 181 | void encode(ThreadContext context, ByteList src, OutputStream out) throws IOException { |
124 | 182 | while (hasNext()) { |
125 | 183 | handleChar(readUtf8Char(context)); |
|
0 commit comments