Skip to content

Commit 693616c

Browse files
committed
Support custom conversions for the mapped Item fields
- Implemented resolution of convertible types Closes gh-115
1 parent bb64ae8 commit 693616c

3 files changed

Lines changed: 147 additions & 0 deletions

File tree

src/main/java/ru/rt/restream/reindexer/convert/FieldConverter.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
package ru.rt.restream.reindexer.convert;
1717

18+
import ru.rt.restream.reindexer.convert.util.ConversionUtils;
19+
import ru.rt.restream.reindexer.convert.util.ResolvableType;
20+
import ru.rt.restream.reindexer.util.Pair;
21+
1822
/**
1923
* An interface that can be implemented to convert field value between the Reindexer stored database type:
2024
* {@link ru.rt.restream.reindexer.FieldType}, and the one used within the POJO representation.
@@ -36,4 +40,14 @@ public interface FieldConverter<X, Y> {
3640
* @return the Reindexer stored database type value
3741
*/
3842
Y convertToDatabaseType(X field);
43+
44+
/**
45+
* Returns a {@link Pair} of source and target {@link ResolvableType}s.
46+
* By default, the source and target types are determined based on {@link X} and {@link Y} parameters,
47+
* this can be overridden to provide a custom implementation of how source and target types are determined.
48+
* @return the {@link Pair} of source and target {@link ResolvableType}s to use
49+
*/
50+
default Pair<ResolvableType, ResolvableType> getConvertiblePair() {
51+
return ConversionUtils.resolveConvertiblePair(getClass(), FieldConverter.class);
52+
}
3953
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2020 Restream
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package ru.rt.restream.reindexer.convert.util;
17+
18+
import org.apache.commons.lang3.reflect.TypeUtils;
19+
import ru.rt.restream.reindexer.util.Pair;
20+
21+
import java.lang.reflect.Type;
22+
import java.lang.reflect.TypeVariable;
23+
import java.util.Collection;
24+
import java.util.Map;
25+
import java.util.Objects;
26+
import java.util.concurrent.ConcurrentHashMap;
27+
28+
/**
29+
* For internal use only, as this contract is likely to change.
30+
*/
31+
public final class ConversionUtils {
32+
33+
private static final Map<Class<?>, Pair<ResolvableType, ResolvableType>> CONVERTIBLE_PAIR_CACHE = new ConcurrentHashMap<>();
34+
35+
/**
36+
* Resolves source and target types based on {@code [0]} and {@code [1]} parameters from {@code converterType} interface implementation.
37+
* If {@code converterType} source or target type is an array, the type is determined by {@code Class#getComponentType},
38+
* in case of a generic type, only {@code java.util.Collection} assignable types are considered, nested generic types
39+
* (i.e. {@code List<List<String>>}) are not supported, such types will not be recognized and IllegalArgumentException will be thrown.
40+
* @param converterClass the {@code converterType} interface implementation class to use
41+
* @param converterType the converter interface to use
42+
* @return the {@link Pair} of source and target {@link ResolvableType}s to use
43+
*/
44+
public static Pair<ResolvableType, ResolvableType> resolveConvertiblePair(Class<?> converterClass, Class<?> converterType) {
45+
Objects.requireNonNull(converterClass, "converterClass must not be null");
46+
// https://bugs.openjdk.java.net/browse/JDK-8161372
47+
Pair<ResolvableType, ResolvableType> typePair = CONVERTIBLE_PAIR_CACHE.get(converterClass);
48+
if (typePair != null) {
49+
return typePair;
50+
}
51+
Objects.requireNonNull(converterType, "converterType must not be null");
52+
TypeVariable<? extends Class<?>>[] typeParameters = converterType.getTypeParameters();
53+
if (typeParameters.length < 2) {
54+
throw new IllegalArgumentException(
55+
String.format("Converter type: %s must have 2 type arguments", converterType.getName()));
56+
}
57+
return CONVERTIBLE_PAIR_CACHE.computeIfAbsent(converterClass, k -> {
58+
Map<TypeVariable<?>, Type> typeArguments = TypeUtils.getTypeArguments(converterClass, converterType);
59+
Type sourceType = typeArguments.get(typeParameters[0]);
60+
ResolvableType resolvableSourceType = resolveType(sourceType);
61+
if (resolvableSourceType == null) {
62+
throw new IllegalArgumentException(
63+
String.format("Cannot resolve source type: %s of converter type: %s for converter class: %s",
64+
sourceType, converterType.getName(), converterClass.getName()));
65+
}
66+
Type targetType = typeArguments.get(typeParameters[1]);
67+
ResolvableType resolvableTargetType = resolveType(targetType);
68+
if (resolvableTargetType == null) {
69+
throw new IllegalArgumentException(
70+
String.format("Cannot resolve target type: %s of converter type: %s for converter class: %s",
71+
targetType, converterType.getName(), converterClass.getName()));
72+
}
73+
return new Pair<>(resolvableSourceType, resolvableTargetType);
74+
});
75+
}
76+
77+
private static ResolvableType resolveType(Type type) {
78+
if (TypeUtils.isAssignable(type, Collection.class)) {
79+
Type typeArgument = TypeUtils.getTypeArguments(type, Collection.class)
80+
.get(Collection.class.getTypeParameters()[0]);
81+
if (typeArgument instanceof Class<?>) {
82+
Class<?> containerType = TypeUtils.getRawType(type, Collection.class);
83+
return new ResolvableType(containerType, (Class<?>) typeArgument, true);
84+
}
85+
return null;
86+
}
87+
if (type instanceof Class<?>) {
88+
Class<?> targetType = (Class<?>) type;
89+
return new ResolvableType(targetType, targetType.getComponentType(), targetType.isArray());
90+
}
91+
return null;
92+
}
93+
94+
private ConversionUtils() {
95+
// utils
96+
}
97+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2020 Restream
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package ru.rt.restream.reindexer.convert.util;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Getter;
20+
import lombok.RequiredArgsConstructor;
21+
import lombok.ToString;
22+
23+
/**
24+
* Represents a resolved type, this includes {@code type} information,
25+
* {@code componentType} and {@code isCollectionLike} if the {@code type} is either
26+
* assignable of {@code java.util.Collection} or an array.
27+
*/
28+
@Getter
29+
@ToString
30+
@EqualsAndHashCode
31+
@RequiredArgsConstructor
32+
public final class ResolvableType {
33+
private final Class<?> type;
34+
private final Class<?> componentType;
35+
private final boolean isCollectionLike;
36+
}

0 commit comments

Comments
 (0)