707 Converter Specification

707.1 Introduction

Data conversion is an inherent part of writing software in a type safe language. In Java, converting strings to proper types or to convert one type to a more convenient type is often done manually. Any errors are then handled inline.

In release 6, the OSGi specifications introduced Data Transfer Objects (DTOs). DTOs are public objects without open generics that only contain public instance fields based on simple types, arrays, and collections. In many ways DTOs can be used as an alternative to Java beans. Java beans are hiding their fields and provide access methods which separates the contract (the public interface) from the internal usage. Though this model has advantages in technical applications it tends to add overhead. DTOs unify the specification with the data since the data is what is already public when it is sent to another process or serialized.

This specification defines the OSGi Converter that makes it easy to convert many types to other types, including scalars, Collections, Maps, Beans, Interfaces and DTOs without having to write the boilerplate conversion code. The converter strictly adheres to the rules specified in this chapter. Converters can also be customized using converter builders.

707.2 Entities

The following entities are used in this specification:

  • Converter - a converter can perform conversion operations.

  • Standard Converter - a converter implementation that follows this specification.

  • Converter Builder - can create customized converters by specifying rules for specific conversions.

  • Source - the object to be converted.

  • Target - the target of the conversion.

  • Source Type - the type of the source to be converted.

  • Target Type - the desired type of the conversion target.

  • Rule - a rule is used to customize the behavior of the converter.

Figure 707.1 Converter Entity overview

Converter Entity overview

707.3 Standard Converter

The Standard Converter is a converter that follows precisely what is described in this specification. It converts source objects to the desired target type if a suitable conversion is available. An instance can be obtained by calling the static standardConverter() method on the Converters class.

Some example conversions:

Converter c = Converters.standardConverter();
    
// Scalar conversions    
MyEnum e = c.convert(MyOtherEnum.BLUE).to(MyEnum.class);
BigDecimal bd = c.convert(12345).to(BigDecimal.class);

// Collection/array conversions
List<String> ls = Arrays.asList("978", "142", "-99");
long[] la = c.convert(ls).to(long[].class);

// Map conversions
Map someMap = new HashMap();
someMap.put("timeout", "700"); 
MyInterface mi = c.convert(someMap).to(MyInterface.class);
int t = mi.timeout(); // t=700

707.4 Conversions

For scalars, conversions are only performed when the target type is not compatible with the source type. For example, when requesting to convert a java.math.BigDecimal to a java.lang.Number the big decimal is simply used as-is as this type is assignable to the requested target type.

In the case of arrays, Collections and Map-like structures a new object is always returned, even if the target type is compatible with the source type. This copy can be owned and optionally further modified by the caller.

707.4.1 Generics

When converting to a target type with generic type parameters it is necessary to capture these to instruct the converter to produce the correct parameterized type. This can be achieved with the TypeReference based APIs, for example:

Converter c = Converters.standardConverter();
List<Long> list = c.convert("123").to(new TypeReference<List<Long>>(){});
// list will contain the Long value 123L

707.4.2 Scalars

707.4.2.1 Direct conversion between scalars

Direct conversion between the following scalars is supported:

Table 707.1 Scalar types that support direct conversions

to \ from Boolean Character Number null
boolean v.booleanValue() v.charValue() != 0 v.numberValue() != 0 false
char v.booleanValue() ? 1 : 0 v.charValue() (char) v.intValue() 0
number v.booleanValue() ? 1 : 0 (number) v.charValue() v.numberValue() 0

Where conversion is done from corresponding primitive types, these types are boxed before converting. Where conversion is done to corresponding boxed types, the types are boxed after converting.

Direct conversions between Enums and ints and between Dates and longs are also supported, see the sections below.

Conversions between from Map.Entry to scalars follow special rules, see Map.Entry.

All other conversions between scalars are done by converting the source object to a String first and then converting the String value to the target type.

707.4.2.2 Conversion to String

Conversion of scalars to String is done by calling toString() on the object to be converted. In the case of a primitive type, the object is boxed first.

A null object results in a null String value.

Exceptions:

  • java.util.Calendar and java.util.Date are converted to String as described in Date and Calendar.

  • Map.Entry is converter to String according to the rules in Map.Entry.

707.4.2.3 Conversion from String

Conversion from String is done by attempting to invoke the following methods, in order:

  1. public static valueOf(String s)

  2. public constructor taking a single String argument.

Some scalars have special rules for converting from String values. See below.

Table 707.2 Special cases converting to scalars from String

Target Method
char / Character v.length() > 0 ? v.charAt(0) : 0
java.time.Duration Duration.parse(v)
java.time.Instant Instant.parse(v)
java.time.LocalDate LocalDate.parse(v)
java.time.LocalDateTime LocalDateTime.parse(v)
java.time.LocalTime LocalTime.parse(v)
java.time.MonthDay MonthDay.parse(v)
java.time.OffsetTime OffsetTime.parse(v)
java.time.OffsetDateTime OffsetDateTime.parse(v)
java.time.Year Year.parse(v)
java.time.YearMonth YearMonth.parse(v)
java.time.ZonedDateTime ZonedDateTime.parse(v)
java.util.Calendar See Date and Calendar.
java.util.Date See Date and Calendar.
java.util.UUID UUID.fromString(v)
java.util.regex.Pattern Pattern.compile(v)

Note to implementors: some of the classes mentioned in table Table 707.2 are introduced in Java 8. However, a converter implementation does not need to depend on Java 8 in order to function. An implementation of the converter specification could determine its Java runtime dynamically and handle classes in this table depending on availability.

