-1

I have a HashMap which has about 500 key-value pairs in it. These values are to be set into attributes of an object, an example of the said object is below-

public class SomeClass {
    private String attrib1;
    private double attrib2 = Double.NaN
    //getters and setters
}

I have to pull the values from the HashMap based on a constant and then set them into this object. Right now, this is how I am doing it

public void someMethod(HashMap<String, String> mapToUse, SomeClass some) {
    some.setAttrib1(mapToUse.get(MyConstant.SOME_CONST));
    some.setAttrib2(methodToParseDouble(mapToUse.get(MyConstant.SOME_CONST2)));
}

This code works fine without issues, but in my case, I have 500 key-value pairs in the Map and the object contains about 280 attributes. So having 280 hard-coded setters appears ugly in code. Is there a better elegant way to do this?

Right now my code has 280 setter methods called and for each of those I have 280 keys (defined as constants) which I am using to look up the attributes.

I read about BeanUtils, but I am struggling to get it to work with a HashMap. If any of you has a sample code which I can use to pull and set from HashMap, that'd be great.

Edit:

So I got BeanUtils to work, but now I have another problem. BeanUtils working code

    testMap.put("attrib1", "2");
    testMap.put("attrib2", "3");
    testMap.put("completelyDiffAttrib1", "10000");   //This breaks the code
    SomeClass testBean = new SomeClass();

    BeanUtils.populate(testBean, testMap);

The code above works when I have all the attributes mentioned in the Map in my Object, but if I have extra value in HashMap, which is not present as an attribute in the class then my code breaks. I get a NoClassDef found error-

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/collections/FastHashMap
Caused by: java.lang.ClassNotFoundException: org.apache.commons.collections.FastHashMap

I have added the commons-collections4-4.3.jar to the classpath, which was mentioned elsewhere.

I can think of one approach where I can just filter the Map out first and then run it through populate, but I am looking for better ways to do it.


I cannot change how the source is, i.e., it is going to be a HashMap and I need it in that exact form of the object. I am out of ideas, if anyone has any suggestions, I can do a bit of reading. Thanks!

WhiteBird
  • 97
  • 8
  • what's the nature of the key? I mean, do they follow some pattern? – aran Mar 05 '19 at 18:47
  • An object containing 280 attributes appears ugly as well. Are they really attributes of one entity? In this case take into consideration Carcigenicate's answer. – Pavel Smirnov Mar 05 '19 at 18:52
  • 2
    Why on earth you have an object with 280 attributes? – ruohola Mar 05 '19 at 18:54
  • @ruohola It is part of an enterprise application and they need to run this class through a model in non-java code where it'll eventually pass through, so the output has to be in attributes under a single object. I know it sounds stupid, but I do not have control over refining it. – WhiteBird Mar 05 '19 at 18:58
  • @Carcigenicate I do not understand, where do you want me to pass the argument at? – WhiteBird Mar 05 '19 at 18:59
  • 2
    Have you checked this https://stackoverflow.com/questions/16428817/convert-a-mapstring-string-to-a-pojo ? Does it work for you? – Sergei Sirik Mar 05 '19 at 19:03
  • @AsierAranbarri No patterns in the names, unfortunately. – WhiteBird Mar 05 '19 at 19:04
  • @SergeiSirik that looks like it might work at the first glance. Let me try that out! – WhiteBird Mar 05 '19 at 19:05
  • Thus looks like an XY Problem. Why have so many attributes for one object? – Raedwald Mar 05 '19 at 19:16
  • @Raedwald thats the requirement from whoever is consuming off our application, not something that is in our control. – WhiteBird Mar 05 '19 at 19:21

2 Answers2

1

A starting point might be

static final Map<Class<?>, Function<String, Object>> FUNCTION_MAP = new HashMap<>();

static {
    FUNCTION_MAP.put(String.class, s -> s);
    FUNCTION_MAP.put(Float.class, s -> Float.parseFloat(s));
    FUNCTION_MAP.put(Double.class, s -> methodToParseDouble(s));
}

static void someMethod(
        final Map<String, String> mapToUse,
        final SomeClass some
) throws InvocationTargetException, IllegalAccessException {
    // Extract all the methods of SomeClass
    final Method[] methods = some.getClass().getDeclaredMethods();

    for (final Method method : methods) {
        // Consider only methods which are public (setters)
        if (!Modifier.isPublic(method.getModifiers())) {
            continue;
        }

        final String name = method.getName();

        // Check if it is a setter or not
        if (!name.startsWith("set")) {
            continue;
        }

        // Extract the name of the attribute to set (e.g. setAttrib1 -> Attrib1)
        final String[] key = name.split("set");

        // Extract the single argument type of the setter (String, Double, Float, etc.)
        final Class<?> parameterType = method.getParameterTypes()[0];

        // Select the right converter (specified inside FUNCTION_MAP) for the argument type
        final Function<String, Object> converter = FUNCTION_MAP.get(parameterType);

        // Invoke the method, applying the converter on the Map value associated
        // to the attribute name (e.g. key[1] = Attrib1)
        method.invoke(some, converter.apply(mapToUse.get(key[1])));
    }
}

