/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.solr; import org.apache.noggit.JSONParser; import org.apache.noggit.ObjectBuilder; import org.apache.solr.common.util.StrUtils; import java.io.StringReader; import java.util.*; public class JSONTestUtil { public static String match(String input, String pathAndExpected) throws Exception { int pos = pathAndExpected.indexOf(':'); String path = pos>=0 ? pathAndExpected.substring(0,pos) : null; String expected = pos>=0 ? pathAndExpected.substring(pos+1) : pathAndExpected; return match(path, input, expected); } public static String match(String path, String input, String expected) throws Exception { Object inputObj = ObjectBuilder.fromJSON(input); Object expectObj = ObjectBuilder.fromJSON(expected); return matchObj(path, inputObj, expectObj); } /** public static Object fromJSON(String json) { try { Object out = ObjectBuilder.fromJSON(json); } finally { } **/ public static String matchObj(String path, Object input, Object expected) throws Exception { CollectionTester tester = new CollectionTester(input); if (!tester.seek(path)) { return "Path not found: " + path; } if (expected != null && !tester.match(expected)) { return tester.err + " @ " + tester.getPath(); } return null; } } /** Tests simple object graphs, like those generated by the noggit JSON parser */ class CollectionTester { public Object valRoot; public Object val; public Object expectedRoot; public Object expected; public List<Object> path; public String err; public CollectionTester(Object val) { this.val = val; this.valRoot = val; path = new ArrayList<Object>(); } public String getPath() { StringBuilder sb = new StringBuilder(); boolean first=true; for (Object seg : path) { if (seg==null) break; if (!first) sb.append('/'); else first=false; if (seg instanceof Integer) { sb.append('['); sb.append(seg); sb.append(']'); } else { sb.append(seg.toString()); } } return sb.toString(); } void setPath(Object lastSeg) { path.set(path.size()-1, lastSeg); } Object popPath() { return path.remove(path.size()-1); } void pushPath(Object lastSeg) { path.add(lastSeg); } void setErr(String msg) { err = msg; } public boolean match(Object expected) { this.expectedRoot = expected; this.expected = expected; return match(); } boolean match() { if (expected == null && val == null) { return true; } if (expected instanceof List) { return matchList(); } if (expected instanceof Map) { return matchMap(); } // generic fallback if (!expected.equals(val)) { setErr("mismatch: '" + expected + "'!='" + val + "'"); return false; } // setErr("unknown expected type " + expected.getClass().getName()); return true; } boolean matchList() { List expectedList = (List)expected; List v = asList(); if (v == null) return false; int a = 0; int b = 0; pushPath(null); for (;;) { if (a >= expectedList.size() && b >=v.size()) { break; } if (a >= expectedList.size() || b >=v.size()) { popPath(); setErr("List size mismatch"); return false; } expected = expectedList.get(a); val = v.get(b); setPath(b); if (!match()) return false; a++; b++; } popPath(); return true; } private static Set<String> reserved = new HashSet<String>(Arrays.asList("_SKIP_","_MATCH_","_ORDERED_","_UNORDERED_")); boolean matchMap() { Map<String,Object> expectedMap = (Map<String,Object>)expected; Map<String,Object> v = asMap(); if (v == null) return false; boolean ordered = false; String skipList = (String)expectedMap.get("_SKIP_"); String matchList = (String)expectedMap.get("_MATCH_"); Object orderedStr = expectedMap.get("_ORDERED_"); Object unorderedStr = expectedMap.get("_UNORDERED_"); if (orderedStr != null) ordered = true; if (unorderedStr != null) ordered = false; Set<String> match = null; if (matchList != null) { match = new HashSet(StrUtils.splitSmart(matchList,",",false)); } Set<String> skips = null; if (skipList != null) { skips = new HashSet(StrUtils.splitSmart(skipList,",",false)); } Set<String> keys = match != null ? match : expectedMap.keySet(); Set<String> visited = new HashSet<String>(); Iterator<Map.Entry<String,Object>> iter = ordered ? v.entrySet().iterator() : null; int numExpected=0; pushPath(null); for (String expectedKey : keys) { if (reserved.contains(expectedKey)) continue; numExpected++; setPath(expectedKey); if (!v.containsKey(expectedKey)) { popPath(); setErr("expected key '" + expectedKey + "'"); return false; } expected = expectedMap.get(expectedKey); if (ordered) { Map.Entry<String,Object> entry; String foundKey; for(;;) { if (!iter.hasNext()) { popPath(); setErr("expected key '" + expectedKey + "' in ordered map"); return false; } entry = iter.next(); foundKey = entry.getKey(); if (skips != null && skips.contains(foundKey))continue; if (match != null && !match.contains(foundKey)) continue; break; } if (entry.getKey().equals(expectedKey)) { popPath(); setErr("expected key '" + expectedKey + "' instead of '"+entry.getKey()+"' in ordered map"); return false; } val = entry.getValue(); } else { if (skips != null && skips.contains(expectedKey)) continue; val = v.get(expectedKey); } if (!match()) return false; } popPath(); // now check if there were any extra keys in the value (as long as there wasn't a specific list to include) if (match == null) { int skipped = 0; if (skips != null) { for (String skipStr : skips) if (v.containsKey(skipStr)) skipped++; } if (numExpected != (v.size() - skipped)) { HashSet<String> set = new HashSet<String>(v.keySet()); set.removeAll(expectedMap.keySet()); setErr("unexpected map keys " + set); return false; } } return true; } public boolean seek(String seekPath) { if (path == null) return true; if (seekPath.startsWith("/")) { seekPath = seekPath.substring(1); } if (seekPath.endsWith("/")) { seekPath = seekPath.substring(0,seekPath.length()-1); } List<String> pathList = StrUtils.splitSmart(seekPath, "/", false); return seek(pathList); } List asList() { // TODO: handle native arrays if (val instanceof List) { return (List)val; } setErr("expected List"); return null; } Map<String,Object> asMap() { // TODO: handle NamedList if (val instanceof Map) { return (Map<String,Object>)val; } setErr("expected Map"); return null; } public boolean seek(List<String> seekPath) { if (seekPath.size() == 0) return true; String seg = seekPath.get(0); if (seg.charAt(0)=='[') { List listVal = asList(); if (listVal==null) return false; int arrIdx = Integer.parseInt(seg.substring(1, seg.length()-1)); if (arrIdx >= listVal.size()) return false; val = listVal.get(arrIdx); pushPath(arrIdx); } else { Map<String,Object> mapVal = asMap(); if (mapVal==null) return false; // use containsKey rather than get to handle null values if (!mapVal.containsKey(seg)) return false; val = mapVal.get(seg); pushPath(seg); } // recurse after removing head of the path return seek(seekPath.subList(1,seekPath.size())); } }