707.4.2.4 Date and Calendar

A java.util.Date instance is converted to a long value by calling Date.getTime(). Converting a long into a java.util.Date is done by calling new Date(long).

Converting a Date to a String will produce a ISO-8601 UTC date/time string in the following format: 2011-12-03T10:15:30Z. In Java 8 this can be done by calling Date.toInstant().toString(). Converting a String to a Date is done by parsing this ISO-8601 format back into a Date. In Java 8 this function is performed by calling Date.from(Instant.parse(v)).

Conversions from Calendar objects are done by converting the Calendar to a Date via getTime() first, and then converting the resulting Date to the target type. Conversions to a Calendar object are done by converting the source to a Date object with the desired time (always in UTC) and then setting the time in the Calendar object via setTime().

707.4.2.5 Enums

Conversions to Enum types are supported as follows.

Table 707.3 Converting to Enum types

Source Method
Number EnumType.values()[v.intValue()]
String EnumType.valueOf(v). If this does not produce a result a case-insensitive lookup is done for a matching enum value.

Primitives are boxed before conversion is done. Other source types are converted to String before converting to Enum.

707.4.2.6 Map.Entry

Conversion of Map.Entry<K,V> to a target scalar type is done by evaluating the compatibility of the target type with both the key and the value in the entry and then using the best match. This is done in the following order:

  1. If one of the key or value is the same as the target type, then this is used. If both are the same, the key is used.

  2. If one of the key or value type is assignable to the target type, then this is used. If both are assignable the key is used.

  3. If one of the key or value is of type String, this is used and converted to the target type. If both are of type String the key is used.

  4. If none of the above matches the key is converted into a String and this value is then converted to the target type.

Note that when applying these rules care must be taken with null keys and values. A null is not an Object and therefore has no type. As a result null is never the same type as, or type assignable to, a target type.

Conversion to Map.Entry from a scalar is not supported.

707.4.2.7 Optional types

The optional types are Optional, OptionalInt, OptionalLong and OptionalDouble. Conversion to an optional type is supported as follows:

  1. Identify the target type of the optional - for OptionalInt, OptionalLong and OptionalDouble this is the wrapper type for the target primitive. For Optional it is the generic type.

  2. Attempt to convert the value to the target type, throwing a ConversionException if conversion fails

  3. If the converted result is null, return the relevant empty optional type.

  4. If the converted result is not null, wrap it in the relevant optional value

Example: // String "12.3" must be converted to Double. // The Double must be wrapped into the Optional. String sValue="12.3"; Optional<Double> oDouble = converter.convert(sValue) .to(new TypeReference<Optional<Double>>() { });

707.4.3 Arrays and Collections

This section describes conversions from, to and between Arrays and Collections. This includes Lists, Sets, Queues and Double-ended Queues (Deques).

707.4.3.1 Converting from a scalar

Scalars are converted into a Collection or Array by creating an instance of the target type suitable for holding a single element. The scalar source object will be converted to target element type if necessary and then set as the element.

A null value will result in an empty Collection or Array.

Exceptions:

  • Converting a String to a char[] or Character[] will result in an array with characters representing the characters in the String.

707.4.3.2 Converting to a scalar

If a Collection or array needs to be converted to a scalar, the first element is taken and converted into the target type. Example:

Converter converter = Converters.standardConverter();
String s = converter.convert(new int[] {1,2}).to(String.class)); // s="1"

If the collection or array has no elements, the null value is used to convert into the target type.

Note: deviations from this mechanism can be achieved by using a ConverterBuilder. For example:

// Use an ConverterBuilder to create a customized converter          
ConverterBuilder cb = converter.newConverterBuilder();
cb.rule(new Rule<int[], String>(v -> Arrays.stream(v).
    mapToObj(Integer::toString).collect(Collectors.joining(","))) {});
cb.rule(new Rule<String, int[]>(v -> Arrays.stream(v.split(",")).
    mapToInt(Integer::parseInt).toArray()) {});
Converter c = cb.build();

String s2 = c.convert(new int[] {1,2}).to(String.class)); // s2="1,2"
int[] sa = c.convert("1,2").to(String[].class); // sa={1,2}

Exceptions:

  • Converting a char[] or Character[] into a String results in a String where each character represents the elements of the character array.

707.4.3.3 Converting to an Array or Collection

When converting to an Array or Collection a separate instance is returned that can be owned by the caller. By default the result is created eagerly and populated with the converted content.

When converting to a java.util.Collection, java.util.List or java.util.Set the converter can produce a live view over the backing object that changes when the backing object changes. The live view can be enabled by specifying the view() modifier.

In all cases the object returned is a separate instance that can be owned by the client. Once the client modifies the returned object a live view will stop reflecting changes to the backing object.

Table 707.4 Collection / Array target creation

Target Method
Collection interface A mutable implementation is created. For example, if the target type is java.util.Queue then the converter can create a java.util.LinkedList. When converting to a subinterface of java.util.Set the converter must choose a set implementation that preserves iteration order.
Collection concrete type A new instance is created by calling Class.newInstance() on the provided type. For example if the target type is ArrayDeque then the converter creates a target object by calling ArrayDeque.class.newInstance(). The converter may choose to use a call a well-known constructor to optimize the creation of the collection.
Collection, List or Set with view() modifier A live view over the backing object is created, changes to the backing object will be reflected, unless the view object is modified by the client.
T[] A new array is created via Array.newInstance(Class<T> cls, int x) where x is the required size of the target collection.

Before inserting values into the resulting collection/array they are converted to the desired target type. In the case of arrays this is the type of the array. When inserting into a Collection generic type information about the target type can be made available by using the to(TypeReference) or to(Type) methods. If no type information is available, source elements are inserted into the target object as-is without further treatment.

For example, to convert an array of Strings into a list of Integers:

List<Integer> result =
  converter.convert(Arrays.asList("1","2","3")).
    to(new TypeReference<List<Integer>>() {});

The following example converts an array of ints into a set of Doubles. Note that the resulting set must preserve the same iteration order as the original array:

Set<Double> result =
  converter.convert(new int[] {2,3,2,1}).
    to(new TypeReference<Set<Double>>() {})
// result is 2.0, 3.0, 1.0

Values are inserted in the target Collection/array as follows:

  • If the source object is null, an empty collection/array is produced.

  • If the source is a Collection or Array, then each of its elements is converted into desired target type, if known, before inserting. Elements are inserted into the target collection in their normal iteration order.

  • If the source is a Map-like structure (as described in Maps, Interfaces, Java Beans, DTOs and Annotations) then Map.Entry elements are obtained from it by converting the source to a Map (if needed) and then calling Map.entrySet(). Each Map.Entry element is then converted into the target type as described in Map.Entry before inserting in the target.

707.4.3.4 Converting to maps

Conversion to a map-like structure from an Array or Collection is not supported by the Standard Converter.

707.4.4 Maps, Interfaces, Java Beans, DTOs and Annotations

Entities that can hold multiple key-value pairs are all treated in a similar way. These entities include Maps, Dictionaries, Interfaces, Java Beans, Annotations and OSGi DTOs. We call these map-like types. Additionally objects that provide a map view via getProperties() are supported.

When converting between map-like types, a Map can be used as intermediary. When converting to other, non map-like, structures the map is converted into an iteration order preserving collection of Map.Entry values which in turn is converted into the target type.

707.4.4.1 Converting from a scalar

Conversions from a scalar to a map-like type are not supported by the standard converter.

707.4.4.2 Converting to a scalar

Conversions of a map-like structure to a scalar are done by iterating through the entries of the map and taking the first Map.Entry instance. Then this instance is converted into the target scalar type as described in Map.Entry.

An empty map results in a null scalar value.

707.4.4.3 Converting to an Array or Collection

A map-like structure is converted to an Array or Collection target type by creating an ordered collection of Map.Entry objects. Then this collection is converted to the target type as described in Arrays and Collections and Map.Entry.

Note that due to the rules for converting a Map.Entry into a scalar it is possible that the target Array or Collection may contain elements that were keys from the map, values from the map, or a mixture of the two. To be certain that only keys or values from the map will be used then it is recommended to first convert the map-like structure to a map, and then to convert the keySet or values from that Map into the required target type.

For example:

  Map<Integer,String> map = Map.of(1, "hi", 
        2, null,
        3, "ho");
        
  Converter c = Converters.standardConverter();
    
  List<String> result;
    
  // This result will be ["hi", "2", "ho"]
  result = c.convert(map).to(new TypeReference<List<String>>(){})

  // This result will be ["1", "2", "3"]
  result = c.convert(map.keySet()).to(new TypeReference<List<String>>(){})

  // This result will be ["hi", null, "ho"]
  result = c.convert(map.values()).to(new TypeReference<List<String>>(){})

707.4.4.4 Converting to a map-like structure

Conversions from one map-like structure to another map-like structure are supported. For example, conversions between a map and an annotation, between a DTO and a Java Bean or between one interface and another interface are all supported.

707.4.4.4.1 Key Mapping

When converting to or from a Java type, the key is derived from the method or field name. Certain common property name characters, such as full stop ('.' \u002E) and hyphen-minus ('-' \u002D) are not valid in Java identifiers. So the name of a method must be converted to its corresponding key name as follows:

  • A single dollar sign ('$' \u0024) is removed unless it is followed by:

    • A low line ('_' \u005F) and a dollar sign in which case the three consecutive characters ("$_$") are converted to a single hyphen-minus ('-' \u002D).

    • Another dollar sign in which case the two consecutive dollar signs ("$$") are converted to a single dollar sign.

  • A single low line ('_' \u005F) is converted into a full stop ('.' \u002E) unless is it followed by another low line in which case the two consecutive low lines ("__") are converted to a single low line.

  • All other characters are unchanged.

  • If the type that declares the method also declares a public static final PREFIX_ field whose value is a compile-time constant String, then the key name is prefixed with the value of the PREFIX_ field. PREFIX_ fields in super-classes or super-interfaces are ignored.

Table 707.5 contains some name mapping examples.

Table 707.5 Component Property Name Mapping Examples

Component Property Type Method Name Component Property Name
myProperty143 myProperty143
$new new
my$$prop my$prop
dot_prop dot.prop
_secret .secret
another__prop another_prop
three___prop three_.prop
four_$__prop four._prop
five_$_prop five..prop
six$_$prop six-prop
seven$$_$prop seven$.prop

