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.
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.
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
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.
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
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.
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
andjava.util.Date
are converted toString
as described in Date and Calendar. -
Map.Entry
is converter to String according to the rules in Map.Entry.
Conversion from String is done by attempting to invoke the following methods, in order:
-
public static valueOf(String s)
-
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.
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()
.
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.
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:
-
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.
-
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.
-
If one of the key or value is of type
String
, this is used and converted to the target type. If both are of typeString
the key is used. -
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.
This section describes conversions from, to and between Arrays and Collections. This includes Lists, Sets, Queues and Double-ended Queues (Deques).
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 achar[]
orCharacter[]
will result in an array with characters representing the characters in the String.
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[]
orCharacter[]
into aString
results in a String where each character represents the elements of the character array.
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 String
s into a
list of Integer
s:
List<Integer> result =
converter.convert(Arrays.asList("1","2","3")).
to(new TypeReference<List<Integer>>() {});
The following example converts an array of int
s into a
set of Double
s. 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 aMap
(if needed) and then callingMap.entrySet()
. EachMap.Entry
element is then converted into the target type as described in Map.Entry before inserting in the target.
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.
Conversions from a scalar to a map-like type are not supported by the standard converter.
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.
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>>(){})
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.
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 constantString
, then the key name is prefixed with the value of thePREFIX_
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 constantString
, then the id is prefixed with the value of thePREFIX_
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 |
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.
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.
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.
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.
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.
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();
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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)"
-
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.
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.
The message for this exception.
Create a Conversion Exception with a message.
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
The object that should be converted.
Start a conversion for the given object.
A Converting object to complete the conversion.
Start defining a function that can perform given conversions.
A Functioning object to complete the definition.
Obtain a builder to create a modified converter based on this converter. For more details see the ConverterBuilder interface.
A new Converter Builder.
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
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.
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.
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.
A rule implementation.
Register a conversion rule for this converter.
This converter builder for further building.
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.
Special object to indicate that a custom converter rule or error handler cannot handle the conversion.
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.
Factory class to obtain the standard converter or a new converter builder.
Thread-safe
Obtain a converter builder based on the standard converter.
A new converter builder.
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
<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.
<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.
<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.
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
<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.
<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.
<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.
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.
The conversion function to use.
Create an instance with a conversion function.
The function to perform the conversion.
The function.
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
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.
When converting between map-like types use case-insensitive mapping of keys.
The current Converting
object so that additional calls
can be chained.
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.
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.
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.
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.
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.
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.
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.
Interface for custom conversion rules.
The function to perform the conversion.
The function.
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
A TypeReference cannot be directly instantiated. To use it, it has to be extended, typically as an anonymous inner class.
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.
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.
The function to perform the conversion.
The function.
[1]The Java Language Specification, Java SE 8 Editionhttps://docs.oracle.com/javase/specs/jls/se8/html/index.html