Generic object factory - java

I got several objects that are created based on DataObject class.
So I'd like to make generic factory to construct them.
I'v tried something like this:
interface FactoryObject<T> {
T create(DataObject data);
}
public class Factory {
List<FactoryObject> fromDataObjectArray(DataObject[] data, Class<? extends FactoryObject> cls) {
return Arrays.stream(Optional.ofNullable(data).orElse(new DataObject[0]))
.map(d -> cls.create()).collect(Collectors.toList());
}
}
Finally, I'd like to call
List<MyClass> myClasses = Factory.fromDataObjectArray(data, MyClass.class);
But method create() cannot be resolved, how can I achieve what I need?

If I understand you code, you want to create a list of instances; you could do something like that:
<T> List<T> fromDataObjectArray(DataObject[] data, FactoryObject<T> fac) {
return Arrays.stream(Optional.ofNullable(data).orElse(new DataObject[0]))
.map(d -> fac.create(d)).collect(Collectors.toList());
}
UPDATE:
If I understand your comment below, you want a composite factory that will determine from a DataObject what is the actual factory you want to use to create your instance.
You could do something like this:
public class CompositeFactory<T> implements FactoryObject<T> {
private final Function<DataObject,FactoryObject<? extends T>>[] funcs;
public CompositeFactory(
Function<DataObject,FactoryObject<? extends T>>... funcs) {
this.funcs = funcs;
}
#Override
public T create(DataObject data) {
for (Function<DataObject,FactoryObject<? extends T>> func: funcs) {
FactoryObject<? extends T> fac = func.apply(data);
if (fac != null) {
return fac.create(data);
}
}
return null; // or throw an exception
}
}
Another way to do that is conditional factories:
public class ConditionalFactory<T> implements FactoryObject<T> {
private final Predicate<DataObject> cond;
private final FactoryObject<? extends T> ifFac;
private final FactoryObject<? extends T> elseFac;
public ConditionalFactory(Predicate<DataObject> cond,
FactoryObject<? extends T> ifFac,
FactoryObject<? extends T> elseFac) {
this.cond = cond;
this.ifFac = ifFac;
this.elseFac = elseFac;
}
#Override
public T create(DataObject data) {
return (cond.test(data) ? ifFac : elseFac).create(data);
}
}
UPDATE 2:
Exemple: let's say you have the following classes:
class MyClass1 extends MyClass {
public MyClass1(DataObject data) {
}
}
class MyClass2 extends MyClass {
public MyClass2(DataObject data) {
}
}
...
and the corresponding factories:
FactoryObject<MyClass1> fac1 = (data) -> new MyClass1(data);
FactoryObject<MyClass2> fac2 = (data) -> new MyClass2(data);
FactoryObject<MyClass3> fac3 = (data) -> new MyClass3(data);
...
and let's say you can determine the actual class from the value of DataObject.getType():
You could do:
FactoryObject<MyClass> fact = new CompositeFactory<MyClass>(
(data)-> data.getType().equals("value1") ? fac1 : null,
(data)-> data.getType().equals("value2") ? fac2 : null,
(data)-> data.getType().equals("value3") ? fac3 : null,
...
);
you could also do:
FactoryObject<MyClass> fac = new CompositeFactory<MyClass>(
(data)->{
switch (data.getType()) {
case "value1":
return fac1;
case "value2":
return fac2;
...
default:
return null;
}
});
or:
FactoryObject<MyClass> fac = new ConditionalFactory<MyClass>(
(data)->data.getType().equals("value1"), fac1,
new ConditionalFactory<MyClass>(
(data)->data.getType().equals("value2"), fac2,
fac3));

Related

Factory pattern using generics

