saker.build Documentation TaskDoc JavaDoc Packages
public class DataConverterUtils
Utility class providing functionality for converting between different types of objects.

The main purpose of this class to provide methods for converting between different types. This functionality is required, as structures defined in build scripts might not be directly representable as Java objects. Furthermore, it may be necessary to convert between different versions of a class to avoid linkage errors.

The class can provide the following conversional functionalities:

  • Converting between String and primitive types.
  • Converting to arrays, or collections.
  • Converting to generic types in a limited manner.
  • Converting build system related structured task results and build target task results.
  • Converting to enums.
  • Enclosing Map instances into interfaces.
  • Adapting different interface class versions for compatibility conflicts.
  • Extending the conversion mechanism.
  • Converting objects based on their own declared methods.
Unless otherwise noted, all converted objects that conversion functions return in this class should be treated as immutable/unmodifiable, meaning, modifications to the returned objects do not propagate back to the converted value.

Calling functions on the returned objects from the conversion methods may throw ConversionFailedException at any time. This usually means that conversion of some nested elements failed.

Calling Object.equals(Object) and Object.hashCode() may not work properly on the returned converted objects, and clients should not rely on them. If they want to use these functions, they should clone the data contents of the converted objects into a representation of their own and use those for comparions.

Converting to primitive types works in the following way:

  1. If the value is a task result, it will be resolved.
  2. If the target type is Boolean then the value will be converted to string, and Boolean.valueOf(String) will be used.
  3. If the target type is Character, then the value will be converted to string, and the first character is returned. If the string is longer than 1 character, an exception is thrown.
  4. If the value is a Number, then the appropriate Number function will be called to convert to the target type. E.g. Number.intValue().
  5. Else the value is converted to string, and Long.valueOf(String) or Double.valueOf(String) will be used to parse it (based on the target type precision). Then the above conversion function is called as in Number conversion.
  6. In case of any format exceptions, a ConversionFailedException is thrown.
Converting to arrays or collections work in a way that every element of the value should be converted to the target element type. This usually requires generic information about the collections to be present. The class allows wrapping a single value into singleton collections/arrays. The conversion between collection types are usually delayed, meaning that the element conversions are only executed when the actual elements are accessed. The returned collections are generally unmodifiable.

Users should avoiding conversions to the type Set as the returned object might violate the requirements of that class. As element conversions are delayed, it is possible that there are elements in the returned set that equal. It is recommended that users convert to Collection or List classes when they want to use collections. They should handle the case of duplicates gracefully.

Arrays and collections are convertible vice-versa. The class is able to handle arrays of generic type, by converting each array element to the generic component type.

The class is able to convert StructuredListTaskResult to collection instances.

For collections, maps can only be converted to other maps with different generic arguments. The converted maps are not recommended do be accessed via Map.get(Object), but they only should be enumerated. The class is able to convert StructuredMapTaskResult to map instances.

When converting to collection or map types, it is recommended to only use the interfaces as the targe type, not the implementation classes. I.e. use List as a target type to convert to a list, but not ArrayList.

The generic type handling during conversion is limited to collections and generic arrays. Any type variables and wildcards are not kept as side information after the conversion is done. The class forbids conversion to a target type of instance TypeVariable, but may allow to WildcardType, when the wildcard contains only a single upper bound.

When the class encounters StructuredTaskResult instances to convert, it may resolve the value immediately, or delay the resolution of the value until it is accessed. This can result in less dependency reporting for tasks. E.g. if a StructuredMapTaskResult is converted to a Map, the dependencies to the value tasks are only installed when their values are actually accessed, which may result in less dependencies reported towards the build system. If no TaskResultResolver is specified during conversion, a ConversionFailedException is most likely to be thrown.

Converting to enumeration values works by converting the value to string, and calling Enum.valueOf(Class<T>, String) for it.

The class supports enclosing Map instances into a specified interface target type. This works by creating a Proxy object as a result of conversion, and resolving the elements of the map when a method is called on it. The proxy object is defined in the base classloader for the conversion.

Interface wrapped maps will look up the keys on the value map based on the method called on the proxy object. If the method has no arguments, and has the format get<FieldName> or get_<FieldName> then the value for the string FieldName will be returned from the call. The value will be converted by the rules of this class to the generic return type of the method. If the wrap interface is an annotation type, then the key name will be exactly the same as the method name. If no key was found for the field name, then the default implementation for the interface method will be returned (if any).

If a method with a single String argument is called, then a two phase lookup will happen, first for the key specified by the method name, and second on the resolved value for the argument key. E.g. if getField(String) is called with "key" argument, then the value for the key "Field" will be resolved, and if it is present, then the value for "key" will be resolved on the previous result. After that, the result will be converted to the return type of the method.

If all the previous lookups fail, the method call will throw an UnsupportedOperationException.

The interface wrapped maps are immutable, and their backing map instances will be copied during instantiation.