Below is an example of using the PREFIX_ constant in an annotation. The example receives an untyped Dictionary in the updated() callback with configuration information. Each key in the dictionary is prefixed with the PREFIX_. The annotation can be used to read the configuration using typed methods with short names.

  public @interface MyAnnotation {
    static final String PREFIX_ = "com.acme.config.";

    long timeout() default 1000L;
    String tempdir() default "/tmp";
    int retries() default 10;
  }
  
  public void updated(Dictionary dict) {
    // dict contains:
    // "com.acme.config.timeout" = "500"
    // "com.acme.config.tempdir" = "/temp"
    
    MyAnnotation cfg = converter.convert(dict).to(MyAnnotation.class);
    
    long configuredTimeout = cfg.timeout(); // 500
    int configuredRetries = cfg.retries(); // 10
    
    // ...
  }

However, if the type is a single-element annotation, see 9.7.3 in [1] The Java Language Specification, Java SE 8 Edition, then the key name for the value method is derived from the name of the component property type rather than the name of the method. In this case, the simple name of the component property type, that is, the name of the class without any package name or outer class name, if the component property type is an inner class, must be converted to the value method's property name as follows:

  • When a lower case character is followed by an upper case character, a full stop ('.' \u002E) is inserted between them.

  • Each uppercase character is converted to lower case.

  • All other characters are unchanged.

  • If the annotation type declares a PREFIX_ field whose value is a compile-time constant String, then the id is prefixed with the value of the PREFIX_ field.

Table 707.6 contains some mapping examples for the value method.

Table 707.6 Single-Element Annotation Mapping Examples for value Method

Type Name value Method Component Property Name
ServiceRanking service.ranking
Some_Name some_name
OSGiProperty osgi.property

707.4.4.4.2 Converting to a Map

When converting to a Map a separate instance is returned that can be owned by the caller. By default the result is created eagerly and populated with converted content.

When converting to a java.util.Map the converter can produce a live view over the backing object that changes when the backing object changes. The live view can be enabled by specifying the view() modifier.

In all cases the object returned is a separate instance that can be owned by the client. When the client modifies the returned object a live view will stop reflecting changes to the backing object.

Table 707.7 Map target creation

Target Method
Map interface A mutable implementation is created. For example, if the target type is ConcurrentNavigableMap then the implementation can create a ConcurrentSkipListMap.
Map concrete type A new instance is created by calling Class.newInstance() on the provided type. For example if the target type is HashMap then the converter creates a target object by calling HashMap.class.newInstance(). The converter may choose to use a call a well-known constructor to optimize the creation of the map.
java.util.Map with view() modifier A map view over the backing object is created, changes to the backing object will be reflected in the map, unless the map is modified by the client.

When converting from a map-like object to a Map or sub-type, each key-value pair in the source map is converted to desired types of the target map using the generic information if available. Map type information for the target type can be made available by using the to(TypeReference) or to(Type) methods. If no type information is available, key-value pairs are used in the map as-is.

707.4.4.4.3 Dictionary

Converting between a map and a Dictionary is done by iterating over the source and inserting the key value pairs in the target, converting them to the requested target type, if known. As with other generic types, target type information for Dictionaries can be provided via a TypeReference.

707.4.4.4.4 Interface

Converting a map-like structure into an interface can be a useful way to give a map of untyped data a typed API. The converter synthesizes an interface instance to represent the conversion.

Note that converting to annotations provides similar functionality with the added benefit of being able to specify default values in the annotation code.

707.4.4.4.4.1 Converting to an Interface

When converting into an interface the converter will create a dynamic proxy to implement the interface. The name of the method returning the value should match the key of the map entry, taking into account the mapping rules specified in Key Mapping. The key of the map may need to be converted into a String first.

Conversion is done on demand: only when the method on the interface is actually invoked. This avoids conversion errors on methods for which the information is missing or cannot be converted, but which the caller does not require.

Note that the converter will not copy the source map when converting to an interface allowing changes to the source map to be reflected live to the proxy. The proxy cannot cache the conversions.

Interfaces can provide methods for default values by providing a single-argument method override in addition to the no-parameter method matching the key name. If the type of the default does not match the target type it is converted first. For example:

interface Config {
  int my_value(); // no default
  int my_value(int defVal);
  int my_value(String defVal); // String value is automatically converted to int
  boolean my_other_value();
}

// Usage
Map<String, Object> myMap = new HashMap<>(); // an example map
myMap.put("my.other.value", "true");
Config cfg = converter.convert(myMap).to(Config.class);
int val = cfg.my_value(17); // if not set then use 17
boolean val2 = cfg.my_other_value(); // val2=true

Default values are used when the key is not present in the map for the method. If a key is present with a null value, then null is taken as the value and converted to the target type.

If no default is specified and a requested value is not present in the map, a ConversionException is thrown.

707.4.4.4.4.2 Converting from an Interface

An interface can also be the source of a conversion to another map-like type. The name of each method without parameters is taken as key, taking into account the Key Mapping. The method is invoked using reflection to produce the associated value. Default methods must also be handled by the converter.

Whether a conversion source object is an interface is determined dynamically. When an object implements multiple interfaces by default the first interface from these that has no-parameter methods is taken as the source type. To select a different interface use the sourceAs(Class) modifier:

Map m = converter.convert(myMultiInterface).
      sourceAs(MyInterfaceB.class).to(Map.class);

If the source object also has a getProperties() method as described in Types with getProperties(), this getProperties() method is used to obtain the map view by default. This behavior can be overridden by using the sourceAs(Class) modifier.

707.4.4.4.5 Annotation

Conversion to and from annotations behaves similar to interface conversion with the added capability of specifying a default in the annotation definition.