I would like to build a class that caches classes of type CachedObject using Map.
public class CachedObject {
protected Long id;
public CachedObject(Long id) {
this.id = id;
}
}
Below is the factory class.
public class CachedObjectFactory<T extends CachedObject> {
private static Logger logger = LoggerFactory.getLogger(CachedObjectFactory.class);
private Map<Long, T> cacheMap = new HashMap<>();
public T get(Class<T> type, Long id) throws CachedObjectInstantiationException {
T cachedObject = cacheMap.get(id);
try {
if(cachedObject == null) {
cachedObject = type.getDeclaredConstructor().newInstance(id);
}
} catch(Exception e) {
throw new CachedObjectInstantiationException(e.getMessage());
}
return cachedObject;
}
}
I have a class that extends CacheableObject as below:
#Component
class X extends CachedObject {
public X(Long id) {
super(id);
}
....
}
When I try to create an instance of class X that extends CachedObject using the get method in the factory as below: (please note that cachedObjectFactory is autowired using Spring)
#Component
class Y extends CachedObject {
CachedObjectFactory<CachedObject> cachedObjectFactory;
Y(Long id, CachedObjectFactory cachedObjectFactory) {
super(id);
this.cachedObjectFactory = cachedObjectFactory;
}
public void someMethod() {
X x = cachedFactory.get(X.class, id);
}
}
I get the compile time error "The method get(Class, Long) in the type CachedObjectFactory is not applicable for the arguments (Class,
Long)". How should I instantiate an object X using the factory method?
Declaring a field as CachedObjectFactory<CachedObject> doesn't really mean anything -- the parameter already has CachedObject as an upper bound.
You can get your code to compile by changing you factory to look like this:
public class CachedObjectFactory {
private Map<Long, Object> cacheMap = new HashMap<>();
public <T extends CachedObject> T get(Class<T> type, Long id) {
T cachedObject = (T)cacheMap.get(id);
try {
if(cachedObject == null) {
cachedObject = type.getDeclaredConstructor().newInstance(id);
}
} catch(Exception e) {
throw new RuntimeException(e.getMessage());
}
return cachedObject;
}
}
As you are using your factory for many classes, making it generic doesn't really make sense.
Of course if two instances of different subclasses of CachedObject have the same id you'll get a runtime ClassCastException.

Dynamodb attribute converter provider for enhanced type extending Hashmap

I have a type which is extending HashMap<String, String>. As per the documentation here, it is possible to add a custom converter for the type. But it seems not working. The contents of the hashMap doesn't get converted, output looks like below;
"summary": {
"en": null
},
Any idea how to convert Label and its fields along with it's hashmap's contents?
Parent
#DynamoDbBean(converterProviders = {
CustomAttributeConverterProvider.class,
DefaultAttributeConverterProvider.class})
public class Summary extends BaseEntry {
private #Valid Label summary = null;
}
Child
#DynamoDbBean(converterProviders = {
CustomAttributeConverterProvider.class,
DefaultAttributeConverterProvider.class})
public class Label extends HashMap<String, String> {
private #Valid String en = null;
}
HashMapAttributeConverter
public class HashMapAttributeConverter implements AttributeConverter<Map<String, String>> {
private static AttributeConverter<Map<String, String>> mapConverter;
/** Default constructor. */
public HashMapAttributeConverter() {
mapConverter =
MapAttributeConverter.builder(EnhancedType.mapOf(String.class, String.class))
.mapConstructor(HashMap::new)
.keyConverter(StringStringConverter.create())
.valueConverter(StringAttributeConverter.create())
.build();
}
#Override
public AttributeValue transformFrom(Map<String, String> input) {
return mapConverter.transformFrom(input);
}
#Override
public Map<String, String> transformTo(AttributeValue input) {
return mapConverter.transformTo(input);
}
#Override
public EnhancedType<Map<String, String>> type() {
return mapConverter.type();
}
#Override
public AttributeValueType attributeValueType() {
return mapConverter.attributeValueType();
}
}
CustomAttributeConverterProvider
public class CustomAttributeConverterProvider implements AttributeConverterProvider {
private final List<AttributeConverter<?>> customConverters =
Arrays.asList(new HashMapAttributeConverter());
private final Map<EnhancedType<?>, AttributeConverter<?>> customConvertersMap;
private final AttributeConverterProvider defaultProvider =
DefaultAttributeConverterProvider.create();
public CustomAttributeConverterProvider() {
customConvertersMap =
customConverters.stream().collect(Collectors.toMap(AttributeConverter::type, c -> c));
}
#Override
public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
return (AttributeConverter<T>)
customConvertersMap.computeIfAbsent(enhancedType, defaultProvider::converterFor);
}
}
#Override
public <T> AttributeConverter<T> converterFor(EnhancedType<T> type) {
// in this method you have to return only your converter based on type
// otherwise null should be returned. It will fix your issue.
}

Gson #AutoValue and Optional<> dont work together, is there a workaround?

Gson doesnt have direct support for serializing #AutoValue classes or for Optional<> fields, but com.ryanharter.auto.value adds #AutoValue and net.dongliu:gson-java8-datatype adds Optional<> and other java8 types.
However, they dont work together AFAICT.
Test code:
public class TestOptionalWithAutoValue {
private static final Gson gson = new GsonBuilder().serializeNulls()
// doesnt matter which order these are registered in
.registerTypeAdapterFactory(new GsonJava8TypeAdapterFactory())
.registerTypeAdapterFactory(AutoValueGsonTypeAdapterFactory.create())
.create();
#Test
public void testAutoValueOptionalEmpty() {
AvoTestClass subject = AvoTestClass.create(Optional.empty());
String json = gson.toJson(subject, AvoTestClass.class);
System.out.printf("Json produced = %s%n", json);
AvoTestClass back = gson.fromJson(json, new TypeToken<AvoTestClass>() {}.getType());
assertThat(back).isEqualTo(subject);
}
#Test
public void testAutoValueOptionalFull() {
AvoTestClass subject = AvoTestClass.create(Optional.of("ok"));
String json = gson.toJson(subject, AvoTestClass.class);
System.out.printf("Json produced = '%s'%n", json);
AvoTestClass back = gson.fromJson(json, new TypeToken<AvoTestClass>() {}.getType());
assertThat(back).isEqualTo(subject);
}
}
#AutoValue
public abstract class AvoTestClass {
abstract Optional<String> sval();
public static AvoTestClass create(Optional<String> sval) {
return new AutoValue_AvoTestClass(sval);
}
public static TypeAdapter<AvoTestClass> typeAdapter(Gson gson) {
return new AutoValue_AvoTestClass.GsonTypeAdapter(gson);
}
}
#GsonTypeAdapterFactory
public abstract class AutoValueGsonTypeAdapterFactory implements TypeAdapterFactory {
public static TypeAdapterFactory create() {
return new AutoValueGson_AutoValueGsonTypeAdapterFactory();
}
}
gradle dependencies:
annotationProcessor "com.google.auto.value:auto-value:1.7.4"
annotationProcessor("com.ryanharter.auto.value:auto-value-gson-extension:1.3.1")
implementation("com.ryanharter.auto.value:auto-value-gson-runtime:1.3.1")
annotationProcessor("com.ryanharter.auto.value:auto-value-gson-factory:1.3.1")
implementation 'net.dongliu:gson-java8-datatype:1.1.0'
Fails with:
Json produced = {"sval":null}
...
java.lang.NullPointerException: Null sval
...
net.dongliu.gson.OptionalAdapter is called on serialization, but not deserialization.
Im wondering if theres a workaround, or if the answer is that Gson needs to have direct support for Optional<> ?
Glad to see you've updated your question by adding much more information and even by adding a test! :) That really makes it clear!
I'm not sure, but the generated type adapter has no mention for the default value for sval:
jsonReader.beginObject();
// [NOTE] This is where it is initialized with null, so I guess it will definitely fail if the `sval` property is not even present in the deserialized JSON object
Optional<String> sval = null;
while (jsonReader.hasNext()) {
String _name = jsonReader.nextName();
// [NOTE] This is where it skips `null` value so it even does not reach to the `OptionalAdapter` run
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
continue;
}
switch (_name) {
default: {
if ("sval".equals(_name)) {
TypeAdapter<Optional<String>> optional__string_adapter = this.optional__string_adapter;
if (optional__string_adapter == null) {
this.optional__string_adapter = optional__string_adapter = (TypeAdapter<Optional<String>>) gson.getAdapter(TypeToken.getParameterized(Optional.class, String.class));
}
sval = optional__string_adapter.read(jsonReader);
continue;
}
jsonReader.skipValue();
}
}
}
jsonReader.endObject();
return new AutoValue_AvoTestClass(sval);
I have no idea if there is a way to configure the default values in AutoValue or other generators you mentioned, but it looks like a bug.
If there is no any way to work around it (say, library development abandoned; it takes too much time to wait for a fix; whatever), you can always implement it yourself, however with some runtime cost (basically this how Gson works under the hood for data bag objects).
The idea is delegating the job to the built-in RuntimeTypeAdapterFactory so that it could deal with a concrete class, not an abstract one, and set all fields according to the registered type adapters (so that the Java 8 types are supported as well).
The cost here is reflection, thus that adapter may work slower than generated type adapters.
Another thing is that if a JSON property does not even encounter in the JSON object, the corresponding field will remain null.
This requires another post-deserialization type adapter.
final class SubstitutionTypeAdapterFactory
implements TypeAdapterFactory {
private final Function<? super Type, ? extends Type> substitute;
private SubstitutionTypeAdapterFactory(final Function<? super Type, ? extends Type> substitute) {
this.substitute = substitute;
}
static TypeAdapterFactory create(final Function<? super Type, ? extends Type> substitute) {
return new SubstitutionTypeAdapterFactory(substitute);
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
#Nullable
final Type substitution = substitute.apply(typeToken.getType());
if ( substitution == null ) {
return null;
}
#SuppressWarnings("unchecked")
final TypeAdapter<T> delegateTypeAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.get(substitution));
return delegateTypeAdapter;
}
}
final class DefaultsTypeAdapterFactory
implements TypeAdapterFactory {
private final Function<? super Type, ? extends Type> substitute;
private final LoadingCache<Class<?>, Collection<Map.Entry<Field, ?>>> fieldsCache;
private DefaultsTypeAdapterFactory(final Function<? super Type, ? extends Type> substitute, final Function<? super Type, ?> toDefault) {
this.substitute = substitute;
fieldsCache = CacheBuilder.newBuilder()
// TODO tweak the cache
.build(new CacheLoader<Class<?>, Collection<Map.Entry<Field, ?>>>() {
#Override
public Collection<Map.Entry<Field, ?>> load(final Class<?> clazz) {
// TODO walk hieararchy
return Stream.of(clazz.getDeclaredFields())
.map(field -> {
#Nullable
final Object defaultValue = toDefault.apply(field.getGenericType());
if ( defaultValue == null ) {
return null;
}
field.setAccessible(true);
return new AbstractMap.SimpleImmutableEntry<>(field, defaultValue);
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
});
}
static TypeAdapterFactory create(final Function<? super Type, ? extends Type> substitute, final Function<? super Type, ?> toDefault) {
return new DefaultsTypeAdapterFactory(substitute, toDefault);
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
#Nullable
final Type substitution = substitute.apply(typeToken.getType());
if ( substitution == null ) {
return null;
}
if ( !(substitution instanceof Class) ) {
return null;
}
final Collection<Map.Entry<Field, ?>> fieldsToPatch = fieldsCache.getUnchecked((Class<?>) substitution);
if ( fieldsToPatch.isEmpty() ) {
return null;
}
final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegateTypeAdapter.write(out, value);
}
#Override
public T read(final JsonReader in)
throws IOException {
final T value = delegateTypeAdapter.read(in);
for ( final Map.Entry<Field, ?> e : fieldsToPatch ) {
final Field field = e.getKey();
final Object defaultValue = e.getValue();
try {
if ( field.get(value) == null ) {
field.set(value, defaultValue);
}
} catch ( final IllegalAccessException ex ) {
throw new RuntimeException(ex);
}
}
return value;
}
};
}
}
#AutoValue
abstract class AvoTestClass {
abstract Optional<String> sval();
static AvoTestClass create(final Optional<String> sval) {
return new AutoValue_AvoTestClass(sval);
}
static Class<? extends AvoTestClass> type() {
return AutoValue_AvoTestClass.class;
}
}
public final class OptionalWithAutoValueTest {
private static final Map<Type, Type> autoValueClasses = ImmutableMap.<Type, Type>builder()
.put(AvoTestClass.class, AvoTestClass.type())
.build();
private static final Map<Class<?>, ?> defaultValues = ImmutableMap.<Class<?>, Object>builder()
.put(Optional.class, Optional.empty())
.build();
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new GsonJava8TypeAdapterFactory())
.registerTypeAdapterFactory(SubstitutionTypeAdapterFactory.create(autoValueClasses::get))
.registerTypeAdapterFactory(DefaultsTypeAdapterFactory.create(autoValueClasses::get, type -> {
if ( type instanceof Class ) {
return defaultValues.get(type);
}
if ( type instanceof ParameterizedType ) {
return defaultValues.get(((ParameterizedType) type).getRawType());
}
return null;
}))
.create();
#SuppressWarnings("unused")
private static Stream<Optional<String>> test() {
return Stream.of(
Optional.of("ok"),
Optional.empty()
);
}
#ParameterizedTest
#MethodSource
public void test(final Optional<String> optional) {
final AvoTestClass before = AvoTestClass.create(optional);
final String json = gson.toJson(before, AvoTestClass.class);
final AvoTestClass after = gson.fromJson(json, AvoTestClass.class);
Assert.assertEquals(before, after);
}
}
This solution is reflection-based heavily, but it's just a work-around if the generators cannot do the job (again, not sure if they can be configured so that there are no such issues).