If the target type is an interface, and the value implements an interface with the same qualified name, but is not assignable to the target type, then the class executes interface adapting conversion. This can happen when different class versions of the same interface is loaded in the JVM, and conversion between them is requested. As they are loaded from different classloaders, they are not assignable to each other, however they should be convertible to each other, as they represent the same class. This scenario can happen if the interface evolves with different releases of a library, and due to different dependencies for the runtime, they both need to be loaded. In this case interface adapting conversion is done.

During the above conversion the value object will be wrapped into a Proxy that implements the interface in the base classloader, and forwards calls to the actual object which implements the interface from a different classloader. The method calls are forwarded, and the passed arguments and return type will be adapted as well. Important: For this to properly work, make sure to only use interfaces as argument and return types for an interface that might be subject to adaptation. Collection interfaces will be automatically adapted with their generic types, but other generic interfaces may not be supported.

The class allows extending the conversion mechanism via the DataConverter, ConversionContext, and related classes. Conversion can be initiated based on a field or method. In this case the ConverterConfiguration annotations on the given element will be taken into account to specialize the conversion mechanism for that passed value.

The target class can also be used to specialize the conversion by declaring a static method with the name valueOf. The valueOf methods should have the same return type as the class they are declared in. Only the methods which have an assignable return type to the declaring class will be considered.

If the value to convert is an instance of StructuredTaskResult, then the class will search for a method valueOf(StructuredTaskResult, ConversionContext) and use it to convert if available. In other cases the valueOf methods will be listed in the target type, and the one with the closes matching parameter type will be chosen based on the value type. I.e. If an Integer is being converted to a type, valueOf(Integer) will have priority over valueOf(Number).

If any valueOf method conversions fail, the other applicable methods will be also tried to execute the conversion.

The converted values can also specify the conversions by declaring a no-arg public method in the to<target-type-simple-name>() format. The simple name of the target type will be retrieved, and the method prefixed by the phrase "to" will be looked up. If the return type of that method is assignable to the target type, then it will be tried to execute the conversion.

Methods
public static Object
Adapts the interfaces of the specified object using the rules of this class.
public static <T> T
convert(Object value, Class<T> targettype)
Converts the value object to the target type using the rules of this class.
public static Object
convert(Object value, Type type)
Converts the value object to the target type using the rules of this class.
public static Object
convert(TaskResultResolver taskresultresolver, ClassLoader baseclassloader, Object value, Type targettype, Iterable<extends ConverterConfiguration> converterconfigurations)
Converts the value object to the target type using the rules of this class.
public static Object
convert(TaskResultResolver taskresultresolver, Object value, Field fieldtype)
Converts the value object to the target field type using the rules of this class.
public static Object
convert(TaskResultResolver taskresultresolver, Object value, Method targetmethodtype)
Converts the value object to the target method type using the rules of this class.
public static Object
convert(ConversionContext conversioncontext, Object value, Type type)
Converts the value to the target type using the specified conversion context.
public static Object
convertDefault(ConversionContext conversioncontext, Object value, Type type)
Converts the value to the target type using the specified conversion context without examining the converter configuration for the context.
public static <T extends DataConverter> T
Instantiates the argument DataConverter class.
public static <T> T
Wraps the given map into the specified interface using the rules of this class.
public static <T> T
Wraps the given map into the specified interface using the rules of this class.
public static <T> T
wrapMappedInterface(Map<String, ?> map, Class<T> itf, ClassLoader cl, TaskResultResolver taskresultresolver)
Wraps the given map into the specified interface using the rules of this class.
public static Object adaptInterface(ClassLoader classloader, Object obj)
Adapts the interfaces of the specified object using the rules of this class.

This method examines if all of the interfaces of the object are present in the specified classloader. If not, then a proxy object will be created in the specified classloader with the adapted interfaces.

Note: Adapting parameterized interfaces is not yet supported, unless they are Collection, List, Set, Map or Iterable.

If the argument object is already adapted, its adaptation will be unwrapped.

If the method fails to properly adapt the argument, or it doesn't need adaptation, the argument object will be returned without modification.

classloaderThe classloader to adapt the interfaces for.
objThe object to adapt the interfaces of.
The adapted object. This might be identity same to the argument object if it needs no adaptation.
public static <T> T convert(Object value, Class<T> targettype) throws ConversionFailedException
Converts the value object to the target type using the rules of this class.

No converter configurations and no TaskResultResolver will be used. The default conversion mechanism will be used.

This method is same as convert(Object, Type), but returns the result casted to the generic type argument T.

TThe target type.
valueThe object to convert.
targettypeThe target type of the conversion.
The converted value.
ConversionFailedExceptionIf the conversion fails.
public static Object convert(Object value, Type type) throws ConversionFailedException
Converts the value object to the target type using the rules of this class.

No converter configurations and no TaskResultResolver will be used. The default conversion mechanism will be used.

