Skip to content

Instantly share code, notes, and snippets.

@hyamamoto
Last active December 23, 2015 05:49
Show Gist options
  • Save hyamamoto/6589802 to your computer and use it in GitHub Desktop.
Save hyamamoto/6589802 to your computer and use it in GitHub Desktop.
A utility class with a generic method to retrieve a value from a Map by a simple dot-separated path string ( A expression you would say... ‘Entity and Property’ style.) Maybe useful for expressing some of your data structure with a single string.

MapValueByPath.pathGet

A utility class with a generic method to retrieve a value from a Map by a simple dot-separated path string ( A expression you would say... ‘Entity and Property’ style.)

The path looks like ...

  • mail.title (applying to Map of Map)
  • label[10] (applying to Map of Array, or Map of List)
  • companyA.departmentC.pc[1].keyboard.id (applying to Map of Map of List of Map of Map of Object)
  • members[Y2013M0362].familyName (applying to Map of Map of String)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* A utility class to retrieve values from a map.<br/>
* (compatible with JDK 1.5)
* </p>
* <b> Examples:</b>
* <dl>
* <dt>'name'</dt>
* <dd>... retrieves a Value from: <b>Map of 'Value' objects with a String type keys</b></dd>
* <dt>'entries[8].label'</dt>
* <dd>... retrieves a Value from: <b>Map of List of Map of 'Value' objects</b></dd>
* <dt>'names[8].entries[2]'</dt>
* <dd>... retrieves a Value from: <b>Map of List of Map of List of 'Value' objects</b></dd>
* <dt>'names[8].entries[2]'</dt>
* <dd>... also retrieves a Value from: <b>Map of Array of Map of Array of 'Value' objects</b></dd>
* </dl>
* and so on...
*
* @author Hiroshi Yamamoto
* @since 2013/09/15
*/
public final class MapValueByPath {
@SuppressWarnings("serial")
public static final class NoSuchPathException extends Exception {
private final String lookingPath;
public NoSuchPathException(String lookingPath) {
super((lookingPath != null ? "Looking: \'" + lookingPath + "\'" : "no such path"));
this.lookingPath = lookingPath;
}
public final String getLookingPath() {
return this.lookingPath;
}
}
@SuppressWarnings("serial")
public static final class PathIndexOutOfBoundsException extends Exception {
public PathIndexOutOfBoundsException(IndexOutOfBoundsException parentEx) {
this(parentEx.getMessage());
}
public PathIndexOutOfBoundsException(String message) {
super(message);
}
}
private MapValueByPath() {}
/**
* Retrieves a value in a map from a path. (A simple {@link Map#get(Object)} extension.)
*
* @param propertyPath
* @return a value found
*/
@SuppressWarnings("unchecked")
public static <V> V pathGet(final/* Immutable */Map<String, V> map,
final/* NotNullable */String propertyPath) //
throws NoSuchPathException, PathIndexOutOfBoundsException {
if (isPathGet(propertyPath)) return pathGetValue(map, propertyPath);
if (map == null) return null;
final int start = propertyPath.indexOf("["), end = propertyPath.indexOf("]");
final V obj;
if (start > -1 && end > -1) {
final String tupleName = propertyPath.substring(0, start);
if (!map.containsKey(tupleName)) throw new NoSuchPathException(propertyPath);
final Object o = map.get(tupleName);
final String p = propertyPath.substring(start + 1, end);
if (o instanceof Object[])
try {
obj = (V) ((Object[]) o)[Integer.valueOf(p)];
} catch (IndexOutOfBoundsException ioob) {
throw new PathIndexOutOfBoundsException(ioob);
} catch (NumberFormatException nfe) {
throw new PathIndexOutOfBoundsException("Index: \'" + p
+ "\' (not a number index of an object array)");
}
else if (o instanceof List<?>)
try {
obj = ((List<V>) o).get(Integer.valueOf(p));
} catch (IndexOutOfBoundsException ioob) {
throw new PathIndexOutOfBoundsException(ioob);
} catch (NumberFormatException nfe) {
throw new PathIndexOutOfBoundsException("Index: \'" + p
+ "\' (not a number index of a list)");
}
else if (o instanceof Map<?, ?>)
if (!((Map<?, ?>) o).containsKey(propertyPath))
throw new NoSuchPathException(propertyPath);
else
obj = ((Map<?, V>) o).get(p);
else
obj = null;
} else {
if (!map.containsKey(propertyPath)) throw new NoSuchPathException(propertyPath);
obj = (V) map.get(propertyPath);
}
return obj;
}
@SuppressWarnings("unchecked")
private static <V> V pathGetValue(final Map<String, V> model, final List<String> paths)//
throws NoSuchPathException, PathIndexOutOfBoundsException {
final V valueInModel = pathGet(model, paths.get(0));
if (paths.size() == 1)
return (V) valueInModel;
else if (valueInModel != null && valueInModel instanceof Map) {
final List<String> tempPaths = new ArrayList<String>(paths);
tempPaths.remove(0);
return (V) pathGetValue((Map<String, V>) valueInModel, tempPaths);
} else if (paths.size() > 1) {
throw new NoSuchPathException(paths.get(1));
} else {
return null;
}
}
private static <V> V pathGetValue(final Map<String, V> model, /* NotNullable */String property) //
throws NoSuchPathException, PathIndexOutOfBoundsException {
return (V) pathGetValue(model, new ArrayList<String>(Arrays.asList(property.split("\\."))));
}
private static boolean isPathGet(final String property) {
return property != null && property.contains(".");
}
public static void main(String[] args) {
final Map<String, Object> rootMap = new LinkedHashMap<String, Object>();
{
final int LEAFSIZE = 16;
rootMap.put("name", "Apple Tree");
rootMap.put("birth", 1999);
final List<Object> branchList;
rootMap.put("branchs", branchList = new ArrayList<Object>(LEAFSIZE));
for (int bIdx = 0; bIdx < LEAFSIZE; bIdx++) {
final Map<String, Object> branchMap;
branchList.add(branchMap = new LinkedHashMap<String, Object>(LEAFSIZE));
branchMap.put("name", "Branch " + bIdx + " of " + LEAFSIZE);
final List<Object> valueList;
branchMap.put("leafs", valueList = new ArrayList<Object>(LEAFSIZE));
for (int lIdx = 0; lIdx < LEAFSIZE; lIdx++)
valueList.add("Leaf " + lIdx + " of " + LEAFSIZE);
}
final List<Object> appleList;
rootMap.put("apples", appleList = new ArrayList<Object>(LEAFSIZE));
for (int l = 0; l < LEAFSIZE; l++)
appleList.add("Apple " + l + " of " + LEAFSIZE);
}
final String[] goodPaths = new String[] {
// existing paths
"name", "birth", "branchs", "branchs[1]", //
"branchs[11].name", "branchs[7].leafs[13]",//
"apples", "apples[14]"};
final String[] badPaths = new String[] {
// non-existing paths
"info", "branch", "branchs[999]", "branchs[goodgrief]", //
"branchs[11].info", "branchs[999].leafs[11]", "branchs[7].leafs[-1]",//
"apple", "apples[999]", "apples[11].seeds[2]"};
for (String[] paths : new String[][] {goodPaths, badPaths})
for (String path : paths)
try {
final Object value = MapValueByPath.pathGet(rootMap, path);
System.out.println("\"" + path + "\" \t-> "
+ (value != null ? "\"" + value + "\"" : "null"));
} catch (PathIndexOutOfBoundsException ex) {
System.out.println("\"" + path + "\" \t-> PathIndexOutOfBounds: \"" + ex.getMessage()
+ "\"");
} catch (NoSuchPathException ex) {
System.out.println("\"" + path + "\" \t-> NoSuchPath: \"" + ex.getMessage() + "\"");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment