7
\$\begingroup\$

I was looking for a solution for a way to easily merge maps of serialized Yaml and came up with this.

Reference to a similar question (not asked by me):

https://stackoverflow.com/questions/25773567/recursive-merge-of-n-level-maps

Here is my take on how to handle this.

MapUtil.java:

import java.util.*;

static class MapUtil {
   public static <K> Map<K, Object> merge(
      Map<K, Object> l,
      Map<K, Object> r)
   {
      for (K key : r.keySet()) {
         Object lValue = l.get(key);
         Object rValue = r.get(key);
         
         if (lValue instanceof Map && rValue instanceof Map) {
            Map<K, Object> lMap = (Map<K, Object>)lValue;
            Map<K, Object> rMap = (Map<K, Object>)rValue;
            
            l.put(key, merge(lMap, rMap));
            
            continue;
         }
         
         if (lValue instanceof List || rValue instanceof List) {
            if (lValue == null) {
               l.put(key, rValue);
               
               continue;
            }
            
            List<Object> lList = (List<Object>)lValue;
            List<Object> rList = (List<Object>)rValue;
            
            if (rList != null)
               lList.addAll(rList);
            
            continue;
         }
         
         if (rValue != null)
            l.put(key, rValue);
      }
      
      return l;
   }
}

Full code with basic example.

Run.java:

import java.util.*;

public class Run {
   public static Map<String, Object> referenceObject() {
      Map<String, Object> object = new TreeMap<>();
      Map<String, Object> properties = new TreeMap<>();
      List<Object> frames = new LinkedList<>();
      Map<String, Object> color = new TreeMap<>();
      
      color.put("red", 0);
      color.put("green", 0);
      color.put("blue", 0);
      
      properties.put("frames", frames);
      properties.put("color", color);
      
      object.put("properties", properties);
      
      // {properties={color={blue=0, green=0, red=0}, position={x=0, y=0, z=0}}}
      
      return object;
   }
   
   public static Map<String, Object> mixColor(
      Integer red,
      Integer green,
      Integer blue)
   {
      Map<String, Object> object = new TreeMap<>();
      Map<String, Object> properties = new TreeMap<>();
      Map<String, Object> color = new TreeMap<>();
      
      if (red != null)
         color.put("red", red);
      
      if (green != null)
         color.put("green", green);
      
      if (blue != null)
         color.put("blue", blue);
      
      properties.put("color", color);
      
      object.put("properties", properties);
      
      // {properties={color={red=?, green=?, blue=?}}}
      
      return object;
   }
   
   public static Map<String, Object> addFramePosition(
      Integer x,
      Integer y,
      Integer z)
   {
      Map<String, Object> object = new TreeMap<>();
      Map<String, Object> properties = new TreeMap<>();
      Map<String, Object> position = new TreeMap<>();
      List<Object> frames = new LinkedList<>();
      
      if (x != null)
         position.put("x", x);
      
      if (y != null)
         position.put("y", y);
      
      if (z != null)
         position.put("z", z);
      
      frames.add(position);
      
      properties.put("frames", frames);
      
      object.put("properties", properties);
      
      // {properties={position={x=?, y=?, z=?}}}
      
      return object;
   }
   
   public static void main(
      String[] args)
   {
      Map<String, Object> object = referenceObject();
      
      // Mix color red
      MapUtil.merge(object, mixColor(1, 0, 0));
      
      // Move object along x by 1
      MapUtil.merge(object, addFramePosition(1, null, null));
      
      // Move object along y by 2
      MapUtil.merge(object, addFramePosition(null, 2, null));
      
      // Move object along z by 3
      MapUtil.merge(object, addFramePosition(null, null, 3));
      
      // {properties={color={blue=0, green=0, red=1}, frames=[{x=1}, {y=2}, {z=3}]}}
      
      System.out.println(object);
   }
}

The example code creates a reference object which is then given color and a set of animation frames as a list.


UPDATE

I was experimenting with this and found an interesting use case. Because each change is an additive, all changes can be logged and "played back" in sequence to reconstruct its creation. With some added effort, it would be possible to instead store each change as a delta, which would allow the log to be played back in reverse as well.

Full example

import java.util.*;

public class Run {
   public static class RawSceneObject extends TreeMap<String, Object> {
      RawProperties properties = new RawProperties();
      RawFrame frames = new RawFrame();
      RawColor color = new RawColor();
      
      private static final long serialVersionUID = 2643930719380807841L;
      
      public static class RawProperties extends TreeMap<String, Object> {
         private static final long serialVersionUID = 3107728026858104788L;
      }
      
      public static class RawPosition extends TreeMap<String, Object> {
         private static final long serialVersionUID = 6707504298543339821L;
      }
      
      public static class RawColor extends TreeMap<String, Object> {
         private static final long serialVersionUID = 8412996397177357214L;
      }
      