valueThe object to convert.
typeThe target type of the conversion.
The converted value.
ConversionFailedExceptionIf the conversion fails.
public static Object convert(TaskResultResolver taskresultresolver, ClassLoader baseclassloader, Object value, Type targettype, Iterable<extends ConverterConfiguration> converterconfigurations) throws ConversionFailedException
Converts the value object to the target type using the rules of this class.

The arguments will be used to initialize the ConversionContext.

taskresultresolverThe task result resolver. May be null. See ConversionContext.getTaskResultResolver().
baseclassloaderThe base classloader. See ConversionContext.
valueThe object to convert.
targettypeThe target type of the conversion.
converterconfigurationsThe converter configurations to use.
The converted value.
ConversionFailedExceptionIf the conversion fails.
public static Object convert(TaskResultResolver taskresultresolver, Object value, Field fieldtype) throws ConversionFailedException
Converts the value object to the target field type using the rules of this class.

The specified TaskResultResolver will be used for resolving task identifier results, and the ConverterConfiguration annotations on the specified field will be used for converter specialization.

taskresultresolverThe task result resolver. May be null. See ConversionContext.getTaskResultResolver().
valueThe object to convert.
fieldtypeThe target field to convert the object for, this determines the generic target type.
The converted object.
ConversionFailedExceptionIf the conversion fails.
public static Object convert(TaskResultResolver taskresultresolver, Object value, Method targetmethodtype) throws ConversionFailedException
Converts the value object to the target method type using the rules of this class.

The specified TaskResultResolver will be used for resolving task identifier results, and the ConverterConfiguration annotations on the specified method will be used for converter specialization.

taskresultresolverThe task result resolver. May be null. See ConversionContext.getTaskResultResolver().
valueThe object to convert.
targetmethodtypeThe target method to convert the object for, this determines the generic target type.
The converted object.
ConversionFailedExceptionIf the conversion fails.
public static Object convert(ConversionContext conversioncontext, Object value, Type type) throws ConversionFailedException
Converts the value to the target type using the specified conversion context.

This method examines the converter configuration for the specified conversion context, and uses the specified data converters to convert the value. If no data converters were defined, the default conversion mechanism will be applied.

Warning: Do not call this method directly from DataConverter implementation using the same conversion context argument for the DataConverter.convert(ConversionContext, Object, Type) call. It will cause a stack overflow as it will recursively call the same data converter. Call convertDefault(ConversionContext, Object, Type) to forward conversion for the currently converted value.

It is recommended that this method is called with a conversion context retrieved from ConversionContext.genericChildContext(int).

conversioncontextThe conversion context to use.
valueThe object to convert.
typeThe target type of the conversion.
The converted object.
ConversionFailedExceptionIf the conversion fails.
public static Object convertDefault(ConversionContext conversioncontext, Object value, Type type) throws ConversionFailedException
Converts the value to the target type using the specified conversion context without examining the converter configuration for the context.

Unlike convert(ConversionContext, Object, Type), this method doesn't use the converter configuration, and the defined DataConverters to delegate conversion.

conversioncontextThe conversion context to use.
valueThe object to convert.
typeThe target type of the conversion.
The converted object.
ConversionFailedExceptionIf the conversion fails.
public static <T extends DataConverter> T getDataConverterInstance(Class<T> convertertype) throws ConversionFailedException
Instantiates the argument DataConverter class.

Data converter implementations should have a public no-arg constructor.

TThe DataConverter type to instantiate.
convertertypeThe data converter type.
The instantiated data converter.
ConversionFailedExceptionIf the instantiation fails.
public static <T> T wrapMappedInterface(Map<String, ?> map, Class<T> itf) throws IllegalArgumentException
Wraps the given map into the specified interface using the rules of this class.

The classloader of the interface type will be used to define the proxy.

TThe type of the interface.
mapThe map object to wrap.
itfThe interface type to wrap the map for.
The wrapped interface.
IllegalArgumentExceptionIf the type is not an interface.
public static <T> T wrapMappedInterface(Map<String, ?> map, Class<T> itf, ClassLoader cl) throws IllegalArgumentException
Wraps the given map into the specified interface using the rules of this class.
TThe type of the interface.
mapThe map object to wrap.
itfThe interface type to wrap the map for.
clThe classloader to define the proxy in.
The wrapped interface.
IllegalArgumentExceptionIf the type is not an interface.
public static <T> T wrapMappedInterface(Map<String, ?> map, Class<T> itf, ClassLoader cl, TaskResultResolver taskresultresolver) throws IllegalArgumentException
Wraps the given map into the specified interface using the rules of this class.
TThe type of the interface.
mapThe map object to wrap.
itfThe interface type to wrap the map for.
clThe classloader to define the proxy in.
taskresultresolverThe task result resolver or null if not available.
The wrapped interface.
IllegalArgumentExceptionIf the type is not an interface.