This does not require external dependencies.

LppEdd
  • 20,274
  • 11
  • 84
  • 139
  • I'll have a look at this! If you could add few comments here and there that'd help me understand what's going on in there. I will however look them up to understand as well. Thanks. – WhiteBird Mar 05 '19 at 19:07
1

Use reflection.

Here is an suboptimal example solution that uses reflection:

public class Main
{
  public static class BlammyOne
  {
    private String propertyDuece;
    private String propertyTree;
    private String propertyUno;

    public String getPropertyDuece()
    {
      return propertyDuece;
    }

    public String getPropertyTree()
    {
      return propertyTree;
    }

    public String getPropertyUno()
    {
      return propertyUno;
    }

    public void setPropertyDuece(
      final String newValue)
    {
      propertyDuece = newValue;
    }

    public void setPropertyTree(
      final String newValue)
    {
      propertyTree = newValue;
    }

    public void setPropertyUno(
      final String newValue)
    {
      propertyUno = newValue;
    }

    @Override
    public String toString()
    {
      final StringBuilder builder = new StringBuilder();

      builder.append("Uno: ");
      builder.append(propertyUno);
      builder.append(", Duece: ");
      builder.append(propertyDuece);
      builder.append(", Tree: ");
      builder.append(propertyTree);

      return builder.toString();
    }
  }

  public static class BlammyTwo
  {
    private String propertyFive;
    private String propertyFour;
    private String propertyUno;

    public String getPropertyFive()
    {
      return propertyFive;
    }

    public String getPropertyFour()
    {
      return propertyFour;
    }

    public String getPropertyUno()
    {
      return propertyUno;
    }

    public void setPropertyFive(
      final String newValue)
    {
      propertyFive = newValue;
    }

    public void setPropertyFour(
      final String newValue)
    {
      propertyFour = newValue;
    }

    public void setPropertyUno(
      final String newValue)
    {
      propertyUno = newValue;
    }

    @Override
    public String toString()
    {
      final StringBuilder builder = new StringBuilder();

      builder.append("Uno: ");
      builder.append(propertyUno);
      builder.append(", Four: ");
      builder.append(propertyFour);
      builder.append(", Five: ");
      builder.append(propertyFive);

      return builder.toString();
    }
  }

  public static void main(
    final String[] arguments)
  {
    final Map<String, String> valueMap = new HashMap<>();
    final BlammyOne blammyOne = new BlammyOne();
    final BlammyTwo blammyTwo = new BlammyTwo();

    valueMap.put("propertyUno",
      "valueUno");
    valueMap.put("propertyDuece",
      "valueDuece");
    valueMap.put("propertyTree",
      "valueTree");
    valueMap.put("propertyFour",
      "valueFour");
    valueMap.put("propertyFive",
      "valueFive");

    settyBetty(valueMap,
      blammyOne);
    settyBetty(valueMap,
      blammyTwo);

    System.out.println("blammyOne: " + blammyOne);
    System.out.println("blammyTwo: " + blammyTwo);
  }

  private static void settyBetty(
    final Map<String, String> valueMap,
    final Object target)
  {
    final java.lang.reflect.Field[] declaredFieldsArray;

    try
    {
      declaredFieldsArray = target.getClass().getDeclaredFields();

      for (java.lang.reflect.Field currentField : declaredFieldsArray)
      {
        final String fieldValue = currentField.getName();
        final PropertyDescriptor propertyDescriptor;
        final java.lang.reflect.Method writeMethod;

        propertyDescriptor = new PropertyDescriptor(
          currentField.getName(),
          target.getClass());

        writeMethod = propertyDescriptor.getWriteMethod();

        writeMethod.invoke(target,
          fieldValue);
      }
    }
    catch (final SecurityException exception)
    {
      System.out.println("SecurityException: " + exception);
    }
    catch (final IntrospectionException exception)
    {
      System.out.println("IntrospectionException: " + exception);
    }
    catch (IllegalAccessException exception)
    {
      System.out.println("IllegalAccessException: " + exception);
    }
    catch (IllegalArgumentException exception)
    {
      System.out.println("IllegalArgumentException: " + exception);
    }
    catch (InvocationTargetException exception)
    {
      System.out.println("InvocationTargetException: " + exception);
    }
  }
}
DwB
  • 37,124
  • 11
  • 56
  • 82