How generify class with T and List<T>

I am trying to generify my class structure.
I will show my real structure to be more specific.
I am writing application with offline mode support, so I decided to implement my ETag cache mechanism in using Robospice and GreenDao ORM.
I need to cache only GET requests.
Firstly my requests should extend base request(not mine), in my case RetrofitSpiceRequest<T, V>
T is type of return data
V is service type, in my case I am using Retrofit.
The problem is that return type is not List of T types by default and I need to create subclass that extends array of T objects and that use it as return type.
Something like this
public class City {
....
....
....
public static class List extends ArrayList<City> {
.....
.....
}
}
And use City.List as return type.
But I have my DAO declared as following
public class CityDao extends AbstractDao<City, Long> {
}
In each request (GET) I need to have specific DAO as a member in order to cache data if it differs from the server data. Or load data from the local database if there is no connection.
The problem here is that request generified by T type which is mostly list, City.List in my case, of some objects, but my dao is generified by, for example E type which is City in my case.
I want to create method like this
public AbastractDao<T,Long> getRequestDao() {
}
But as far as my Request returns City.List, I have no idea how to generify this class, I feel that it is possible, but now no ideas.
In case of non generic dao method, I have to duplicate code like this
#Override
public void insertReceivedData(City.List received) {
mCityDao.insertOrReplaceInTx(received);
}
#Override
public City.List getCachedData() {
if (mFilterMap != null && mFilterMap.size() > 0) {
return (City.List) mCityDao.loadAll();
} else {
WhereCondition[] whereConditions = QueryUtils.convertPropertyMapToConditionalArray(mFilterMap);
return (City.List) mCityDao.queryBuilder().where(whereConditions[0], Arrays.copyOfRange(whereConditions, 1, whereConditions.length)).list();
}
}
In each request
Please share your ideas.
Thanks.
I end up with following solution. It is not as good as I wanted, but it works and better than duplicating code.
My base request class.
public abstract class BaseGetRequest<L extends List<T>, T, V> extends RetrofitSpiceRequest<L, V> implements FilterableRequest {
// Context
protected Context mContext;
// Filter used in request and in queries
protected Map<Property, String> mFilterMap;
// Session provided Singletone
protected DaoSessionProvider mSessionProvider;
public BaseGetRequest(Class<L> clazz, Class<V> retrofitedInterfaceClass, Context context, Map<Property, String> filterMap) {
super(clazz, retrofitedInterfaceClass);
mContext = context;
mFilterMap = filterMap;
mSessionProvider = ((DaoSessionProvider) mContext.getApplicationContext());
// TODO determine required retry count
setRetryPolicy(new RetryPolicy() {
#Override
public int getRetryCount() {
return 0;
}
#Override
public void retry(SpiceException e) {
}
#Override
public long getDelayBeforeRetry() {
return 0;
}
});
}
protected WhereCondition[] getWhereConditions() {
return QueryUtils.convertPropertyMapToConditionalArray(mFilterMap);
}
public BaseGetRequestV2(Class<L> clazz, Class<V> retrofitedInterfaceClass, Context context) {
this(clazz, retrofitedInterfaceClass, context, null);
}
public abstract AbstractDao<T, Long> getDao();
public abstract L createDataList(List<T> list);
public L getCachedData() {
if (mFilterMap != null && mFilterMap.size() > 0) {
WhereCondition[] whereConditions = getWhereConditions();
return createDataList(getDao().queryBuilder().where(whereConditions[0], Arrays.copyOfRange(whereConditions, 1, whereConditions.length)).list());
} else {
return createDataList(getDao().loadAll());
}
}
public abstract L getData();
#Override
public Map<Property, String> getFilterMap() {
return mFilterMap;
}
public Map<String, String> getStringMap() {
return QueryUtils.convertPropertyMapToString(mFilterMap);
}
#Override
public L loadDataFromNetwork() throws Exception {
L receivedData = null;
try {
receivedData = getData();
WhereCondition[] conditions = getWhereConditions();
getDao().queryBuilder().where(conditions[0],Arrays.copyOfRange(conditions, 1, conditions.length)).buildDelete().executeDeleteWithoutDetachingEntities();
getDao().insertOrReplaceInTx(receivedData);
} catch (Exception ex) {
receivedData = getCachedData();
}
return receivedData;
}
}
And I can extend this class like so:
public class NewsRequest extends BaseGetRequest<NewsArticle.List, NewsArticle, API> {
public static final String TARGET_URL = "/news";
NewsArticleDao mNewsArticleDao;
public NewsRequest(Context context) {
this(context, null);
}
public NewsRequest(Context context, Map<Property, String> filterMap) {
super(NewsArticle.List.class, API.class, context, filterMap);
mNewsArticleDao = mSessionProvider.getDaoSession().getNewsArticleDao();
}
#Override
public AbstractDao<NewsArticle, Long> getDao() {
return mNewsArticleDao;
}
#Override
public NewsArticle.List createDataList(List<NewsArticle> list) {
return new NewsArticle.List(list);
}
#Override
public NewsArticle.List getData() {
return getService().getNews(getStringMap());
}
}

