|
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() + "\""); |
|
} |
|
} |
|
} |