/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 com.google.j2objc.nio.charset; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; /*-[ #include <iconv.h> #define BYTES_PER_CHAR (sizeof(jchar) / sizeof(jbyte)) ]-*/ /** * Native charset decoder using libiconv. * * @author Keith Stanger */ public class IconvCharsetDecoder extends CharsetDecoder { private final long iconvName; private long iconvHandle; protected IconvCharsetDecoder( Charset charset, float averageCharsPerByte, float maxCharsPerByte, long iconvName) { super(charset, averageCharsPerByte, maxCharsPerByte); this.iconvName = iconvName; } @Override protected native void implReset() /*-[ iconv_close((iconv_t)self->iconvHandle_); self->iconvHandle_ = 0LL; ]-*/; /*-[ static jint getMalformedLength(iconv_t coder, char *inPos, size_t inRemaining) { // Enable discarding of illegal sequences. int discardIllegal = 1; iconvctl(coder, ICONV_SET_DISCARD_ILSEQ, &discardIllegal); char *in = inPos; size_t inSize = inRemaining; char outBuf[0]; char *out = outBuf; size_t outSize = 0; size_t ret = iconv(coder, &in, &inSize, &out, &outSize); int malformedResult = ret == (size_t)-1 ? errno : 0; // The number of illegal bytes read. size_t malformedLength = inRemaining - inSize; // Find the smallest number of bytes that will yield the same result when advanced by that // amount. for (int i = 1; i < malformedLength; i++) { in = inPos + i; size_t inSize2 = inRemaining - i; ret = iconv(coder, &in, &inSize2, &out, &outSize); int newResult = ret == (size_t)-1 ? errno : 0; if (newResult == malformedResult && inSize2 == inSize) { in = inPos; inSize = i + 1; ret = iconv(coder, &in, &inSize, &out, &outSize); if (ret != (size_t)-1 || errno != EINVAL || inSize != i + 1) { malformedLength = i; break; } } } // Reset discarding of illegal sequences to off. discardIllegal = 0; iconvctl(coder, ICONV_SET_DISCARD_ILSEQ, &discardIllegal); return (jint)malformedLength; } ]-*/ @Override protected native CoderResult decodeLoop(ByteBuffer inBuf, CharBuffer outBuf) /*-[ jint inSize = [inBuf remaining]; if (inSize <= 0) { return JavaNioCharsetCoderResult_get_UNDERFLOW(); } iconv_t coder = (iconv_t)self->iconvHandle_; if (coder == NULL) { coder = iconv_open( CFByteOrderGetCurrent() == CFByteOrderLittleEndian ? "UTF-16LE" : "UTF-16BE", (const char *)self->iconvName_); self->iconvHandle_ = (jlong)coder; } IOSByteArray *inArray = nil; char *inRaw; if ([inBuf hasArray]) { jint pos = [inBuf position]; inRaw = (char *)&[inBuf array]->buffer_[[inBuf arrayOffset] + pos]; [inBuf positionWithInt:pos + inSize]; } else { inArray = [IOSByteArray newArrayWithLength:inSize]; [inBuf getWithByteArray:inArray]; inRaw = (char *)inArray->buffer_; } size_t inRawBytes = inSize; jint outSize = [outBuf remaining]; IOSCharArray *outArray = nil; char *outRaw = NULL; size_t outRawBytes = outSize * BYTES_PER_CHAR; if ([outBuf hasArray]) { outRaw = (char *)&[outBuf array]->buffer_[[outBuf arrayOffset] + [outBuf position]]; } else if (outSize > 0) { outArray = [IOSCharArray newArrayWithLength:outSize]; outRaw = (char *)outArray->buffer_; } size_t ret = iconv(coder, &inRaw, &inRawBytes, &outRaw, &outRawBytes); JavaNioCharsetCoderResult *result = JavaNioCharsetCoderResult_get_UNDERFLOW(); if (ret == (size_t)-1) { switch (errno) { case EINVAL: // Incomplete multibyte sequence at the end of input, return UNDERFLOW. break; case EILSEQ: result = JavaNioCharsetCoderResult_malformedForLengthWithInt_( getMalformedLength(coder, inRaw, inRawBytes)); break; case E2BIG: result = JavaNioCharsetCoderResult_get_OVERFLOW(); break; } } if (inRawBytes > 0) { [inBuf positionWithInt:[inBuf position] - (jint)inRawBytes]; } jint encodedChars = outSize - ((jint)outRawBytes / BYTES_PER_CHAR); if (encodedChars > 0) { if (outArray) { [outBuf putWithCharArray:outArray withInt:0 withInt:encodedChars]; } else { [outBuf positionWithInt:[outBuf position] + encodedChars]; } } [inArray release]; [outArray release]; return result; ]-*/; protected native void finalize() /*-[ iconv_close((iconv_t)self->iconvHandle_); ]-*/; }