Author: davidb Date: Thu Apr 21 13:11:21 2016 New Revision: 1740300 URL: http://svn.apache.org/viewvc?rev=1740300&view=rev Log: Felix Converter Service - Update implementation to follow updated API
Added: felix/trunk/converter/src/main/java/org/osgi/service/converter/ConversionException.java Modified: felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonCodecImpl.java felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonEncodingImpl.java felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java felix/trunk/converter/src/main/java/org/osgi/service/converter/Converting.java felix/trunk/converter/src/main/java/org/osgi/service/converter/Encoding.java Modified: felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java?rev=1740300&r1=1740299&r2=1740300&view=diff ============================================================================== --- felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java (original) +++ felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java Thu Apr 21 13:11:21 2016 @@ -17,8 +17,6 @@ package org.apache.felix.converter.impl; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.Arrays; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @@ -62,27 +60,22 @@ public class AdapterImpl implements Adap return this; } - @Override - public <F, T> Adapter rule(Rule<F, T> rule) { + public <F, T> Adapter rule(TypeReference<F> fromRef, TypeReference<T> toRef, Function<F, T> toFun, Function<T, F> fromFun) { // TODO Auto-generated method stub return null; } @Override - public <F, T> Adapter rule(Function<F, T> toFun, Function<T, F> fromFun) { - Type[] t = toFun.getClass().getGenericInterfaces(); - - TypeVariable<?>[] tp = toFun.getClass().getTypeParameters(); - System.out.println("*** " + Arrays.toString(tp)); - - TypeReference<Map<String, Adapter>> tr = new TypeReference<Map<String,Adapter>>(){}; - System.out.println("### " + tr); - Type type = tr.getType(); - System.out.println("### " + type); + public <F, T> Adapter rule(Type fromType, Type toType, Function<F, T> toFun, Function<T, F> fromFun) { + // TODO Auto-generated method stub + return null; + } + @Override + public <F, T> Adapter rule(Rule<F, T> rule) { // TODO Auto-generated method stub - return this; + return null; } private class ConvertingWrapper implements Converting { @@ -95,6 +88,25 @@ public class AdapterImpl implements Adap } @Override + public Converting defaultValue(Object defVal) { + // TODO Auto-generated method stub + return this; + } + + @SuppressWarnings("unchecked") + @Override + public <T> T to(Class<T> cls) { + Type type = cls; + return (T) to(type); + } + + @SuppressWarnings("unchecked") + @Override + public <T> T to(TypeReference<T> ref) { + return (T) to(ref.getType()); + } + + @Override public Object to(Type type) { if (object != null) { Function<Object, Object> f = classRules.get(new TypePair(object.getClass(), type)); Modified: felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java?rev=1740300&r1=1740299&r2=1740300&view=diff ============================================================================== --- felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java (original) +++ felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java Thu Apr 21 13:11:21 2016 @@ -41,6 +41,7 @@ import java.util.Set; import org.osgi.service.converter.Converter; import org.osgi.service.converter.Converting; +import org.osgi.service.converter.TypeReference; public class ConvertingImpl implements Converting { private static final Map<Class<?>, Class<?>> boxedClasses; @@ -76,6 +77,26 @@ public class ConvertingImpl implements C } @Override + public Converting defaultValue(Object defVal) { + // TODO Auto-generated method stub + return this; + } + + @SuppressWarnings("unchecked") + @Override + public <T> T to(Class<T> cls) { + Type type = cls; + return (T) to(type); + } + + + @SuppressWarnings("unchecked") + @Override + public <T> T to(TypeReference<T> ref) { + return (T) to(ref.getType()); + } + + @Override public Object to(Type type) { Class<?> cls = null; Type[] typeArguments = null; @@ -132,6 +153,11 @@ public class ConvertingImpl implements C } } + @Override + public String toString() { + return to(String.class); + } + private Object convertArrayToSingleValue(Class<?> cls) { Object[] arr = (Object[]) object; if (arr.length == 0) @@ -148,143 +174,6 @@ public class ConvertingImpl implements C return converter.convert(coll.iterator().next()).to(cls); } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Object convertToMapType(Class<?> targetCls, Type[] typeArguments) { - if (Map.class.isAssignableFrom(targetCls)) - return convertToMap(targetCls, typeArguments); - else if (Dictionary.class.isAssignableFrom(targetCls)) - return new Hashtable(convertToMap(Map.class, typeArguments)); - return createProxy(targetCls); - } - - @SuppressWarnings("rawtypes") - private Object createProxy(Class<?> targetCls) { - Map m = mapView(object); - return Proxy.newProxyInstance(targetCls.getClassLoader(), new Class[] {targetCls}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - String propName = getAccessorPropertyName(method); - if (propName == null) - return null; - - Class<?> targetType = method.getReturnType(); - - return converter.convert(m.get(propName)).to(targetType); - } - }); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private Map convertToMap(Class<?> targetCls, Type[] typeArguments) { - Map m = mapView(object); - if (m == null) - return null; - Class<?> targetKeyType = null, targetValueType = null; - if (typeArguments != null && typeArguments.length > 1 && - typeArguments[0] instanceof Class && typeArguments[1] instanceof Class) { - targetKeyType = (Class<?>) typeArguments[0]; - targetValueType = (Class<?>) typeArguments[1]; - } - - Class<?> ctrCls = interfaceImplementations.get(targetCls); - if (ctrCls == null) - ctrCls = targetCls; - - Map instance = (Map) createMapOrCollection(ctrCls, m.size()); - if (instance == null) - return null; - - for (Map.Entry entry : (Set<Entry>) m.entrySet()) { - Object key = entry.getKey(); - if (targetKeyType != null) - key = converter.convert(key).to(targetKeyType); - Object value = entry.getValue(); - if (targetValueType != null) - value = converter.convert(value).to(targetValueType); - instance.put(key, value); - } - - return instance; - } - - private static Map<?,?> mapView(Object obj) { - if (obj instanceof Map) - return (Map<?,?>) obj; - else if (obj instanceof Dictionary) - return null; // TODO - else - return createMapFromBeanAccessors(obj); - } - - @SuppressWarnings("rawtypes") - private static Map createMapFromBeanAccessors(Object obj) { - Set<String> invokedMethods = new HashSet<>(); - - Map result = new HashMap(); - for (Method md : obj.getClass().getDeclaredMethods()) { - handleMethod(obj, md, invokedMethods, result); - } - for (Method md : obj.getClass().getMethods()) { - handleMethod(obj, md, invokedMethods, result); - } - - return result; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static void handleMethod(Object obj, Method md, Set<String> invokedMethods, Map res) { - String mn = md.getName(); - if (invokedMethods.contains(mn)) - return; // method with this name already invoked - - String propName = getAccessorPropertyName(md); - - try { - res.put(propName.toString(), md.invoke(obj)); - invokedMethods.add(mn); - } catch (Exception e) { - } - } - - private static String getAccessorPropertyName(Method md) { - if (md.getReturnType().equals(Void.class)) - return null; // not an accessor - - if (md.getParameterTypes().length > 0) - return null; // not an accessor - - if (Object.class.equals(md.getDeclaringClass())) - return null; // do not use any methods on the Object class as a accessor - - String mn = md.getName(); - int prefix; - if (mn.startsWith("get")) - prefix = 3; - else if (mn.startsWith("is")) - prefix = 2; - else - return null; // not an accessor prefix - - if (mn.length() <= prefix) - return null; // just 'get' or 'is': not an accessor - String propStr = mn.substring(prefix); - StringBuilder propName = new StringBuilder(propStr.length()); - propName.append(Character.toLowerCase(propStr.charAt(0))); - if (propStr.length() > 1) - propName.append(propStr.substring(1)); - - return propName.toString(); - } - - private boolean isMapType(Class<?> targetCls) { - // All interface types that are not Collections are treated as maps - if (targetCls.isInterface()) - return true; - else - return Dictionary.class.isAssignableFrom(targetCls); - } - @SuppressWarnings("unchecked") private <T> T convertToArray(Class<?> targetClass) { Collection<?> collectionView = collectionView(object); @@ -328,28 +217,64 @@ public class ConvertingImpl implements C return (T) instance; } - private static Object createMapOrCollection(Class<?> targetCls, int initialSize) { - try { - Constructor<?> ctor = targetCls.getConstructor(int.class); - return ctor.newInstance(initialSize); - } catch (Exception e1) { - try { - Constructor<?> ctor2 = targetCls.getConstructor(); - return ctor2.newInstance(); - } catch (Exception e2) { - e2.printStackTrace(); - } + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Map convertToMap(Class<?> targetCls, Type[] typeArguments) { + Map m = mapView(object); + if (m == null) + return null; + Class<?> targetKeyType = null, targetValueType = null; + if (typeArguments != null && typeArguments.length > 1 && + typeArguments[0] instanceof Class && typeArguments[1] instanceof Class) { + targetKeyType = (Class<?>) typeArguments[0]; + targetValueType = (Class<?>) typeArguments[1]; } - return null; + + Class<?> ctrCls = interfaceImplementations.get(targetCls); + if (ctrCls == null) + ctrCls = targetCls; + + Map instance = (Map) createMapOrCollection(ctrCls, m.size()); + if (instance == null) + return null; + + for (Map.Entry entry : (Set<Entry>) m.entrySet()) { + Object key = entry.getKey(); + if (targetKeyType != null) + key = converter.convert(key).to(targetKeyType); + Object value = entry.getValue(); + if (targetValueType != null) + value = converter.convert(value).to(targetValueType); + instance.put(key, value); + } + + return instance; } - private static Collection<?> collectionView(Object obj) { - if (obj instanceof Collection) - return (Collection<?>) obj; - else if (obj instanceof Object[]) - return Arrays.asList((Object[]) obj); - else - return Collections.singleton(obj); + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Object convertToMapType(Class<?> targetCls, Type[] typeArguments) { + if (Map.class.isAssignableFrom(targetCls)) + return convertToMap(targetCls, typeArguments); + else if (Dictionary.class.isAssignableFrom(targetCls)) + return new Hashtable(convertToMap(Map.class, typeArguments)); + return createProxy(targetCls); + } + + @SuppressWarnings("rawtypes") + private Object createProxy(Class<?> targetCls) { + Map m = mapView(object); + return Proxy.newProxyInstance(targetCls.getClassLoader(), new Class[] {targetCls}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String propName = getAccessorPropertyName(method); + if (propName == null) + return null; + + Class<?> targetType = method.getReturnType(); + + return converter.convert(m.get(propName)).to(targetType); + } + }); } private Object handleNull(Class<?> cls) { @@ -365,6 +290,14 @@ public class ConvertingImpl implements C } } + private boolean isMapType(Class<?> targetCls) { + // All interface types that are not Collections are treated as maps + if (targetCls.isInterface()) + return true; + else + return Dictionary.class.isAssignableFrom(targetCls); + } + private Class<?> primitiveToBoxed(Class<?> cls) { Class<?> boxed = boxedClasses.get(cls); if (boxed != null) @@ -441,8 +374,96 @@ public class ConvertingImpl implements C return null; } - @Override - public String toString() { - return to(String.class); + private static Collection<?> collectionView(Object obj) { + if (obj instanceof Collection) + return (Collection<?>) obj; + else if (obj instanceof Object[]) + return Arrays.asList((Object[]) obj); + else + return Collections.singleton(obj); + } + + @SuppressWarnings("rawtypes") + private static Map createMapFromBeanAccessors(Object obj) { + Set<String> invokedMethods = new HashSet<>(); + + Map result = new HashMap(); + for (Method md : obj.getClass().getDeclaredMethods()) { + handleMethod(obj, md, invokedMethods, result); + } + for (Method md : obj.getClass().getMethods()) { + handleMethod(obj, md, invokedMethods, result); + } + + return result; + } + + private static Object createMapOrCollection(Class<?> targetCls, int initialSize) { + try { + Constructor<?> ctor = targetCls.getConstructor(int.class); + return ctor.newInstance(initialSize); + } catch (Exception e1) { + try { + Constructor<?> ctor2 = targetCls.getConstructor(); + return ctor2.newInstance(); + } catch (Exception e2) { + e2.printStackTrace(); + } + } + return null; + } + + private static String getAccessorPropertyName(Method md) { + if (md.getReturnType().equals(Void.class)) + return null; // not an accessor + + if (md.getParameterTypes().length > 0) + return null; // not an accessor + + if (Object.class.equals(md.getDeclaringClass())) + return null; // do not use any methods on the Object class as a accessor + + String mn = md.getName(); + int prefix; + if (mn.startsWith("get")) + prefix = 3; + else if (mn.startsWith("is")) + prefix = 2; + else + return null; // not an accessor prefix + + if (mn.length() <= prefix) + return null; // just 'get' or 'is': not an accessor + String propStr = mn.substring(prefix); + StringBuilder propName = new StringBuilder(propStr.length()); + propName.append(Character.toLowerCase(propStr.charAt(0))); + if (propStr.length() > 1) + propName.append(propStr.substring(1)); + + return propName.toString(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static void handleMethod(Object obj, Method md, Set<String> invokedMethods, Map res) { + String mn = md.getName(); + if (invokedMethods.contains(mn)) + return; // method with this name already invoked + + String propName = getAccessorPropertyName(md); + + try { + res.put(propName.toString(), md.invoke(obj)); + invokedMethods.add(mn); + } catch (Exception e) { + } + } + + private static Map<?,?> mapView(Object obj) { + if (obj instanceof Map) + return (Map<?,?>) obj; + else if (obj instanceof Dictionary) + return null; // TODO + else + return createMapFromBeanAccessors(obj); } } Modified: felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonCodecImpl.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonCodecImpl.java?rev=1740300&r1=1740299&r2=1740300&view=diff ============================================================================== --- felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonCodecImpl.java (original) +++ felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonCodecImpl.java Thu Apr 21 13:11:21 2016 @@ -77,8 +77,12 @@ public class JsonCodecImpl implements Co } @Override - public void to(OutputStream os) throws IOException { - os.write(toString().getBytes(StandardCharsets.UTF_8)); + public void to(OutputStream os) { + try { + os.write(toString().getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override @@ -97,7 +101,7 @@ public class JsonCodecImpl implements Co } @Override - public void to(OutputStream out, Charset charset) throws IOException { + public void to(OutputStream out, Charset charset) { // TODO Auto-generated method stub } Modified: felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonEncodingImpl.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonEncodingImpl.java?rev=1740300&r1=1740299&r2=1740300&view=diff ============================================================================== --- felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonEncodingImpl.java (original) +++ felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/JsonEncodingImpl.java Thu Apr 21 13:11:21 2016 @@ -43,8 +43,12 @@ public class JsonEncodingImpl implements } @Override - public void to(OutputStream os) throws IOException { - os.write(encode(object).getBytes(StandardCharsets.UTF_8)); + public void to(OutputStream os) { + try { + os.write(encode(object).getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override Modified: felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java?rev=1740300&r1=1740299&r2=1740300&view=diff ============================================================================== --- felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java (original) +++ felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java Thu Apr 21 13:11:21 2016 @@ -15,19 +15,20 @@ */ package org.osgi.service.converter; +import java.lang.reflect.Type; import java.util.function.Function; /** * An {@link Adapter} is used to modify the behaviour of the Converter service, * which can be useful when some of the conversions should be done different to * the Converter Specification. - * + * * @author $Id:$ */ public interface Adapter extends Converter { /** * Specify a conversion rule by providing a rule object. - * + * * @param rule The conversion rule. * @return The current adapter, can be used to chain invocations. */ @@ -37,13 +38,13 @@ public interface Adapter extends Convert * Specify a rule for the conversion to and from two classes. The rule * specifies the conversion in both directions. This overload makes it easy * to provide the conversions as lambdas, for example: - * + * * <pre> * adapter.rule(String[].class, String.class, * v -> Stream.of(v).collect(Collectors.joining(",")), * v -> v.split(",")); * </pre> - * + * * @param <F> the type to convert from. * @param <T> the type to convert to. * @param fromCls the class to convert from. @@ -55,19 +56,9 @@ public interface Adapter extends Convert <F, T> Adapter rule(Class<F> fromCls, Class<T> toCls, Function<F, T> toFun, Function<T, F> fromFun); - /** - * Specify a rule for the conversion to and from two classes. The rule - * specifies the conversion in both directions. This overload makes it easy - * to provide the conversions as method references. - * - * @param <F> the type to convert from. - * @param <T> the type to convert to. - * @param toFun the function to perform the conversion. - * @param fromFun the function to perform the reverse conversion. - * @return The current adapter, can be used to chain invocations. - */ - <F, T> Adapter rule(Function<F, T> toFun, Function<T, F> fromFun); + <F, T> Adapter rule(TypeReference<F> fromRef, TypeReference<T> toRef, + Function<F, T> toFun, Function<T, F> fromFun); -// <F, T> Adapter rule(Function<TypeReference<F>, TypeReference<T>> toFun, -// Function<TypeReference<T>,TypeReference<F>> fromFun); + <F, T> Adapter rule(Type fromType, Type toType, + Function<F, T> toFun, Function<T, F> fromFun); } Added: felix/trunk/converter/src/main/java/org/osgi/service/converter/ConversionException.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/osgi/service/converter/ConversionException.java?rev=1740300&view=auto ============================================================================== --- felix/trunk/converter/src/main/java/org/osgi/service/converter/ConversionException.java (added) +++ felix/trunk/converter/src/main/java/org/osgi/service/converter/ConversionException.java Thu Apr 21 13:11:21 2016 @@ -0,0 +1,41 @@ +/* + * Copyright (c) OSGi Alliance (2016). All Rights Reserved. + * + * 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 org.osgi.service.converter; + +/** + * This Runtime Exception is thrown when an object is requested to be converted but the conversion + * cannot be done. For example when the String "test" is to be converted into a Long. + */ +public class ConversionException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * Create a Conversion Exception with a message. + * @param message The message for this exception. + */ + public ConversionException(String message) { + super(message); + } + + /** + * Create a Conversion Exception with a message and a nested cause. + * @param message The message for this exception. + * @param cause The causing exception. + */ + public ConversionException(String message, Throwable cause) { + super(message, cause); + } +} Modified: felix/trunk/converter/src/main/java/org/osgi/service/converter/Converting.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/osgi/service/converter/Converting.java?rev=1740300&r1=1740299&r2=1740300&view=diff ============================================================================== --- felix/trunk/converter/src/main/java/org/osgi/service/converter/Converting.java (original) +++ felix/trunk/converter/src/main/java/org/osgi/service/converter/Converting.java Thu Apr 21 13:11:21 2016 @@ -25,17 +25,21 @@ import java.lang.reflect.Type; * @author $Id:$ */ public interface Converting { - /** + /** + * The default value to use when the object cannot be converted or in case of conversion + * from a {@code null} value. + * @param defVal The default value. + * @return The current {@code Converting} object so that additional calls can be chained. + */ + Converting defaultValue(Object defVal); + + /** * Specify the target object type for the conversion as a class object. * * @param cls The class to convert to. * @return The converted object. */ - @SuppressWarnings("unchecked") - default <T> T to(Class<T> cls) { - Type type = cls; - return (T) to(type); - } + <T> T to(Class<T> cls); /** * Specify the target object type as a {@link TypeReference}. If the target @@ -52,13 +56,10 @@ public interface Converting { * @param ref A type reference to the object being converted to. * @return The converted object. */ - @SuppressWarnings("unchecked") - default <T> T to(TypeReference<T> ref) { - return (T) to(ref.getType()); - } + <T> T to(TypeReference<T> ref); /** - * Specify the target object type as a Java Refelection Type object. + * Specify the target object type as a Java Reflection Type object. * * @param type A Type object to represent the target type to be converted * to. @@ -67,7 +68,7 @@ public interface Converting { Object to(Type type); /** - * Same as to(String.class) + * Same as {@code to(String.class)}. * @return The converted object. */ @Override Modified: felix/trunk/converter/src/main/java/org/osgi/service/converter/Encoding.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/osgi/service/converter/Encoding.java?rev=1740300&r1=1740299&r2=1740300&view=diff ============================================================================== --- felix/trunk/converter/src/main/java/org/osgi/service/converter/Encoding.java (original) +++ felix/trunk/converter/src/main/java/org/osgi/service/converter/Encoding.java Thu Apr 21 13:11:21 2016 @@ -15,7 +15,6 @@ */ package org.osgi.service.converter; -import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; @@ -36,11 +35,11 @@ public interface Encoding { /** * Use an output stream as the target of the encoding operation. UTF-8 will - * be used. + * be used, if applicable. * * @param out The output stream to use. */ - void to(OutputStream out) throws IOException; + void to(OutputStream out); /** * Use an output stream as the target of the encoding operation. @@ -48,7 +47,7 @@ public interface Encoding { * @param out The output stream to use. * @param charset The character set to use. */ - void to(OutputStream out, Charset charset) throws IOException; + void to(OutputStream out, Charset charset); /** * Encode the object and append the result to an appendable.