When converting to an annotation type, the converter will return an instance of the requested annotation class. As with interfaces, values are only obtained from the conversion source when the annotation method is actually called. If the requested value is not available, the default as specified in the annotation class is used. If no default is specified a ConversionException is thrown.

Similar to interfaces, conversions to and from annotations also follow the Key Mapping for annotation element names. Below a few examples of conversions to an annotation:

@interface MyAnnotation {
  String[] args() default {"arg1", "arg2"};
}

// Will set sa={"args1", "arg2"}
String[] sa = converter.convert(new HashMap()).to(MyAnnotation.class).args();

// Will set a={"x", "y", "z"}
Map m = Collections.singletonMap("args", new String [] {"x", "y", "z"});
String[] a = converter.convert(m).to(MyAnnotation.class).args();

// Will set a1={}
Map m1 = Collections.singletonMap("args", null)
String[] a1 = converter.convert(m1).to(MyAnnotation.class).args();

// Will set a2={""}
Map m2 = Collections.singletonMap("args", "")
String[] a2 = converter.convert(m2).to(MyAnnotation.class).args();

// Will set a3={","}
Map m3 = Collections.singletonMap("args", ",")
String[] a3 = converter.convert(m3).to(MyAnnotation.class).args();
707.4.4.4.5.1 Marker annotations

If an annotation is a marker annotation, see 9.7.2 in [1] The Java Language Specification, Java SE 8 Edition, then the property name is derived from the name of the annotation, as described for single-element annotations in Key Mapping, and the value of the property is Boolean.TRUE.

When converting to a marker annotation the converter checks that the source has key and value that are consistent with the marker annotation. If they are not, for example if the value is not present or does not convert to Boolean.TRUE, then a conversion will result in a Conversion Exception.

707.4.4.4.6 Java Beans

Java Beans are concrete (non-abstract) classes that follow the Java Bean naming convention. They provide public getters and setters to access their properties and have a public no-parameter constructor. When converting from a Java Bean introspection is used to find the read accessors. A read accessor must have no arguments and a non-void return value. The method name must start with get followed by a capitalized property name, for example getSize() provides access to the property size. For boolean/Boolean properties a prefix of is is also permitted. Properties names follow the Key Mapping.

For the converter to consider an object as a Java Bean the sourceAsBean() or targetAsBean() modifier needs to be invoked, for example:

  Map m = converter.convert(myBean).sourceAsBean().to(Map.class);

When converting to a Java Bean, the bean is constructed eagerly. All available properties are set in the bean using the bean's write accessors, that is, public setters methods with a single argument. All methods of the bean class itself and its super classes are considered. When a property cannot be converted this will cause a ConversionException. If a property is missing in the source, the property will not be set in the bean.

Note: access via indexed bean properties is not supported.

Note: the getClass() method of the java.lang.Object class is not considered an accessor.

707.4.4.4.7 DTOs

DTOs are classes with public non-static fields and no methods other than the ones provided by the java.lang.Object class. OSGi DTOs extend the org.osgi.dto.DTO class, however objects following the DTO rules that do not extend the DTO class are also treated as DTOs by the converter. DTOs may have static fields, or non-public instance fields. These are ignored by the converter.

When converting from a DTO to another map-like structure each public instance field is considered. The field name is taken as the key for the map entry, taking into account Key Mapping, the field value is taken as the value for the map entry.

When converting to a DTO, the converter attempts to find fields that match the key of each entry in the source map and then converts the value to the field type before assigning it. The key of the map entries may need to be converted into a String first. Keys are mapped according to Key Mapping.

The DTO is constructed using its no-parameter constructor and each public field is filled with data from the source eagerly. Fields present in the DTO but missing in the source object not be set.

The converter only considers a type to be a DTO type if it declares no methods. However, if a type needs to be treated as a DTO that has methods, the converter can be instructed to do this using the sourceAsDTO() and targetAsDTO() modifiers.

707.4.4.4.8 Types with getProperties()

The converter uses reflection to find a public java.util.Map getProperties() or java.util.Dictionary getProperties() method on the source type to obtain a map view over the source object. This map view is used to convert the source object to a map-like structure.

If the source object both implements an interface and also has a public getProperties() method, the converter uses the getProperties() method to obtain the map view. This getProperties() may or may not be part of an implemented interface.

Note: this mechanism can only be used to convert to another type. The reverse is not supported

707.4.4.4.9 Specifying target types

The converter always produces an instance of the target type as specified with the to(Class), to(TypeReference) or to(Type) method. In some cases the converter needs to be instructed how to treat this target object. For example the desired target type might extend a DTO class adding some methods and behavior to the DTO. As this target class now has methods, the converter will not recognize it as a DTO. The targetAs(Class), targetAsBean() and targetAsDTO() methods can be used here to instruct the converter to treat the target object as certain type of object to guide the conversion.

For example:

  MyExtendedDTO med = converter.convert(someMap).
      targetAsDTO().to(MyExtendedDTO.class)

In this example the converter will return a MyExtendedDTO instance but it will treat is as a MyDTO type.

707.5 Repeated or Deferred Conversions

In certain situations the same conversion needs to be performed multiple times, on different source objects. Or maybe the conversion needs to be performed asynchronously as part of a async stream processing pipeline. For such cases the Converter can produce a Function, which will perform the conversion once applied. The function can be invoked multiple times with different source objects. The Converter can produce this function through the function() method, which provides an API similar to the convert(Object) method, with the difference that instead of returning the conversion, once to() is called, a Function that can perform the conversion on apply(T) is returned.

