blob: 7fe5728c72088106a0a2a1c95d9b0b3f3858e19b [file] [log] [blame]
/**
*******************************************************************************
* Copyright (C) 2006-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
*******************************************************************************
*/
package com.ibm.icu.charset;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import com.ibm.icu.impl.Assert;
/**
* An abstract class that provides framework methods of decoding operations for concrete
* subclasses.
* In the future this class will contain API that will implement converter sematics of ICU4C.
* @stable ICU 3.6
*/
public abstract class CharsetDecoderICU extends CharsetDecoder{
int toUnicodeStatus;
byte[] toUBytesArray = new byte[128];
int toUBytesBegin = 0;
int toULength;
char[] charErrorBufferArray = new char[128];
int charErrorBufferLength;
int charErrorBufferBegin;
char[] invalidCharBuffer = new char[128];
int invalidCharLength;
/**
* Maximum number of indexed bytes
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
protected static final int EXT_MAX_BYTES = 0x1f;
/* store previous UChars/chars to continue partial matches */
byte[] preToUArray = new byte[EXT_MAX_BYTES];
int preToUBegin;
int preToULength; /* negative: replay */
int preToUFirstLength; /* length of first character */
int mode;
Object toUContext = null;
private CharsetCallback.Decoder onUnmappableCharacter = CharsetCallback.TO_U_CALLBACK_STOP;
private CharsetCallback.Decoder onMalformedInput = CharsetCallback.TO_U_CALLBACK_STOP;
CharsetCallback.Decoder toCharErrorBehaviour = new CharsetCallback.Decoder() {
public CoderResult call(CharsetDecoderICU decoder, Object context, ByteBuffer source,
CharBuffer target, IntBuffer offsets, char[] buffer, int length, CoderResult cr) {
if (cr.isUnmappable()) {
return onUnmappableCharacter.call(decoder, context, source, target, offsets, buffer,
length, cr);
} else /* if (cr.isMalformed()) */ {
return onMalformedInput.call(decoder, context, source, target, offsets, buffer,
length, cr);
}
// return CharsetCallback.TO_U_CALLBACK_STOP.call(decoder, context, source, target, offsets, buffer, length, cr);
}
};
// exist to keep implOnMalformedInput and implOnUnmappableInput from being too recursive
private boolean malformedInputCalled = false;
private boolean unmappableCharacterCalled = false;
/*
* Construct a CharsetDecorderICU based on the information provided from a CharsetICU object.
*
* @param cs The CharsetICU object containing information about how to charset to decode.
*/
CharsetDecoderICU(CharsetICU cs) {
super(cs, (1/cs.maxCharsPerByte), cs.maxCharsPerByte);
}
/*
* Is this Decoder allowed to use fallbacks? A fallback mapping is a mapping
* that will convert a byte sequence to a Unicode codepoint sequence, but
* the encoded Unicode codepoint sequence will round trip convert to a different
* byte sequence. In ICU, this is can be called a reverse fallback.
* @return A boolean
*/
final boolean isFallbackUsed() {
return true;
}
/**
* Fallback is currently always used by icu4j decoders.
*/
static final boolean isToUUseFallback() {
return isToUUseFallback(true);
}
/**
* Fallback is currently always used by icu4j decoders.
*/
static final boolean isToUUseFallback(boolean iUseFallback) {
return true;
}
/**
* Sets the action to be taken if an illegal sequence is encountered
*
* @param newAction action to be taken
* @exception IllegalArgumentException
* @stable ICU 3.6
*/
protected final void implOnMalformedInput(CodingErrorAction newAction) {
// don't run infinitely
if (malformedInputCalled)
return;
// if we get a replace, do not let the nio replace
if (newAction == CodingErrorAction.REPLACE) {
malformedInputCalled = true;
super.onMalformedInput(CodingErrorAction.IGNORE);
malformedInputCalled = false;
}
onMalformedInput = getCallback(newAction);
}
/**
* Sets the action to be taken if an illegal sequence is encountered
*
* @param newAction action to be taken
* @exception IllegalArgumentException
* @stable ICU 3.6
*/
protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
// dont run infinitely
if (unmappableCharacterCalled)
return;
// if we get a replace, do not let the nio replace
if (newAction == CodingErrorAction.REPLACE) {
unmappableCharacterCalled = true;
super.onUnmappableCharacter(CodingErrorAction.IGNORE);
unmappableCharacterCalled = false;
}
onUnmappableCharacter = getCallback(newAction);
}
/**
* Sets the callback encoder method and context to be used if an illegal sequence is encounterd.
* You would normally call this twice to set both the malform and unmappable error. In this case,
* newContext should remain the same since using a different newContext each time will negate the last
* one used.
* @param err CoderResult
* @param newCallback CharsetCallback.Encoder
* @param newContext Object
* @stable ICU 4.0
*/
public final void setToUCallback(CoderResult err, CharsetCallback.Decoder newCallback, Object newContext) {
if (err.isMalformed()) {
onMalformedInput = newCallback;
} else if (err.isUnmappable()) {
onUnmappableCharacter = newCallback;
} else {
/* Error: Only malformed and unmappable are handled. */
}
if (toUContext == null || !toUContext.equals(newContext)) {
toUContext = newContext;
}
}
private static CharsetCallback.Decoder getCallback(CodingErrorAction action){
if(action==CodingErrorAction.REPLACE){
return CharsetCallback.TO_U_CALLBACK_SUBSTITUTE;
}else if(action==CodingErrorAction.IGNORE){
return CharsetCallback.TO_U_CALLBACK_SKIP;
}else /* if(action==CodingErrorAction.REPORT) */ {
return CharsetCallback.TO_U_CALLBACK_STOP;
}
}
private final ByteBuffer EMPTY = ByteBuffer.allocate(0);
/**
* Flushes any characters saved in the converter's internal buffer and
* resets the converter.
* @param out action to be taken
* @return result of flushing action and completes the decoding all input.
* Returns CoderResult.UNDERFLOW if the action succeeds.
* @stable ICU 3.6
*/
protected final CoderResult implFlush(CharBuffer out) {
return decode(EMPTY, out, null, true);
}
/**
* Resets the to Unicode mode of converter
* @stable ICU 3.6
*/
protected void implReset() {
toUnicodeStatus = 0 ;
toULength = 0;
charErrorBufferLength = 0;
charErrorBufferBegin = 0;
/* store previous UChars/chars to continue partial matches */
preToUBegin = 0;
preToULength = 0; /* negative: replay */
preToUFirstLength = 0;
mode = 0;
}
/**
* Decodes one or more bytes. The default behaviour of the converter
* is stop and report if an error in input stream is encountered.
* To set different behaviour use @see CharsetDecoder.onMalformedInput()
* This method allows a buffer by buffer conversion of a data stream.
* The state of the conversion is saved between calls to convert.
* Among other things, this means multibyte input sequences can be
* split between calls. If a call to convert results in an Error, the
* conversion may be continued by calling convert again with suitably
* modified parameters.All conversions should be finished with a call to
* the flush method.
* @param in buffer to decode
* @param out buffer to populate with decoded result
* @return Result of decoding action. Returns CoderResult.UNDERFLOW if the decoding
* action succeeds or more input is needed for completing the decoding action.
* @stable ICU 3.6
*/
protected CoderResult decodeLoop(ByteBuffer in,CharBuffer out){
if(in.remaining() < toUCountPending()){
return CoderResult.UNDERFLOW;
}
// if (!in.hasRemaining()) {
// toULength = 0;
// return CoderResult.UNDERFLOW;
// }
in.position(in.position() + toUCountPending());
/* do the conversion */
CoderResult ret = decode(in, out, null, false);
// ok was there input held in the previous invocation of decodeLoop
// that resulted in output in this invocation?
in.position(in.position() - toUCountPending());
return ret;
}
/*
* Implements the ICU semantic for decode operation
* @param in The input byte buffer
* @param out The output character buffer
* @return Result of decoding action. Returns CoderResult.UNDERFLOW if the decoding
* action succeeds or more input is needed for completing the decoding action.
*/
abstract CoderResult decodeLoop(ByteBuffer in, CharBuffer out, IntBuffer offsets, boolean flush);
/*
* Implements the ICU semantic for decode operation
* @param source The input byte buffer
* @param target The output character buffer
* @param offsets
* @param flush true if, and only if, the invoker can provide no
* additional input bytes beyond those in the given buffer.
* @return Result of decoding action. Returns CoderResult.UNDERFLOW if the decoding
* action succeeds or more input is needed for completing the decoding action.
*/
final CoderResult decode(ByteBuffer source, CharBuffer target, IntBuffer offsets, boolean flush) {
/* check parameters */
if (target == null || source == null) {
throw new IllegalArgumentException();
}
/*
* Make sure that the buffer sizes do not exceed the number range for
* int32_t because some functions use the size (in units or bytes)
* rather than comparing pointers, and because offsets are int32_t values.
*
* size_t is guaranteed to be unsigned and large enough for the job.
*
* Return with an error instead of adjusting the limits because we would
* not be able to maintain the semantics that either the source must be
* consumed or the target filled (unless an error occurs).
* An adjustment would be sourceLimit=t+0x7fffffff; for example.
*/
/*agljport:fix
if(
((size_t)(sourceLimit-s)>(size_t)0x7fffffff && sourceLimit>s) ||
((size_t)(targetLimit-t)>(size_t)0x3fffffff && targetLimit>t)
) {
*err=U_ILLEGAL_ARGUMENT_ERROR;
return;
}
*/
/* flush the target overflow buffer */
if (charErrorBufferLength > 0) {
int i = 0;
do {
if (!target.hasRemaining()) {
/* the overflow buffer contains too much, keep the rest */
int j = 0;
do {
charErrorBufferArray[j++] = charErrorBufferArray[i++];
} while (i < charErrorBufferLength);
charErrorBufferLength = (byte) j;
return CoderResult.OVERFLOW;
}
/* copy the overflow contents to the target */
target.put(charErrorBufferArray[i++]);
if (offsets != null) {
offsets.put(-1); /* no source index available for old output */
}
} while (i < charErrorBufferLength);
/* the overflow buffer is completely copied to the target */
charErrorBufferLength = 0;
}
if (!flush && !source.hasRemaining() && toULength == 0 && preToULength >= 0) {
/* the overflow buffer is emptied and there is no new input: we are done */
return CoderResult.UNDERFLOW;
}
/*
* Do not simply return with a buffer overflow error if
* !flush && t==targetLimit
* because it is possible that the source will not generate any output.
* For example, the skip callback may be called;
* it does not output anything.
*/
return toUnicodeWithCallback(source, target, offsets, flush);
}
/* Currently, we are not using offsets in ICU4J. */
/* private void updateOffsets(IntBuffer offsets,int length, int sourceIndex, int errorInputLength) {
int limit;
int delta, offset;
if(sourceIndex>=0) {
/*
* adjust each offset by adding the previous sourceIndex
* minus the length of the input sequence that caused an
* error, if any
*/
/* delta=sourceIndex-errorInputLength;
} else {
/*
* set each offset to -1 because this conversion function
* does not handle offsets
*/
/* delta=-1;
}
limit=offsets.position()+length;
if(delta==0) {
/* most common case, nothing to do */
/* } else if(delta>0) {
/* add the delta to each offset (but not if the offset is <0) */
/* while(offsets.position()<limit) {
offset=offsets.get(offsets.position());
if(offset>=0) {
offsets.put(offset+delta);
}
//FIXME: ++offsets;
}
} else /* delta<0 */ /* {
/*
* set each offset to -1 because this conversion function
* does not handle offsets
* or the error input sequence started in a previous buffer
*/
/* while(offsets.position()<limit) {
offsets.put(-1);
}
}
} */
final CoderResult toUnicodeWithCallback(ByteBuffer source, CharBuffer target, IntBuffer offsets, boolean flush){
int sourceIndex;
int errorInputLength;
boolean converterSawEndOfInput, calledCallback;
//int t=target.position();
int s=source.position();
/* variables for m:n conversion */
ByteBuffer replayArray = ByteBuffer.allocate(EXT_MAX_BYTES);
int replayArrayIndex = 0;
ByteBuffer realSource=null;
boolean realFlush=false;
int realSourceIndex=0;
CoderResult cr = CoderResult.UNDERFLOW;
/* get the converter implementation function */
sourceIndex=0;
if(preToULength>=0) {
/* normal mode */
} else {
/*
* Previous m:n conversion stored source units from a partial match
* and failed to consume all of them.
* We need to "replay" them from a temporary buffer and convert them first.
*/
realSource=source;
realFlush=flush;
realSourceIndex=sourceIndex;
//UConverterUtility.uprv_memcpy(replayArray, replayBegin, preToUArray, preToUBegin, -preToULength);
replayArray.put(preToUArray,0, -preToULength);
source=replayArray;
source.position(0);
source.limit(replayArrayIndex-preToULength);
flush=false;
sourceIndex=-1;
preToULength=0;
}
/*
* loop for conversion and error handling
*
* loop {
* convert
* loop {
* update offsets
* handle end of input
* handle errors/call callback
* }
* }
*/
for(;;) {
/* convert */
cr = decodeLoop(source, target, offsets, flush);
/*
* set a flag for whether the converter
* successfully processed the end of the input
*
* need not check cnv->preToULength==0 because a replay (<0) will cause
* s<sourceLimit before converterSawEndOfInput is checked
*/
converterSawEndOfInput= (cr.isUnderflow() && flush && source.remaining()==0 && toULength == 0);
/* no callback called yet for this iteration */
calledCallback=false;
/* no sourceIndex adjustment for conversion, only for callback output */
errorInputLength=0;
/*
* loop for offsets and error handling
*
* iterates at most 3 times:
* 1. to clean up after the conversion function
* 2. after the callback
* 3. after the callback again if there was truncated input
*/
for(;;) {
/* update offsets if we write any */
/* Currently offsets are not being used in ICU4J */
/* if(offsets!=null) {
int length=(target.position()-t);
if(length>0) {
updateOffsets(offsets, length, sourceIndex, errorInputLength);
/*
* if a converter handles offsets and updates the offsets
* pointer at the end, then pArgs->offset should not change
* here;
* however, some converters do not handle offsets at all
* (sourceIndex<0) or may not update the offsets pointer
*/
//TODO: pArgs->offsets=offsets+=length;
/* }
if(sourceIndex>=0) {
sourceIndex+=(source.position()-s);
}
} */
if(preToULength<0) {
/*
* switch the source to new replay units (cannot occur while replaying)
* after offset handling and before end-of-input and callback handling
*/
if(realSource==null)
{
realSource=source;
realFlush=flush;
realSourceIndex=sourceIndex;
//UConverterUtility.uprv_memcpy(replayArray, replayBegin, preToUArray, preToUBegin, -preToULength);
replayArray.put(preToUArray,0, -preToULength);
// reset position
replayArray.position(0);
source=replayArray;
source.limit(replayArrayIndex-preToULength);
flush=false;
if((sourceIndex+=preToULength)<0) {
sourceIndex=-1;
}
preToULength=0;
} else {
/* see implementation note before _fromUnicodeWithCallback() */
//agljport:todo U_ASSERT(realSource==NULL);
Assert.assrt(realSource==null);
}
}
/* update pointers */
s=source.position();
//t=target.position();
if(cr.isUnderflow()) {
if(s<source.limit())
{
/*
* continue with the conversion loop while there is still input left
* (continue converting by breaking out of only the inner loop)
*/
break;
} else if(realSource!=null) {
/* switch back from replaying to the real source and continue */
source = realSource;
flush=realFlush;
sourceIndex=realSourceIndex;
realSource=null;
break;
} else if(flush && toULength>0) {
/*
* the entire input stream is consumed
* and there is a partial, truncated input sequence left
*/
/* inject an error and continue with callback handling */
cr = CoderResult.malformedForLength(toULength);
calledCallback=false; /* new error condition */
} else {
/* input consumed */
if(flush) {
/*
* return to the conversion loop once more if the flush
* flag is set and the conversion function has not
* successfully processed the end of the input yet
*
* (continue converting by breaking out of only the inner loop)
*/
if(!converterSawEndOfInput) {
break;
}
/* reset the converter without calling the callback function */
implReset();
}
/* done successfully */
return cr;
}
}
/* U_FAILURE(*err) */
{
if( calledCallback || cr.isOverflow() ||
(cr.isMalformed() && cr.isUnmappable())
) {
/*
* the callback did not or cannot resolve the error:
* set output pointers and return
*
* the check for buffer overflow is redundant but it is
* a high-runner case and hopefully documents the intent
* well
*
* if we were replaying, then the replay buffer must be
* copied back into the UConverter
* and the real arguments must be restored
*/
if(realSource!=null) {
int length;
Assert.assrt(preToULength==0);
length = source.limit() - source.position();
if(length>0) {
//UConverterUtility.uprv_memcpy(preToUArray, preToUBegin, pArgs.sourceArray, pArgs.sourceBegin, length);
source.get(preToUArray, preToUBegin, length);
preToULength=(byte)-length;
}
}
return cr;
}
}
/* copy toUBytes[] to invalidCharBuffer[] */
errorInputLength=invalidCharLength=toULength;
if(errorInputLength>0) {
copy(toUBytesArray, 0, invalidCharBuffer, 0, errorInputLength);
}
/* set the converter state to deal with the next character */
toULength=0;
/* call the callback function */
cr = toCharErrorBehaviour.call(this, toUContext, source, target, offsets, invalidCharBuffer, errorInputLength, cr);
/*
* loop back to the offset handling
*
* this flag will indicate after offset handling
* that a callback was called;
* if the callback did not resolve the error, then we return
*/
calledCallback=true;
}
}
}
/*
* Returns the number of chars held in the converter's internal state
* because more input is needed for completing the conversion. This function is
* useful for mapping semantics of ICU's converter interface to those of iconv,
* and this information is not needed for normal conversion.
* @return The number of chars in the state. -1 if an error is encountered.
*/
/*public*/ int toUCountPending() {
if(preToULength > 0){
return preToULength ;
} else if(preToULength < 0){
return -preToULength;
} else if(toULength > 0){
return toULength;
} else {
return 0;
}
}
private void copy(byte[] src, int srcOffset, char[] dst, int dstOffset, int length) {
for(int i=srcOffset; i<length; i++){
dst[dstOffset++]=(char)(src[srcOffset++] & UConverterConstants.UNSIGNED_BYTE_MASK);
}
}
/*
* ONLY used by ToU callback functions.
* This function will write out the specified characters to the target
* character buffer.
* @return A CoderResult object that contains the error result when an error occurs.
*/
static final CoderResult toUWriteUChars( CharsetDecoderICU cnv,
char[] ucharsArray, int ucharsBegin, int length,
CharBuffer target, IntBuffer offsets, int sourceIndex) {
CoderResult cr = CoderResult.UNDERFLOW;
/* write UChars */
if(offsets==null) {
while(length>0 && target.hasRemaining()) {
target.put(ucharsArray[ucharsBegin++]);
--length;
}
} else {
/* output with offsets */
while(length>0 && target.hasRemaining()) {
target.put(ucharsArray[ucharsBegin++]);
offsets.put(sourceIndex);
--length;
}
}
/* write overflow */
if(length>0) {
cnv.charErrorBufferLength= 0;
cr = CoderResult.OVERFLOW;
do {
cnv.charErrorBufferArray[cnv.charErrorBufferLength++]=ucharsArray[ucharsBegin++];
} while(--length>0);
}
return cr;
}
/*
* This function will write out the Unicode substitution character to the
* target character buffer.
* Sub classes to override this method if required
* @param decoder
* @param source
* @param target
* @param offsets
* @return A CoderResult object that contains the error result when an error occurs.
*/
/* Note: Currently, this method is not being used because the callback method calls toUWriteUChars with
* the substitution characters. Will leave in here for the time being. To be removed later. (4.0)
*/
/*CoderResult cbToUWriteSub(CharsetDecoderICU decoder,
ByteBuffer source, CharBuffer target,
IntBuffer offsets){
String sub = decoder.replacement();
CharsetICU cs = (CharsetICU) decoder.charset();
if (decoder.invalidCharLength==1 && cs.subChar1 != 0x00) {
char[] subArr = new char[] { 0x1a };
return CharsetDecoderICU.toUWriteUChars(decoder, subArr, 0, sub
.length(), target, offsets, source.position());
} else {
return CharsetDecoderICU.toUWriteUChars(decoder, sub.toCharArray(),
0, sub.length(), target, offsets, source.position());
}
}*/
/**
* Returns the maxBytesPerChar value for the Charset that created this decoder.
* @return maxBytesPerChar
* @stable ICU 4.8
*/
public final float maxBytesPerChar() {
return ((CharsetICU)(this.charset())).maxBytesPerChar;
}
}