2

On my current project, I need to convert values between types that are only known at runtime: the input type is the JSON type of the input, and the output type is defined in a configuration file loaded at runtime.

I came up with a generic solution, but I'm not quite happy with it, because supporting new input types means you need to change existing supported output type classes (which goes against the Open/Closed Principle), and I use a lot of instanceof and a lot of casting.

Are there better solutions than what I did in terms of maintainability?


I tried to get a kind of minimal example of my code, so here it is:

package org.example.scratch;

import static java.lang.Float.*;
import static java.lang.Integer.*;

import java.time.LocalDate;
import java.time.ZonedDateTime;

import lombok.Value;

class Scratch {
    public static void main(String[] args) {
        // runtime values
        final FieldValue value = StringValue.of("3.14"); // input value
        final Converter<?> converter = FloatConverter.of(); // output type

        Mapper<?> mapper = Mapper.of(converter);
        FieldValue converted = mapper.convert(value);
        System.out.println(converted);
    }

    @Value(staticConstructor = "of")
    static class Mapper<T extends FieldValue> {
        Converter<T> converter;

        public T convert(FieldValue value) {
            return converter.apply(value);
        }
    }

    interface Converter<T extends FieldValue> {
        T apply(FieldValue value);
    }

    @Value(staticConstructor = "of")
    static class IntegerConverter implements Converter<IntegerValue> {
        @Override
        public IntegerValue apply(FieldValue value) {
            if (value instanceof IntegerValue) {
                return (IntegerValue) value;
            }

            if (value instanceof FloatValue) {
                return IntegerValue.of(((FloatValue) value).getValue().intValue());
            }

            if (value instanceof StringValue) {
                return IntegerValue.of(parseInt(((StringValue) value).getValue()));
            }

            throw new IllegalArgumentException("Impossible to convert a " + value.getClass().getName() + " to a " + IntegerValue.class.getSimpleName());
        }
    }

    @Value(staticConstructor = "of")
    static class FloatConverter implements Converter<FloatValue> {
        @Override
        public FloatValue apply(FieldValue value) {
            if (value instanceof FloatValue) {
                return (FloatValue) value;
            }

            if (value instanceof IntegerValue) {
                return FloatValue.of(((IntegerValue) value).getValue().floatValue());
            }

            if (value instanceof StringValue) {
                return FloatValue.of(parseFloat(((StringValue) value).getValue()));
            }

            throw new IllegalArgumentException("Impossible to convert a " + value.getClass().getName() + " to a " + FloatValue.class.getSimpleName());
        }
    }

    @Value(staticConstructor = "of")
    static class DateConverter implements Converter<DateValue> {
        @Override
        public DateValue apply(FieldValue value) {
            if (value instanceof DateValue) {
                return (DateValue) value;
            }

            if (value instanceof StringValue) {
                return DateValue.of(LocalDate.parse(((StringValue) value).getValue()));
            }

            throw new IllegalArgumentException("Impossible to convert a " + value.getClass().getName() + " to a " + DateValue.class.getSimpleName());
        }
    }

    @Value(staticConstructor = "of")
    static class TimestampConverter implements Converter<TimestampValue> {
        @Override
        public TimestampValue apply(FieldValue value) {
            if (value instanceof TimestampValue) {
                return (TimestampValue) value;
            }

            if (value instanceof StringValue) {
                return TimestampValue.of(ZonedDateTime.parse(((StringValue) value).getValue()));
            }

            throw new IllegalArgumentException("Impossible to convert a " + value.getClass().getName() + " to a " + TimestampValue.class.getSimpleName());
        }
    }

    interface FieldValue {}

    @Value(staticConstructor = "of")
    static class IntegerValue implements FieldValue {
        Integer value;
    }

    @Value(staticConstructor = "of")
    static class FloatValue implements FieldValue {
        Float value;
    }

    @Value(staticConstructor = "of")
    static class DateValue implements FieldValue {
        LocalDate value;
    }

    @Value(staticConstructor = "of")
    static class TimestampValue implements FieldValue {
        ZonedDateTime value;
    }

    @Value(staticConstructor = "of")
    static class StringValue implements FieldValue {
        String value;
    }
}
CidTori
  • 351
  • 4
  • 17

1 Answers1

0

If maintainability is your goal, then I think you could group some things together to keep things simple and easy to change. Please note - I removed Lombok, as I have trouble getting it to work on my machine. I also removed static functionality, as that doesn't seem necessary for your use case.

GenericValue<T>

public abstract class GenericValue<T>
{

   private T value;
   
   public GenericValue(T value)
   {
   
      this.value = value;
   
   }

   public GenericValue(GenericValue<?> value)
   {
   
      this.value = convert(value);
   
   }

   public T getValue()
   {
   
      return this.value;
   
   }
   
   public abstract T convert(GenericValue<?> inputType);