The following example sets up a Function that can perform conversions to Integer objects. A default value of 999 is specified for the conversion:

  Converter c = Converters.standardConverter();

  // Obtain a function for the conversion 
  Function<Object, Integer> cf = c.function().defaultValue(999).to(Integer.class);

  // Use the converter multiple times:
  Integer i1 = cf.apply("123"); // i1 = 123
  Integer i2 = cf.apply("");    // i2 = 999

The Function returned by the converter is thread safe and can be used concurrently or asynchronously in other threads.

707.6 Customizing converters

The Standard Converter applies the conversion rules described in this specification. While this is useful for many applications, in some cases deviations from the specified rules may be necessary. This can be done by creating a customized converter. Customized converters are created based on an existing converter with additional rules specified that override the existing converter's behavior. A customized converter is created through a ConverterBuilder. Customized converters implement the converter interface and as such can be used to create further customized converters. Converters are immutable, once created they cannot be modified, so they can be freely shared without the risk of modification to the converter's behavior.

For example converting a Date to a String may require a specific format. The default Date to String conversion produces a String in the format yyyy-MM-ddTHH:mm:ss.SSSZ. If we want to produce a String in the format yyMMddHHmmssZ instead a custom converter can be applied:

SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmssZ") {
  @Override
  public synchronized StringBuffer format(Date date, StringBuffer toAppendTo, 
                                          FieldPosition pos) {
    // Make the method synchronized to support multi threaded access
    return super.format(date, toAppendTo, pos);
  }
};
ConverterBuilder cb = Converters.newConverterBuilder();
cb.rule(new TypeRule<>(Date.class, String.class, sdf::format));
Converter c = cb.build();
    
String s = c.convert(new Date()).to(String.class);
// s = "160923102853+0100" or similar

Custom conversions are also applied to embedded conversions that are part of a map or other enclosing object:

class MyBean {
  //... fields ommitted
  boolean getEnabled() { /* ... */ }
  void setEnabled(boolean e)  { /* ... */ }
  Date getStartDate() { /* ... */ }
  void setStartDate(Date d) { /* ... */ }
}

MyBean mb = new MyBean();
mb.setStartDate(new Date());
mb.setEnabled(true);

Map<String, String> m = c.convert(mb).sourceAsBean().
    to(new TypeReference<Map<String, String>>(){});
String en = m.get("enabled");   // en = "true"
String sd = m.get("startDate"); // sd = "160923102853+0100" or similar

A converter rule can return CANNOT_HANDLE to indicate that it cannot handle the conversion, in which case next applicable rule is handed the conversion. If none of the registered rules for the current converter can handle the conversion, the parent converter object is asked to convert the value. Since custom converters can be the basis for further custom converters, a chain of custom converters can be created where a custom converter rule can either decide to handle the conversion, or it can delegate back to the next converter in the chain by returning CANNOT_HANDLE if it wishes to do so.

707.6.1 Catch-all rules

It is also possible to register converter rules which are invoked for every conversion with the rule(ConverterFunction) method. When multiple rules are registered, they are evaluated in the order of registration, until a rule indicates that it can handle a conversion. A rule can indicate that it cannot handle the conversion by returning the CANNOT_HANDLE constant. Rules targeting specific types are evaluated before catch-all rules.

707.7 Conversion failures

Not all conversions can be performed by the standard converter. It cannot convert text such as 'lorem ipsum' into a long value. Or the number pi into a map. When a conversion fails, the converter will throw a ConversionException.

If meaningful conversions exist between types not supported by the standard converter, a customized converter can be used, see Customizing converters.

Some applications require different behavior for error scenarios. For example they can use an empty value such as 0 or "" instead of the exception, or they might require a different exception to be thrown. For these scenarios a custom error handler can be registered. The error handler is only invoked in cases where otherwise a ConversionException would be thrown. The error handler can return a different value instead or throw another exception.

An error handler is registered by creating a custom converter and providing it with an error handler via the errorHandler(ConverterFunction) method. When multiple error handlers are registered for a given converter they are invoked in the order in which they were registered until an error handler either throws an exception or returns a value other than CANNOT_HANDLE.

707.8 Security

An implementation of this specification will require the use of Java Reflection APIs. Therefore it should have the appropriate permissions to perform these operations when running under the Java Security model.

707.9 org.osgi.util.converter

Version 1.0

Converter Package Version 1.0.

Bundles wishing to use this package must list the package in the Import-Package header of the bundle's manifest. This package has two types of users: the consumers that use the API in this package and the providers that implement the API in this package.

Example import for consumers using the API in this package:

Import-Package: org.osgi.util.converter; version="[1.0,2.0)"

Example import for providers implementing the API in this package:

Import-Package: org.osgi.util.converter; version="[1.0,1.1)"

707.9.1 Summary

  • ConversionException - This Runtime Exception is thrown when an object is requested to be converted but the conversion cannot be done.

  • Converter - The Converter service is used to start a conversion.

  • ConverterBuilder - A builder to create a new converter with modified behavior based on an existing converter.

  • ConverterFunction - An functional interface with a convert method that is passed the original object and the target type to perform a custom conversion.

  • Converters - Factory class to obtain the standard converter or a new converter builder.

  • Converting - This interface is used to specify the target that an object should be converted to.

  • Functioning - This interface is used to specify the target function to perform conversions.

  • Rule - A rule implementation that works by capturing the type arguments via subclassing.

  • Specifying - This is the base interface for the Converting and Functioning interfaces and defines the common modifiers that can be applied to these.

  • TargetRule - Interface for custom conversion rules.

  • TypeReference - An object does not carry any runtime information about its generic type.

  • TypeRule - Rule implementation that works by passing in type arguments rather than subclassing.

