package org.apache.solr.common.util; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.solr.common.SolrException; /** * 一个简单的容器类 用来模拟一个有序的 name/value 键值对列表 * <p> * 不同于 Maps: * </p> * <ul> * <li>Names可以重复</li> * <li>元素具有顺序性</li> * <li>元素可以通过数值索引来访问</li> * <li>Names和 Values可以都为 null</li> * </ul> * * <p> * A NamedList provides fast access by element number, but not by name. * </p> * <p> * NamedList被序列化后,元素的顺序就比较重要了.所以ResponseWriters输出格式如JSON通常会选择一个容易在不同客户端保存并持有顺序的数据结构. * * 如果通过key访问比序列化更重要是,参考{@link SimpleOrderedMap},或者简单使用常规的{@link Map} * </p> * */ public class NamedList<T> implements Cloneable, Serializable, Iterable<Map.Entry<String,T>> { private static final long serialVersionUID = 1957981902839867821L; protected final List<Object> nvPairs; /** Creates an empty instance */ public NamedList() { nvPairs = new ArrayList<Object>(); } /** *创建一个实例,支持Map.Entry<String, ? extends T>[]类型 * * <p> * Modifying the contents of the Entry[] after calling this constructor may change * the NamedList (in future versions of Solr), but this is not guaranteed and should * not be relied upon. To modify the NamedList, refer to {@link #add(String, Object)} * or {@link #remove(String)}. * </p> * * @param nameValuePairs the name value pairs */ public NamedList(Map.Entry<String, ? extends T>[] nameValuePairs) { nvPairs = nameValueMapToList(nameValuePairs); } /** *创建一个实例,支持明确的name/value配对键值. * <p> * When using this constructor, runtime type safety is only guaranteed if * all even numbered elements of the input list are of type "T". * </p> * * @param nameValuePairs underlying List which should be used to implement a NamedList * @deprecated Use {@link #NamedList(java.util.Map.Entry[])} for the NamedList instantiation */ @Deprecated public NamedList(List<Object> nameValuePairs) { nvPairs=nameValuePairs; } /** * * 序列化Map.Entry<String,?> 为一个List.这个List中索引为(0,2,4. ..etc)的是String,奇数元素(1,3,5...etc)为"T"类型. * * @return Modified List as per the above description * @deprecated This a temporary placeholder method until the guts of the class * are actually replaced by List<String, ?>. * @see <a href="https://issues.apache.org/jira/browse/SOLR-912">SOLR-912</a> */ @Deprecated private List<Object> nameValueMapToList(Map.Entry<String, ? extends T>[] nameValuePairs) { List<Object> result = new ArrayList<Object>(); for (Map.Entry<String, ?> ent : nameValuePairs) { result.add(ent.getKey()); result.add(ent.getValue()); } return result; } /** The total number of name/value pairs */ public int size() { return nvPairs.size() >> 1; } /** * The name of the pair at the specified List index * * @return null if no name exists */ public String getName(int idx) { return (String)nvPairs.get(idx << 1); } /** * The value of the pair at the specified List index * * @return may be null */ @SuppressWarnings("unchecked") public T getVal(int idx) { return (T)nvPairs.get((idx << 1) + 1); } /** * 在list的末端添加 name/value键值对. */ public void add(String name, T val) { nvPairs.add(name); nvPairs.add(val); } /** * 修改指定索引处的键值对的name值. */ public void setName(int idx, String name) { nvPairs.set(idx<<1, name); } /** *修改指定索引处的键值对的value值. * * @return 老的对于索引的value值 */ public T setVal(int idx, T val) { int index = (idx<<1)+1; @SuppressWarnings("unchecked") T old = (T)nvPairs.get( index ); nvPairs.set(index, val); return old; } /** *删除指定索引处的键值对的name/value值. * * @return 删除的键值对的value值 */ public T remove(int idx) { int index = (idx<<1); nvPairs.remove(index); @SuppressWarnings("unchecked") T result = (T)nvPairs.remove(index); // same index, as things shifted in previous remove return result; } /** * 扫描指定索引处开始的List列表,并返回第一处name为指定名字的键值对的索引 * * @param name 查询的name,可能为null * @param start 搜索查询起始索引 * @return 第一处匹配键值的索引,如果不匹配,返回-1 */ public int indexOf(String name, int start) { int sz = size(); for (int i=start; i<sz; i++) { String n = getName(i); if (name==null) { if (n==null) return i; // matched null } else if (name.equals(n)) { return i; } } return -1; } /** * 返回第一个name为指定值的实例的value值. * * <p> * NOTE: this runs in linear time (it scans starting at the * beginning of the list until it finds the first pair with * the specified name). * * @return null if not found or if the value stored was null. * @see #indexOf * @see #get(String,int) * */ public T get(String name) { return get(name,0); } /** * Gets the value for the first instance of the specified name * found starting at the specified index. * <p> * NOTE: this runs in linear time (it scans starting at the * specified position until it finds the first pair with * the specified name). * * @return null if not found or if the value stored was null. * @see #indexOf */ public T get(String name, int start) { int sz = size(); for (int i=start; i<sz; i++) { String n = getName(i); if (name==null) { if (n==null) return getVal(i); } else if (name.equals(n)) { return getVal(i); } } return null; } /** * Gets the values for the the specified name * * @param name Name * @return List of values */ public List<T> getAll(String name) { List<T> result = new ArrayList<T>(); int sz = size(); for (int i = 0; i < sz; i++) { String n = getName(i); if (name==n || (name!=null && name.equals(n))) { result.add(getVal(i)); } } return result; } /** * Removes all values matching the specified name * * @param name Name */ private void killAll(String name) { int sz = size(); // Go through the list backwards, removing matches as found. for (int i = sz - 1; i >= 0; i--) { String n = getName(i); if (name==n || (name!=null && name.equals(n))) { remove(i); } } } /** * 递归解析NameList结构到一个指定的元素中.随着NameList树的解析,最后一个元素可以是任何类型,包括NameList, * 但前面所有元素必须NamedList对象本身.如果指定的层次结构不存在,那么返回为null.NameList是允许null值的, * 所以最后返回的值也可以是null; * * 这个方法对解析solr响应的/admin/mbeans 句柄特别有用,当然也同样用于其他复杂结构的工作处理. * * 推荐明确抛出返回值,一个比较安全的选择及时接受Object对象的返回值,然后去确认它的类型. * * 使用示例: * * String coreName = (String) response.findRecursive * ("solr-mbeans", "CORE", "core", "stats", "coreName"); * long numDoc = (long) response.findRecursive * ("solr-mbeans", "CORE", "searcher", "stats", "numDocs"); * * @param args * One or more strings specifying the tree to navigate. * @return the last entry in the given path hierarchy, null if not found. */ public Object findRecursive(String... args) { NamedList<?> currentList = null; Object value = null; for (int i = 0; i < args.length; i++) { String key = args[i]; /* * 第一次循环,currentList为null,所以我们把 NameList这个对象分配给currentList. * 然后我们检索这个列表的第一个key,然后把key对应的对象值 赋值给value 变量. * * 第二次循环遍历时,首先确认上一次我们获得的value是否是一个NameList. * 如果是NameList对象,那么将该对象赋值给currentList,抓取下一个key对应的value值,并开始遍历. * 如果不是一个NameList对象,重置value值为null,中断遍历. * * 赋值value为null,然后结束循环遍历,看起来做的不正确,但是有一个非常简单的原因, * 它的工作原理:如果循环到最后一个key,在检索到对应的value值时会自然结束循环遍历,并且这段代码永远不会执行的. * */ if (currentList == null) { currentList = this; } else { if (value instanceof NamedList) { currentList = (NamedList<?>) value; } else { value = null; break; } } /* * 这里不再需要验证currentList是否为null.如果当前list为null的话,上面代码value instanceof NamedList * 的检查会失败的.如果这种情况发生的话,循环就会在这之前结束. * */ value = currentList.get(key, 0); } return value; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(‘{‘); int sz = size(); for (int i=0; i<sz; i++) { if (i != 0) sb.append(‘,‘); sb.append(getName(i)); sb.append(‘=‘); sb.append(getVal(i)); } sb.append(‘}‘); return sb.toString(); } /** * * 帮助类实现 Map.Entry<String, T>用来存储 NamedList中的key-value关系. */ public static final class NamedListEntry<T> implements Map.Entry<String,T> { public NamedListEntry() { } public NamedListEntry(String _key, T _value) { key = _key; value = _value; } @Override public String getKey() { return key; } @Override public T getValue() { return value; } @Override public T setValue(T _value) { T oldValue = value; value = _value; return oldValue; } private String key; private T value; } /** * 遍历Map,依次增加了它的键/值对 * */ public boolean addAll(Map<String,T> args) { for (Map.Entry<String, T> entry : args.entrySet() ) { add(entry.getKey(), entry.getValue()); } return args.size()>0; } /**将给定的NameList的元素添加到当前NameList对象中 */ public boolean addAll(NamedList<T> nl) { nvPairs.addAll(nl.nvPairs); return nl.size()>0; } /** * 生成一个<i>浅拷贝</i>的NameList. */ @Override public NamedList<T> clone() { ArrayList<Object> newList = new ArrayList<Object>(nvPairs.size()); newList.addAll(nvPairs); return new NamedList<T>(newList); } //---------------------------------------------------------------------------- // Iterable 接口 //---------------------------------------------------------------------------- /** * 支持Iterable接口 */ @Override public Iterator<Map.Entry<String,T>> iterator() { final NamedList<T> list = this; Iterator<Map.Entry<String,T>> iter = new Iterator<Map.Entry<String,T>>() { int idx = 0; @Override public boolean hasNext() { return idx < list.size(); } @Override public Map.Entry<String,T> next() { final int index = idx++; Map.Entry<String,T> nv = new Map.Entry<String,T>() { @Override public String getKey() { return list.getName( index ); } @Override public T getValue() { return list.getVal( index ); } @Override public String toString() { return getKey()+"="+getValue(); } @Override public T setValue(T value) { return list.setVal(index, value); } }; return nv; } @Override public void remove() { throw new UnsupportedOperationException(); } }; return iter; } /** * NOTE: 线性时间执行 (it scans starting at the * beginning of the list until it finds the first pair with * the specified name). */ public T remove(String name) { int idx = indexOf(name, 0); if(idx != -1) return remove(idx); return null; } /** * 删除并返回指定name的所有values.如果没有匹配,返回null.这个方法返回所有匹配对象,不考虑数据类型. * 如果解析solr配置选项,{@link #removeConfigArgs(String)} 或者 {@link #removeBooleanArg(String)} * 方法是一个更好的选择. * * @param name Name * @return List of values */ public List<T> removeAll(String name) { List<T> result = new ArrayList<T>(); result = getAll(name); if (result.size() > 0 ) { killAll(name); return result; } return null; } /** * 用来从NameList对象中获取一个boolean参数.如果name不存在,返回null.如果对应name有多个value, * 或者这个值不是Boolean或者String类型,会抛出一个异常.如果只有一个值存在,并且是 一个Boolean或者String类型 * 这个value值就会删除并返回一个Boolean值.如果抛出异常,NamedList将不会改变. * 参考 {@link #removeAll(String)}和 {@link #removeConfigArgs(String)}的更多方式:从NamedList收集的配置信息 * * @param name NameList中要查询的key值. * * @return The boolean value found. * @throws SolrException * If multiple values are found for the name or the value found is * not a Boolean or a String. */ public Boolean removeBooleanArg(final String name) { Boolean bool; List<T> values = getAll(name); if (0 == values.size()) { return null; } if (values.size() > 1) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Only one ‘" + name + "‘ is allowed"); } Object o = get(name); if (o instanceof Boolean) { bool = (Boolean)o; } else if (o instanceof CharSequence) { bool = Boolean.parseBoolean(o.toString()); } else { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "‘" + name + "‘ must have type ‘bool‘ or ‘str‘; found " + o.getClass()); } remove(name); return bool; } /** * 从NamedList对象中获取一个或者多个参数用来保存配置参数.找到所有匹配给定name的entries. * 如果都是字符串或者字符串数组,从NameList中删除它们,并返回{@link Collection}. * * 如果返回的集合是一个{@link ArrayList},那么参数顺序将被保存.如果关联name的value值不是字符串或者是字符串数组, * 那么抛出一个SolrException异常.异常抛出,NameList不会更改.如果没有匹配的值,返回一个空的集合.如果需要删除, * 并在检索到所有匹配的条目时不考虑数据类型,那么使用 {@link #removeAll(String)} 替代. * {@link #removeBooleanArg(String)} 方法用来检索一个boolean参数. * * @param name NameList中要查询的key值 * @return A collection of the values found. * @throws SolrException * If values are found for the input key that are not strings or * arrays of strings. */ @SuppressWarnings("rawtypes") public Collection<String> removeConfigArgs(final String name) throws SolrException { List<T> objects = getAll(name); List<String> collection = new ArrayList<String>(size() / 2); final String err = "init arg ‘" + name + "‘ must be a string " + "(ie: ‘str‘), or an array (ie: ‘arr‘) containing strings; found: "; for (Object o : objects) { if (o instanceof String) { collection.add((String) o); continue; } // If it‘s an array, convert to List (which is a Collection). if (o instanceof Object[]) { o = Arrays.asList((Object[]) o); } // If it‘s a Collection, collect each value. if (o instanceof Collection) { for (Object item : (Collection) o) { if (!(item instanceof String)) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, err + item.getClass()); } collection.add((String) item); } continue; } throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, err + o.getClass()); } if (collection.size() > 0) { killAll(name); } return collection; } public void clear() { nvPairs.clear(); } @Override public int hashCode() { return nvPairs.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof NamedList)) return false; NamedList<?> nl = (NamedList<?>) obj; return this.nvPairs.equals(nl.nvPairs); } }
测试类:
package org.apache.solr.common.util; import java.util.ArrayList; import java.util.List; import org.apache.lucene.util.LuceneTestCase; import org.apache.solr.common.SolrException; public class NamedListTest extends LuceneTestCase { public void testRemove() { NamedList<String> nl = new NamedList<String>(); nl.add("key1", "value1"); nl.add("key2", "value2"); assertEquals(2, nl.size()); String value = null; value = nl.remove(0); assertEquals("value1", value); assertEquals(1, nl.size()); value = nl.remove("key2"); assertEquals("value2", value); assertEquals(0, nl.size()); } public void testRemoveAll() { NamedList<String> nl = new NamedList<String>(); nl.add("key1", "value1-1"); nl.add("key2", "value2-1"); nl.add("key1", "value1-2"); nl.add("key2", "value2-2"); nl.add("key1", "value1-3"); nl.add("key2", "value2-3"); nl.add("key1", "value1-4"); nl.add("key2", "value2-4"); nl.add("key1", "value1-5"); nl.add("key2", "value2-5"); nl.add("key1", "value1-6"); assertEquals(11, nl.size()); List<String> values = null; values = nl.removeAll("key1"); assertEquals("value1-1", values.get(0)); assertEquals("value1-3", values.get(2)); assertEquals(6, values.size()); assertEquals(5, nl.size()); values = nl.removeAll("key2"); assertEquals(5, values.size()); assertEquals(0, nl.size()); } public void testRemoveArgs() { NamedList<Object> nl = new NamedList<Object>(); nl.add("key1", "value1-1"); nl.add("key2", "value2-1"); nl.add("key1", "value1-2"); nl.add("key2", "value2-2"); nl.add("key1", "value1-3"); nl.add("key2", "value2-3"); nl.add("key1", "value1-4"); nl.add("key2", "value2-4"); nl.add("key1", "value1-5"); nl.add("key2", "value2-5"); nl.add("key1", "value1-6"); nl.add("key2", 0); nl.add("key2", "value2-7"); assertEquals(13, nl.size()); List<String> values = (ArrayList<String>) nl.removeConfigArgs("key1"); assertEquals("value1-1", values.get(0)); assertEquals("value1-3", values.get(2)); assertEquals(6, values.size()); assertEquals(7, nl.size()); try { values = (ArrayList<String>) nl.removeConfigArgs("key2"); fail(); } catch(SolrException e) { // Expected exception. assertTrue(true); } // nl should be unmodified when removeArgs throws an exception. assertEquals(7, nl.size()); } public void testRecursive() { /** * NL结构说明 */ // key1 // key2 // - key2a // - key2b // --- key2b1 // --- key2b2 // - key2c // - k2int1 // key3 // - key3a // --- key3a1 // --- key3a2 // --- key3a3 // - key3b // - key3c // 实例化一个多样的NL结构. NamedList<String> nl2b = new NamedList<String>(); nl2b.add("key2b1", "value2b1"); nl2b.add("key2b2", "value2b2"); NamedList<String> nl3a = new NamedList<String>(); nl3a.add("key3a1", "value3a1"); nl3a.add("key3a2", "value3a2"); nl3a.add("key3a3", "value3a3"); NamedList<Object> nl2 = new NamedList<Object>(); nl2.add("key2a", "value2a"); nl2.add("key2b", nl2b); nl2.add("k2int1", (int) 5); NamedList<Object> nl3 = new NamedList<Object>(); nl3.add("key3a", nl3a); nl3.add("key3b", "value3b"); nl3.add("key3c", "value3c"); nl3.add("key3c", "value3c2"); NamedList<Object> nl = new NamedList<Object>(); nl.add("key1", "value1"); nl.add("key2", nl2); nl.add("key3", nl3); // 简单的三级检查. String test1 = (String) nl.findRecursive("key2", "key2b", "key2b2"); assertEquals("value2b2", test1); String test2 = (String) nl.findRecursive("key3", "key3a", "key3a3"); assertEquals("value3a3", test2); // 二级检查 String test3 = (String) nl.findRecursive("key3", "key3c"); assertEquals("value3c", test3); // 检查无效值返回null. String test4 = (String) nl.findRecursive("key3", "key3c", "invalid"); assertEquals(null, test4); String test5 = (String) nl.findRecursive("key3", "invalid", "invalid"); assertEquals(null, test5); String test6 = (String) nl.findRecursive("invalid", "key3c"); assertEquals(null, test6); // 验证检索NamedList对象具有正确的类型. Object test7 = nl.findRecursive("key2", "key2b"); assertTrue(test7 instanceof NamedList); // Integer检查. int test8 = (Integer) nl.findRecursive("key2", "k2int1"); assertEquals(5, test8); // Check that a single argument works the same as get(String). String test9 = (String) nl.findRecursive("key1"); assertEquals("value1", test9); // enl == 明确嵌套列表 // // key1 // - key1a // - key1b // key2 (null list) NamedList<NamedList<String>> enl = new NamedList<NamedList<String>>(); NamedList<String> enlkey1 = new NamedList<String>(); NamedList<String> enlkey2 = null; enlkey1.add("key1a", "value1a"); enlkey1.add("key1b", "value1b"); enl.add("key1", enlkey1); enl.add("key2", enlkey2); // 和上面的测试很类似, 只是重复了明确嵌套的对象类型. String enltest1 = (String) enl.findRecursive("key1", "key1a"); assertEquals("value1a", enltest1); String enltest2 = (String) enl.findRecursive("key1", "key1b"); assertEquals("value1b", enltest2); // 验证:在存储一个null值时,get方法返回的是一个null,那么验证这个递归方法. Object enltest3 = enl.get("key2"); assertNull(enltest3); Object enltest4 = enl.findRecursive("key2"); assertNull(enltest4); } }
时间: 2024-10-06 13:26:43