   public static void main(String[] args)
   {
   
      GenericValue<String> input = new StringValue("123"); // input value
      
      {
      
         GenericValue<Float> output = new FloatValue(input);
      
         Float result = output.getValue();
      
         System.out.println(result);
      
      }
      
      {
      
         GenericValue<Integer> output = new IntegerValue(input);
      
         Integer result = output.getValue();
      
         System.out.println(result);
      
      }
      
   }

}

~

IntegerValue

public class IntegerValue extends GenericValue<Integer>
{

   public IntegerValue(Integer value)
   {
      super(value);
   }
   
   public IntegerValue(GenericValue<?> value)
   {
      super(value);
   }
   
   public Integer convert(GenericValue<?> inputType)
   {
   
      if (inputType instanceof IntegerValue)
      {
      
         IntegerValue temp = ((IntegerValue) inputType);
      
         return (temp.getValue());
      
      }
      
      else if (inputType instanceof StringValue)
      {
      
         StringValue temp = (StringValue) inputType;
      
         return (Integer.parseInt(temp.getValue()));
      
      }
      
      else if (inputType instanceof FloatValue)
      {
      
         FloatValue temp = (FloatValue) inputType;
      
         return (temp.getValue().intValue());
      
      }
      
      else
      {
      
         throw new IllegalArgumentException("Impossible to convert a " + inputType.getClass().getName() + " to " + this.getClass().getSimpleName());
      
      }
      
   }

}

~

FloatValue

public class FloatValue extends GenericValue<Float>
{

   public FloatValue(Float value)
   {
      super(value);
   }

   public FloatValue(GenericValue<?> value)
   {
      super(value);
   }
   
   public Float convert(GenericValue<?> inputType)
   {
   
      if (inputType instanceof IntegerValue)
      {
      
         IntegerValue temp = (IntegerValue) inputType;
      
         return Float.valueOf(temp.getValue().floatValue());
      
      }
      
      else if (inputType instanceof StringValue)
      {
      
         StringValue temp = (StringValue) inputType;
      
         return Float.parseFloat(temp.getValue());
      
      }
      
      else if (inputType instanceof FloatValue)
      {
      
         FloatValue temp = (FloatValue) inputType;
      
         return temp.getValue();
      
      }
      
      else
      {
      
         throw new IllegalArgumentException("Impossible to convert a " + inputType.getClass().getName() + " to " + this.getClass().getSimpleName());
      
      }
   
   }

}

~

StringValue

public class StringValue extends GenericValue<String>
{

   public StringValue(String value)
   {
      super(value);
   }

   public StringValue(GenericValue<?> value)
   {
      super(value);
   }

   public String convert(GenericValue<?> inputType)
   {
   
      if (inputType instanceof IntegerValue || inputType instanceof FloatValue || inputType instanceof StringValue)
      {
      
         String string = String.valueOf(inputType.getValue());
      
         return (string);
      
      }
      
      else
      {
      
         throw new IllegalArgumentException("Impossible to convert a " + inputType.getClass().getName() + " to " + this.getClass().getSimpleName());
      
      }
      
   }

}

~

DateValue

import java.time.LocalDate;

public class DateValue extends GenericValue<LocalDate>
{

   public DateValue(LocalDate value)
   {
      super(value);
   }

   public DateValue(GenericValue<?> value)
   {
      super(value);
   }
   
   public LocalDate convert(GenericValue<?> inputType)
   {
   
      if (inputType instanceof StringValue)
      {
      
         StringValue temp = ((StringValue) inputType);
      
         return LocalDate.parse(temp.getValue());
      
      }
      
      else if (inputType instanceof DateValue)
      {
      
         DateValue temp = (DateValue) inputType;
      
         return temp.getValue();
      
      }
      
      else
      {
      
         throw new IllegalArgumentException("Impossible to convert a " + inputType.getClass().getName() + " to " + this.getClass().getSimpleName());
      
      }
   
   }

}

~

TimestampValue

import java.time.ZonedDateTime;

public class TimestampValue extends GenericValue<ZonedDateTime>
{

   public TimestampValue(ZonedDateTime value)
   {
      super(value);
   }

   public TimestampValue(GenericValue<?> value)
   {
      super(value);
   }
   
   public ZonedDateTime convert(GenericValue<?> inputType)
   {
   
      if (inputType instanceof StringValue)
      {
      
         StringValue temp = ((StringValue) inputType);
      
         return ZonedDateTime.parse(temp.getValue());
      
      }
      
      else if (inputType instanceof TimestampValue)
      {
      
         TimestampValue temp = (TimestampValue) inputType;
      
         return temp.getValue();
      
      }
      
      else
      {
      
         throw new IllegalArgumentException("Impossible to convert a " + inputType.getClass().getName() + " to " + this.getClass().getSimpleName());
      
      }
   
   }

}
davidalayachew
  • 1,279
  • 1
  • 11
  • 22