package play.data.binding; import play.utils.Utils; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public class ParamNode { private final String name; private final Map<String, ParamNode> _children = new HashMap<String, ParamNode>(8); private String[] values = null; private String originalKey; // splits a string on one-ore-more instances of .[] // this works so that all the following strings (param naming syntaxes) // is resolved into the same structural hierarchy: // a.b.c=12 // a[b].c=12 // a[b][c]=12 // a.b[c]=12 private final static String keyPartDelimiterRegexpString = "[\\.\\[\\]]+"; public ParamNode(String name) { this.name = name; } public String getName() { return name; } public String[] getValues() { return values; } public String getFirstValue(Class<?> type) { if (values == null) { return null; } if (values.length>1 && String.class.equals(type)) { // special handling for string - when multiple values, concatenate them with comma.. return Utils.join(values, ", "); } else { return values[0]; } } public void addChild( ParamNode child) { _children.put(child.name, child); } public ParamNode getChild(String name) { return getChild( name, false); } public ParamNode getChild(String name, boolean returnEmptyChildIfNotFound) { ParamNode child = getChild( name.split(keyPartDelimiterRegexpString)); if (child == null && returnEmptyChildIfNotFound) { child = new ParamNode(name); } return child; } public static class RemovedNode { public final ParamNode removedFrom; public final ParamNode removedNode; public RemovedNode(ParamNode removedFrom, ParamNode removedNode) { this.removedFrom = removedFrom; this.removedNode = removedNode; } } /** * Removes a child from this node, but stores what is removed to list. * The we can later call which will add it back again. * This is a "hack" related to #1195 which makes it possible to reuse the RootParamsNode-structure * if you want to perform the bind-operation multiple times. * * @param name the name of the child-node in this paramNode which should be removed. * @param removedNodesList a list where info about what is removed where is stored. * @return true if anything was removed. */ public boolean removeChild(String name, List<RemovedNode> removedNodesList) { ParamNode removedNode = _children.remove(name); if ( removedNode != null) { removedNodesList.add( new RemovedNode(this, removedNode)); return true; } else { return false; } } public static void restoreRemovedChildren( List<RemovedNode> removedNodesList ) { for ( RemovedNode rn : removedNodesList) { rn.removedFrom._children.put( rn.removedNode.name, rn.removedNode); } } private ParamNode getChild(String[] nestedNames) { ParamNode currentChildNode = this; for (int i=0; i<nestedNames.length; i++) { currentChildNode = currentChildNode._children.get(nestedNames[i]); if (currentChildNode == null) { return null; } } return currentChildNode; } public Collection<ParamNode> getAllChildren() { return _children.values(); } public Set<String> getAllChildrenKeys() { return _children.keySet(); } public void setValue(String[] value, String originalKey) { this.values = value; this.originalKey = originalKey; } public String getOriginalKey() { if (originalKey==null) { return name; } return originalKey; } public static RootParamNode convert(Map<String, String[]> params) { RootParamNode root = new RootParamNode( params); for (Map.Entry<String, String[]> e : params.entrySet()) { String key = e.getKey(); String[] values = e.getValue(); if (values != null && values.length == 0) { values = null; } ParamNode currentParent = root; for ( String name : key.split(keyPartDelimiterRegexpString)) { ParamNode paramNode = currentParent.getChild( name ); if (paramNode ==null) { // first time we see this node - create it and add it to parent paramNode = new ParamNode(name); currentParent.addChild(paramNode); } currentParent = paramNode; } // currentParent is now the last node where we should place the values currentParent.setValue( values, key); } return root; } }