jnr.ffi.provider.converters.CharSequenceParameterConverter Maven / Gradle / Ivy
/*
* Copyright (C) 2012 Wayne Meissner
*
* This file is part of the JNR project.
*
* 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 jnr.ffi.provider.converters;
import jnr.ffi.annotations.Encoding;
import jnr.ffi.annotations.In;
import jnr.ffi.annotations.NulTerminate;
import jnr.ffi.mapper.MethodParameterContext;
import jnr.ffi.mapper.ToNativeContext;
import jnr.ffi.mapper.ToNativeConverter;
import java.lang.annotation.Annotation;
import java.lang.ref.Reference;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.util.Arrays;
import java.util.Collection;
import static jnr.ffi.provider.converters.StringUtil.getEncoder;
import static jnr.ffi.provider.converters.StringUtil.throwException;
/**
* Converts a CharSequence (e.g. String) to a primitive ByteBuffer array parameter
*/
@ToNativeConverter.NoContext
@ToNativeConverter.Cacheable
public class CharSequenceParameterConverter implements ToNativeConverter {
private static final ToNativeConverter DEFAULT = new CharSequenceParameterConverter(Charset.defaultCharset());
private final ThreadLocal> localEncoder = new ThreadLocal>();
private final Charset charset;
public static ToNativeConverter getInstance(Charset charset, ToNativeContext toNativeContext) {
return Charset.defaultCharset().equals(charset) ? DEFAULT : new CharSequenceParameterConverter(charset);
}
public static ToNativeConverter getInstance(ToNativeContext toNativeContext) {
Charset charset = Charset.defaultCharset();
if (toNativeContext instanceof MethodParameterContext) {
// See if the interface class has a global @Encoding declaration
Charset cs = getEncodingCharset(Arrays.asList(((MethodParameterContext) toNativeContext).getMethod().getDeclaringClass().getAnnotations()));
if (cs != null) {
charset = cs;
}
// Allow each method to override the default
cs = getEncodingCharset(Arrays.asList(((MethodParameterContext) toNativeContext).getMethod().getAnnotations()));
if (cs != null) {
charset = cs;
}
}
// Override on a per-parameter basis
Charset cs = getEncodingCharset(toNativeContext.getAnnotations());
if (cs != null) {
charset = cs;
}
return getInstance(charset, toNativeContext);
}
private static Charset getEncodingCharset(Collection annotations) {
for (Annotation a : annotations) {
if (a instanceof Encoding) {
return Charset.forName(((Encoding) a).value());
}
}
return null;
}
private CharSequenceParameterConverter(Charset charset) {
this.charset = charset;
}
@Override
public ByteBuffer toNative(CharSequence string, ToNativeContext context) {
if (string == null) {
return null;
}
CharsetEncoder encoder = getEncoder(charset, localEncoder);
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[(int) (string.length() * encoder.averageBytesPerChar()) + 4]);
CharBuffer charBuffer = CharBuffer.wrap(string);
encoder.reset();
while (charBuffer.hasRemaining()) {
CoderResult result = encoder.encode(charBuffer, byteBuffer, true);
if (result.isUnderflow() && (result = encoder.flush(byteBuffer)).isUnderflow()) {
break;
} else if (result.isOverflow()) {
// Output buffer is full; expand and continue encoding
byteBuffer = grow(byteBuffer);
} else {
throwException(result);
}
}
// ensure native memory is NUL terminated (assume max wchar_t 4 byte termination needed)
if (byteBuffer.remaining() <= 4) byteBuffer = grow(byteBuffer);
byteBuffer.position(byteBuffer.position() + 4);
byteBuffer.flip();
return byteBuffer;
}
private static ByteBuffer grow(ByteBuffer oldBuffer) {
ByteBuffer buf = ByteBuffer.wrap(new byte[oldBuffer.capacity() * 2]);
oldBuffer.flip();
buf.put(oldBuffer);
return buf;
}
@Override
@In
@NulTerminate
public Class nativeType() {
return ByteBuffer.class;
}
}