      public static class RawFrame extends LinkedList<Object> {
         private static final long serialVersionUID = 8978167775407603695L;
      }
      
      public static RawSceneObject mixColor(
         Integer red,
         Integer green,
         Integer blue)
      {
         RawSceneObject object = new RawSceneObject();
         RawProperties properties = new RawProperties();
         RawColor color = new RawColor();
         
         if (red != null)
            color.put("red", red);
         
         if (green != null)
            color.put("green", green);
         
         if (blue != null)
            color.put("blue", blue);
         
         properties.put("color", color);
         
         object.put("properties", properties);
         
         // {properties={color={red=?, green=?, blue=?}}}
         
         return object;
      }
      
      public static RawSceneObject addFramePosition(
         Integer x,
         Integer y,
         Integer z)
      {
         RawSceneObject object = new RawSceneObject();
         RawProperties properties = new RawProperties();
         RawPosition position = new RawPosition();
         RawFrame frames = new RawFrame();
         
         if (x != null)
            position.put("x", x);
         
         if (y != null)
            position.put("y", y);
         
         if (z != null)
            position.put("z", z);
         
         frames.add(position);
         
         properties.put("frames", frames);
         
         object.put("properties", properties);
         
         // {properties={position={x=?, y=?, z=?}}}
         
         return object;
      }
      
      public RawSceneObject() {
         color.put("red", 0);
         color.put("green", 0);
         color.put("blue", 0);
         
         properties.put("frames", frames);
         properties.put("color", color);
         
         put("properties", properties);
         
         // {properties={color={blue=0, green=0, red=0}, position={x=0, y=0, z=0}}}
      }
   }
   
   public static class SceneObject {
      RawSceneObject object = new RawSceneObject();
      List<RawSceneObject> log = new LinkedList<>();
      
      public void mixColor(
         Integer red,
         Integer green,
         Integer blue)
      {
         RawSceneObject raw = RawSceneObject.mixColor(red, green, blue);
         
         log.add(raw);
         
         MapUtil.merge(object, raw);
      }
      
      public void addFramePosition(
         Integer x,
         Integer y,
         Integer z)
      {
         RawSceneObject raw = RawSceneObject.addFramePosition(x, y, z);
         
         log.add(raw);
         
         MapUtil.merge(object, raw);
      }
      
      public void replayLog() {
         System.out.println("  : " + new RawSceneObject());
         
         for (RawSceneObject raw : log)
            System.out.println("  + " + raw);
         
         System.out.println("  = " + this);
      }
      
      public String toString() {
         return object.toString();
      }
   }
   
   public static void main(
      String[] args)
   {
      SceneObject object = new SceneObject();
      
      // Mix color red
      object.mixColor(1, null, null);
      
      // Move object along x by 1
      object.addFramePosition(1, null, null);
      
      // Move object along y by 2
      object.addFramePosition(null, 2, null);
      
      // Move object along z by 3
      object.addFramePosition(null, null, 3);
      
      System.out.printf("Replaying log for... [%s]\n",
         object.getClass().getName());
      
      object.replayLog();
   }
}

The output is:

Replaying log for... [package.Run$SceneObject]
  : {properties={color={blue=0, green=0, red=0}, frames=[]}}
  + {properties={color={red=1}}}
  + {properties={frames=[{x=1}]}}
  + {properties={frames=[{y=2}]}}
  + {properties={frames=[{z=3}]}}
  = {properties={color={blue=0, green=0, red=1}, frames=[{x=1}, {y=2}, {z=3}]}}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Overall, very nice code I have to say, well written.


public static <K> Map<K, Object> merge(
   Map<K, Object> l,
   Map<K, Object> r)
{

I'd use more descriptive names, like base and addition or something like that. I'm just a little bit tired right now, sorry, to come up with better names. What you want the names to convey is "this is the base, and this one will override values in base".


         if (lValue instanceof Map && rValue instanceof Map) {
            Map<K, Object> lMap = (Map<K, Object>)lValue;
            Map<K, Object> rMap = (Map<K, Object>)rValue;
            
            l.put(key, merge(lMap, rMap));
            
            continue;
         }
         
         if (lValue instanceof List || rValue instanceof List) {

I'd use else if instead of continue, would make it much easier to read.


if (lValue instanceof Map && rValue instanceof Map) {

instanceof will throw a NullPointerException if the given object is null, so you want to check that first.


   public static <K> Map<K, Object> merge(
      Map<K, Object> l,
      Map<K, Object> r)
   {
      // ...
      return l;
   }

This is confusing. Either manipulate the given object, or return a copy of it, and make sure that you make the behavior clear in the documentation.


            List<Object> lList = (List<Object>)lValue;
            List<Object> rList = (List<Object>)rValue;
            
            if (rList != null)
               lList.addAll(rList);

You're manipulating the List instance, which might or might not be desired from the callers point of view.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.