707.9.2 public class ConversionException
extends RuntimeException

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.

707.9.2.1 public ConversionException(String message)

The message for this exception.

Create a Conversion Exception with a message.

707.9.2.2 public ConversionException(String message, Throwable cause)

The message for this exception.

The causing exception.

Create a Conversion Exception with a message and a nested cause.

707.9.3 public interface Converter

The Converter service is used to start a conversion. The service is obtained from the service registry. The conversion is then completed via the Converting interface that has methods to specify the target type.

Thread-safe

Consumers of this API must not implement this type

707.9.3.1 public Converting convert(Object obj)

The object that should be converted.

Start a conversion for the given object.

A Converting object to complete the conversion.

707.9.3.2 public Functioning function()

Start defining a function that can perform given conversions.

A Functioning object to complete the definition.

707.9.3.3 public ConverterBuilder newConverterBuilder()

Obtain a builder to create a modified converter based on this converter. For more details see the ConverterBuilder interface.

A new Converter Builder.

707.9.4 public interface ConverterBuilder

A builder to create a new converter with modified behavior based on an existing converter. The modified behavior is specified by providing rules and/or conversion functions. If multiple rules match they will be visited in sequence of registration. If a rule's function returns null the next rule found will be visited. If none of the rules can handle the conversion, the original converter will be used to perform the conversion.

Consumers of this API must not implement this type

707.9.4.1 public Converter build()

Build the specified converter. Each time this method is called a new custom converter is produced based on the rules registered with the builder.

A new converter with the rules provided to the builder.

707.9.4.2 public ConverterBuilder errorHandler(ConverterFunction func)

The function to be used to handle errors.

Register a custom error handler. The custom error handler will be called when the conversion would otherwise throw an exception. The error handler can either throw a different exception or return a value to be used for the failed conversion.

This converter builder for further building.

707.9.4.3 public ConverterBuilder rule(Type type, ConverterFunction func)

The type that this rule will produce.

The function that will handle the conversion.

Register a conversion rule for this converter. Note that only the target type is specified, so the rule will be visited for every conversion to the target type.

This converter builder for further building.

707.9.4.4 public ConverterBuilder rule(TargetRule rule)

A rule implementation.

Register a conversion rule for this converter.

This converter builder for further building.

707.9.4.5 public ConverterBuilder rule(ConverterFunction func)

The function that will handle the conversion.

Register a catch-all rule, will be called of no other rule matches.

This converter builder for further building.

707.9.5 public interface ConverterFunction

An functional interface with a convert method that is passed the original object and the target type to perform a custom conversion.

This interface can also be used to register a custom error handler.

707.9.5.1 public static final Object CANNOT_HANDLE

Special object to indicate that a custom converter rule or error handler cannot handle the conversion.

707.9.5.2 public Object apply(Object obj, Type targetType) throws Exception

The object to be converted. This object will never be null as the convert function will not be invoked for null values.

The target type.

Convert the object into the target type.

The conversion result or CANNOT_HANDLE to indicate that the convert function cannot handle this conversion. In this case the next matching rule or parent converter will be given a opportunity to convert.

Exception– the operation can throw an exception if the conversion can not be performed due to incompatible types.

707.9.6 public class Converters

Factory class to obtain the standard converter or a new converter builder.

Thread-safe

707.9.6.1 public static ConverterBuilder newConverterBuilder()

Obtain a converter builder based on the standard converter.

A new converter builder.

707.9.6.2 public static Converter standardConverter()

Obtain the standard converter.

The standard converter.

707.9.7 public interface Converting
extends Specifying<Converting>

This interface is used to specify the target that an object should be converted to. A Converting instance can be obtained via the Converter.

Not Thread-safe

Consumers of this API must not implement this type

707.9.7.1 public T to(Class<T> cls)

<T>

The type to convert to.

The class to convert to.

Specify the target object type for the conversion as a class object.

The converted object.

707.9.7.2 public T to(Type type)

<T>

The type to convert to.

A Type object to represent the target type to be converted to.

Specify the target object type as a Java Reflection Type object.

The converted object.

707.9.7.3 public T to(TypeReference<T> ref)

<T>

The type to convert to.

A type reference to the object being converted to.

Specify the target object type as a TypeReference. If the target class carries generics information a TypeReference should be used as this preserves the generic information whereas a Class object has this information erased. Example use:

 List<String> result = converter.convert(Arrays.asList(1, 2, 3))
 		.to(new TypeReference<List<String>>() {
 		});

The converted object.

707.9.8 public interface Functioning
extends Specifying<Functioning>

This interface is used to specify the target function to perform conversions. This function can be used multiple times. A Functioning instance can be obtained via the Converter.

Not Thread-safe

Consumers of this API must not implement this type

707.9.8.1 public Function<Object, T> to(Class<T> cls)

<T>

The type to convert to.

The class to convert to.

Specify the target object type for the conversion as a class object.

A function that can perform the conversion.

707.9.8.2 public Function<Object, T> to(Type type)

<T>

The type to convert to.

A Type object to represent the target type to be converted to.

