MapUtils

Vor kurzem stand ich vor dem Problem, unter Java 1.4 zwei Maps miteinander zu "verschmelzen". Dabei kam der Wunsch auf, ähnlich der StringUtils eine Helperklasse zur Hand zu haben. Daraus ist dan folgende kleine Klasse entstanden, welche mir aus zwei Maps eine macht. Außerdem kann man sich Teil-Maps zurückgeben lassen, oder aber Key-Value Paare, welche entweder in Map A oder Map B, aber nicht in beiden vorkommen.

/*
 * Copyright (c) 2009 Ronny Friedland
 * All rights reserved.
 */
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Map;

/**
 * Helperklasse fuer die Modifikation zweier {@link Map}.
 */
public class MapUtils {

  /**
   * Checks if the given map contains no data.
   *
   * @param map
   * @return
   */
  public static boolean isMapEmpty(Map map) {
    boolean isEmpty = true;

    if (null != map && !map.keySet().isEmpty()) {
      isEmpty = false;
    }

    return isEmpty;
  }

  /**
   * Returns the demanded part of the map. First element is the value on
   * position start. Last element is the value on position
   * end-1.
   *
   * @param map
   * @param start
   * @param end
   * @return
   */
  public static Map getMapPart(Map map, int start, int end) {
    Map resultMap = initiateMap(map);

    int count = 0;
    for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) {
      Object key = iterator.next();
      if (count >= start && count < end) {
        resultMap.put(key, map.get(key));
      }
      count++;
    }

    return resultMap;
  }

  /**
   * Returns the tail of the map. First element is the value on position
   * start.
   *
   * @param map
   * @param start
   * @return
   */
  public static Map getMapPartTail(Map map, int start) {
    return getMapPart(map, start, Integer.MAX_VALUE);
  }

  /**
   * Returns the head of the map. Last element is the value on position
   * end-1.
   *
   * @param map
   * @param end
   * @return
   */
  public static Map getMapPartHead(Map map, int end) {
    return getMapPart(map, Integer.MIN_VALUE, end);
  }

  /**
   * Merges two maps to one. If the already exists a key, the values will be
   * merged by there type. If the type is unknown or the types are not equal,
   * the value will be null for the key.
   *
   * @param m1
   * @param m2
   * @return
   */
  public static Map mergeMaps(Map m1, Map m2) {

    Map result = initiateMap(m1);

    if (null != result) {
      // merge both maps to one ...
      result.putAll(m1);
      for (Iterator iterator = m2.keySet().iterator(); iterator.hasNext();) {
        Object key = iterator.next();
        if (result.containsKey(key)) {
          result.put(key, mergeData(result.get(key), m2.get(key)));
        } else {
          result.put(key, m2.get(key));
        }

      }
    }

    return result;
  }

  /**
   * Finds the differences between the both maps and returns a new {@link Map}
   * with the key-value pairs.
   *
   * @param m1
   * @param m2
   * @return
   */
  public static Map getDifferenceMap(Map m1, Map m2) {
    Map result = initiateMap(m1);

    if (null != result) {
      // find differences ...
      for (Iterator iterator = m1.keySet().iterator(); iterator.hasNext();) {
        Object key = iterator.next();
        if (!m2.containsKey(key)) {
          result.put(key, m1.get(key));
        }
      }
      for (Iterator iterator = m2.keySet().iterator(); iterator.hasNext();) {
        Object key = iterator.next();
        if (!m1.containsKey(key)) {
          result.put(key, m2.get(key));
        }
      }
    }

    return result;
  }

  private static Object mergeData(Object value1, Object value2) {
    Object result = null;

    if (null != value1 && null != value2
        && value1.getClass().equals(value2.getClass())) {
      if (value1 instanceof String) {
        result = ((String) value1) + ((String) value2);
      }
      if (value1 instanceof Integer) {
        result = ((Integer) value1) + ((Integer) value2);
      }
      // TODO: implement more ...
    }

    return result;
  }

  private static Map initiateMap(Map map) {
    Map result = null;

    try {
      String clazzName = map.getClass().getName();
      Class clazz = Class.forName(clazzName);

      Constructor ct = clazz.getConstructor();

      result = (Map) ct.newInstance();

    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (SecurityException e) {
      e.printStackTrace();
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
    } catch (IllegalArgumentException e) {
      e.printStackTrace();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
    return result;
  }
}

Die resultierende Map ist vom Typ der ersten übergebenen Map. Hier ist ein kleines Anwendungsbeispiel:

Map m1 = new TreeMap();
m1.put("test1", 1);
m1.put("test2", 2);
m1.put("test3", 3);
m1.put("test4", 4);
Map m2 = new HashMap();
m2.put("test1", 1);
m2.put("test2", 2);
m2.put("test3", 3);
m2.put("test5", 5);

Der Aufruf

MapUtils.mergeMaps(m1, m2)

liefert eine

java.util.TreeMap

mit folgendem Inhalt

{test1=2, test2=4, test3=6, test4=4, test5=5}

Da es sich um Integer-Values handelt, werden diese aufsummiert. Strings hingegen werden verkettet. Weitere Datentypen müssten noch hinzugefügt werden ung ggf. eine Default-Action implementiert werden.

Will man sich nun aber die Elemente 2 bis 3 der Map liefern lassen, erreicht man das mit dem folgenden Aufruf:

MapUtils.getMapPart(m1, 2, 4)
Analog dazu existieren Methoden, mit denen die ersten bzw. die letzten Elemente der Map ermittelt werden können.
Wirklich Sinn macht dieses Feature allerdings in meinen Augen bei einer HashMap nicht ...

Die Utilklasse bietet weiterhin eine Methode, mit der zwei Maps miteinander verglichen werden können. Geliefert wird eine Map, welche die Unterschiede liefert. Basis dafür sind die Keys der Map.