package org.jboss.elasticsearch.tools.content; /* * JBoss, Home of Professional Open Source * Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @authors tag. All rights reserved. */ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * Utility functions for structured content manipulation. Structured content is commonly represented as Map of Maps * structure. * * @author Vlastimil Elias (velias at redhat dot com) * @author Ryszard Kozmik (rkozmik at redhat dot com) */ public class StructureUtils { /** * Typesafe get value from map as {@link Integer} object instance if possible. * * @param values to get value from. Can be null. * @param key to get value from Map. Must be defined. Dot notation not supported here for nesting! * @return Integer value or null. * @throws NumberFormatException if value can't be converted to the int value * */ public static Integer getIntegerValue(Map<String, Object> values, String key) throws NumberFormatException { if (ValueUtils.isEmpty(key)) throw new IllegalArgumentException("key must be defined"); if (values == null) return null; Object node = values.get(key); if (node == null) { return null; } if (node instanceof Integer) { return (Integer) node; } else if (node instanceof Number) { return new Integer(((Number) node).intValue()); } return Integer.parseInt(node.toString()); } /** * Typesafe get value from map as {@link String}. An {@link Object#toString()} is used for nonstring objects. * * @param values to get value from. Can be null. * @param key to get value from Map. Must be defined. Dot notation not supported here for nesting! * @return value for given key as String. */ public static String getStringValue(Map<String, Object> values, String key) { if (ValueUtils.isEmpty(key)) throw new IllegalArgumentException("key must be defined"); if (values == null) return null; Object node = values.get(key); if (node == null) { return null; } else { return node.toString(); } } /** * Typesafe get value from map as {@link List} of {@link String}. If map contains only one object for given ket, it * creates List from it. An {@link Object#toString()} is used for nonstring objects. * * @param values to get value from. Can be null. * @param key to get value from Map. Must be defined. Dot notation not supported here for nesting! * @return value for given key as List of String. Never empty, null is returned in this cases. */ @SuppressWarnings("unchecked") public static List<String> getListOfStringValues(Map<String, Object> values, String key) { if (ValueUtils.isEmpty(key)) throw new IllegalArgumentException("key must be defined"); if (values == null) return null; Object node = values.get(key); if (node == null) { return null; } else if (node instanceof List) { List<Object> nl = (List<Object>) node; if (nl.isEmpty()) return null; List<String> ret = new ArrayList<String>(); for (Object item : nl) { if (item != null) { String s = ValueUtils.trimToNull(item.toString()); if (s != null) ret.add(s); } } if (ret.isEmpty()) return null; else return ret; } else if (node instanceof Map) { return null; } else { if (node instanceof String) { node = ValueUtils.trimToNull((String) node); if (node == null) return null; } List<String> ret = new ArrayList<String>(); ret.add(node.toString()); return ret; } } /** * Filter data in Map. Leave here only data with keys passed in second parameter. * * @param map to filter data inside * @param keysToLeave keys leaved in map. If <code>null</code> or empty then no filtering is performed! */ public static <T> void filterDataInMap(Map<T, Object> map, Set<T> keysToLeave) { if (map == null || map.isEmpty()) return; if (keysToLeave == null || keysToLeave.isEmpty()) return; Set<T> keysToRemove = new HashSet<T>(map.keySet()); keysToRemove.removeAll(keysToLeave); if (!keysToRemove.isEmpty()) { for (T rk : keysToRemove) { map.remove(rk); } } } /** * Remap data in input Map. Leave here only data with defined keys, but change these keys to new ones if necessary. * Some new key can be same as some other old key, but if two new keys are same, then only latest value is preserved * (given by <code>mapToChange</code> key iteration order). * * @param mapToChange Map to remap data inside. Must be mutable! * @param remapInstructions instructions how to remap. If <code>null</code> or empty then remap is not performed and * <code>mapToChange</code> is not changed! Key in this Map must be same as key in <code>mapToChange</code> * which may leave there. Value in this map means new key of value in <code>mapToChange</code> after * remapping. */ public static <T> void remapDataInMap(Map<T, Object> mapToChange, Map<T, T> remapInstructions) { if (mapToChange == null || mapToChange.isEmpty()) return; if (remapInstructions == null || remapInstructions.isEmpty()) return; Map<T, Object> newMap = new HashMap<T, Object>(); for (T keyOrig : mapToChange.keySet()) { if (remapInstructions.containsKey(keyOrig)) { T keyNew = remapInstructions.get(keyOrig); newMap.put(keyNew, mapToChange.get(keyOrig)); } } mapToChange.clear(); mapToChange.putAll(newMap); } /** * Put value into Map of Maps structure. Dot notation supported for deeper level of nesting. * * @param map Map to put value into * @param field to put value into. Dot notation can be used. * @param value to be added into Map * @throws IllegalArgumentException if value can't be added due something wrong in data structure */ @SuppressWarnings("unchecked") public static void putValueIntoMapOfMaps(Map<String, Object> map, String field, Object value) throws IllegalArgumentException { if (map == null) return; if (ValueUtils.isEmpty(field)) { throw new IllegalArgumentException("field argument must be defined"); } if (field.contains(".")) { String[] tokens = field.split("\\."); int tokensCount = tokens.length; Map<String, Object> levelData = map; for (String tok : tokens) { if (tokensCount == 1) { levelData.put(tok, value); } else { Object o = levelData.get(tok); if (o == null) { Map<String, Object> lv = new LinkedHashMap<String, Object>(); levelData.put(tok, lv); levelData = lv; } else if (o instanceof Map) { levelData = (Map<String, Object>) o; } else { throw new IllegalArgumentException("Cant put value for field '" + field + "' because some element in the path is not Map"); } } tokensCount--; } } else { map.put(field, value); } } /** * Remove value from Map of Maps structure. Dot notation supported for deeper level of nesting. * * @param map Map to remove value from * @param field to remove. Dot notation can be used. * @return object removed from structure if any * @throws IllegalArgumentException if value can't be removed due something wrong in data structure */ @SuppressWarnings("unchecked") public static Object removeValueFromMapOfMaps(Map<String, Object> map, String field) throws IllegalArgumentException { if (map == null) return null; if (ValueUtils.isEmpty(field)) { throw new IllegalArgumentException("field argument must be defined"); } if (field.contains(".")) { String[] tokens = field.split("\\."); int tokensCount = tokens.length; Map<String, Object> levelData = map; for (String tok : tokens) { if (tokensCount == 1) { return levelData.remove(tok); } else { Object o = levelData.get(tok); if (o == null) { return null; } else if (o instanceof Map) { levelData = (Map<String, Object>) o; } else { throw new IllegalArgumentException("Cant remove value for field '" + field + "' because some element in the path is not Map"); } } tokensCount--; } } else { return map.remove(field); } return null; } /** * A recursive method which creates a complete and deep copy of the whole structure. * Immutable elements stay as they are but all Lists and Maps are replaced with new instances. * * @param root with the structure to copy * @return deep copy of the given structure */ @SuppressWarnings("unchecked") public static Object getADeepStructureCopy( Object root ) { if ( root==null ) { return null; } else if ( root instanceof List ) { List<Object> rootList = (List<Object>)root; List<Object> copy = new LinkedList<Object>(); for ( Object elem : rootList ) { Object copiedElem = getADeepStructureCopy(elem); if ( copiedElem==null ) continue; copy.add(copiedElem); } return copy; } else if ( root instanceof Map ) { Map<String,Object> rootMap = (Map<String,Object>)root; Map<String,Object> copy = new LinkedHashMap<String,Object>(rootMap.size()); for ( String key : rootMap.keySet() ) { Object copiedElem = getADeepStructureCopy( rootMap.get(key) ); if ( copiedElem==null ) continue; copy.put( key, copiedElem ); } return copy; } else { // Since it's neither a List nor a Map, it has to be an immutable value which we can copy by reference. return root; } } }