Specify the target object type as a Java Reflection Type object.

A function that can perform the conversion.

707.9.8.3 public Function<Object, T> to(TypeReference<T> ref)

<T>

The type to convert to.

A type reference to the object being converted to.

Specify the target object type as a TypeReference. If the target class carries generics information a TypeReference should be used as this preserves the generic information whereas a Class object has this information erased. Example use:

 List<String> result = converter.function()
 		.to(new TypeReference<List<String>>() {
 		});

A function that can perform the conversion.

707.9.9 public abstract class Rule<F, T>
implements TargetRule

The type to convert from.

The type to convert to.

A rule implementation that works by capturing the type arguments via subclassing. The rule supports specifying both from and to types. Filtering on the from by the Rule implementation. Filtering on the to is done by the converter customization mechanism.

707.9.9.1 public Rule(Function<F, T> func)

The conversion function to use.

Create an instance with a conversion function.

707.9.9.2 public ConverterFunction getFunction()

The function to perform the conversion.

The function.

707.9.9.3 public Type getTargetType()

The target type of this rule. The conversion function is invoked for each conversion to the target type.

The target type.

707.9.10 public interface Specifying<T extends Specifying<T>>

Either Converting or Specifying.

This is the base interface for the Converting and Functioning interfaces and defines the common modifiers that can be applied to these.

Not Thread-safe

Consumers of this API must not implement this type

707.9.10.1 public T extends Specifying<T> defaultValue(Object defVal)

The default value.

The default value to use when the object cannot be converted or in case of conversion from a null value.

The current Converting object so that additional calls can be chained.

707.9.10.2 public T extends Specifying<T> keysIgnoreCase()

When converting between map-like types use case-insensitive mapping of keys.

The current Converting object so that additional calls can be chained.

707.9.10.3 public T extends Specifying<T> sourceAs(Class<?> cls)

The class to treat the object as.

Treat the source object as the specified class. This can be used to disambiguate a type if it implements multiple interfaces or extends multiple classes.

The current Converting object so that additional calls can be chained.

707.9.10.4 public T extends Specifying<T> sourceAsBean()

Treat the source object as a JavaBean. By default objects will not be treated as JavaBeans, this has to be specified using this method.

The current Converting object so that additional calls can be chained.

707.9.10.5 public T extends Specifying<T> sourceAsDTO()

Treat the source object as a DTO even if the source object has methods or is otherwise not recognized as a DTO.

The current Converting object so that additional calls can be chained.

707.9.10.6 public T extends Specifying<T> targetAs(Class<?> cls)

The class to treat the object as.

Treat the target object as the specified class. This can be used to disambiguate a type if it implements multiple interfaces or extends multiple classes.

The current Converting object so that additional calls can be chained.

707.9.10.7 public T extends Specifying<T> targetAsBean()

Treat the target object as a JavaBean. By default objects will not be treated as JavaBeans, this has to be specified using this method.

The current Converting object so that additional calls can be chained.

707.9.10.8 public T extends Specifying<T> targetAsDTO()

Treat the target object as a DTO even if it has methods or is otherwise not recognized as a DTO.

The current Converting object so that additional calls can be chained.

707.9.10.9 public T extends Specifying<T> view()

Return a live view over the backing object that reflects any changes to the original object. This is only possible with conversions to java.util.Map, java.util.Collection, java.util.List and java.util.Set. The live view object will cease to be live as soon as modifications are made to it. Note that conversions to an interface or annotation will always produce a live view that cannot be modified. This modifier has no effect with conversions to other types.

The current Converting object so that additional calls can be chained.

707.9.11 public interface TargetRule

Interface for custom conversion rules.

707.9.11.1 public ConverterFunction getFunction()

The function to perform the conversion.

The function.

707.9.11.2 public Type getTargetType()

The target type of this rule. The conversion function is invoked for each conversion to the target type.

The target type.

707.9.12 public class TypeReference<T>

The target type for the conversion.

An object does not carry any runtime information about its generic type. However sometimes it is necessary to specify a generic type, that is the purpose of this class. It allows you to specify an generic type by defining a type T, then subclassing it. The subclass will have a reference to the super class that contains this generic information. Through reflection, we pick this reference up and return it with the getType() call.

 List<String> result = converter.convert(Arrays.asList(1, 2, 3))
 		.to(new TypeReference<List<String>>() {
 		});

Immutable

707.9.12.1 protected TypeReference()

A TypeReference cannot be directly instantiated. To use it, it has to be extended, typically as an anonymous inner class.

707.9.12.2 public Type getType()

Return the actual type of this Type Reference

the type of this reference.

707.9.13 public class TypeRule<F, T>
implements TargetRule

The type to convert from.

The type to convert to.

Rule implementation that works by passing in type arguments rather than subclassing. The rule supports specifying both from and to types. Filtering on the from by the Rule implementation. Filtering on the to is done by the converter customization mechanism.

707.9.13.1 public TypeRule(Type from, Type to, Function<F, T> func)

The type to convert from.

The type to convert to.

The conversion function to use.

Create an instance based on source, target types and a conversion function.

707.9.13.2 public ConverterFunction getFunction()

The function to perform the conversion.

The function.

707.9.13.3 public Type getTargetType()

The target type of this rule. The conversion function is invoked for each conversion to the target type.

The target type.

707.10 References

[1]The Java Language Specification, Java SE 8 Editionhttps://docs.oracle.com/javase/specs/jls/se8/html/index.html