generics - dynamically using types

I have a class Column, which describes the column of a SQL-like table:
public interface Column<S extends Schema<S>, T> {
default String encode(final T value) {
return value.toString();
}
}
A column holds a certain type (integer, string, ...) and has a utility function to convert an instance of that type to a string (which is useful for logging purposes).
Next, I have a class Schema which describes the schema of a table:
public interface Schema<S extends Schema<S>> {
List<Column<S, ?>> getColumns();
}
A schema holds a list of columns.
Let's create a concrete schema with just one column:
public static class MySchema implements Schema<MySchema> {
public static final Column<MySchema, Integer> ID = new Column<MySchema, Integer>(){};
#Override
public List<Column<MySchema, ?>> getColumns() {
return Collections.singletonList(ID);
}
}
Next, I have a class MyData which contains data corresponding with a schema:
public static class MyData<S extends Schema<S>> {
public <T> T get(final Column<S, T> column) {
return (T) new Integer(164); // actual implementation left out
}
}
Encoding the value of a column manually is pretty easy:
final MySchema s = new MySchema();
final MyData<MySchema> d = new MyData<>();
System.out.println("encoded identifier: " + MySchema.ID.encode(d.get(MySchema.ID)));
Now, let's try this dynamically:
for (final Column<MySchema, ?> column : s.getColumns()) {
System.out.println("encoded identifier: " + column.encode(d.get(column)));
}
This doesn't work, since d.get(column) is inferred as capture<?>, which is not what Column.encode() accepts.
How can I solve this? I understand what is going wrong here (Column.encode(T value) only accepts a T, which we don't have right now), but I can't find a solution which does not loose the type-garanty we enforce by only accepting a T.
As a fiddle, here is complete code:
public class Test {
public interface Column<S extends Schema<S>, T> {
default String encode(final T value) {
return value.toString();
}
}
public interface Schema<S extends Schema<S>> {
List<Column<S, ?>> getColumns();
}
public static class MyData<S extends Schema<S>> {
public <T> T get(final Column<S, T> column) {
return (T) new Integer(164); // actual implementation left out
}
}
public static class MySchema implements Schema<MySchema> {
public static final Column<MySchema, Integer> ID = new Column<MySchema, Integer>(){};
#Override
public List<Column<MySchema, ?>> getColumns() {
return Collections.singletonList(ID);
}
}
public static void main(final String a[]) {
final MySchema s = new MySchema();
final MyData<MySchema> d = new MyData<>();
System.out.println("encoded identifier: " + MySchema.ID.encode(d.get(MySchema.ID)));
for (final Column<MySchema, ?> column : s.getColumns()) {
System.out.println("encoded identifier: " + column.encode(d.get(column)));
}
}
}
You can create a helper function with a generic parameter T which allows you to have a Column<T> instead of a Column<?>:
public static void main(final String a[]) {
final MySchema s = new MySchema();
final MyData<MySchema> d = new MyData<>();
for (final Column<MySchema, ?> column : s.getColumns())
encode(column, d);
}
private static <T> void encode(Column<T> column, MyData<MySchema> d) {
System.out.println("encoded identifier: " + column.encode(d.get(column)));
}
Another possibility is to use the same trick and provide a convenience method MyData.getEncoded:
public static class MyData<S extends Schema<S>> {
public <T> T get(final Column<S, T> column) {...}
public <T> String getEncoded(final Column<S, T> column) {
return column.encode(get(column));
}
}

Categories