handle array in query path correctly

This commit is contained in:
金戟 2021-03-20 00:50:30 +08:00
parent 606760d632
commit f54a6ce022
3 changed files with 90 additions and 63 deletions

View File

@ -38,12 +38,12 @@ class DemoOmniMethodsTest {
Parent demo = OmniConstructor.newInstance(Parent.class); Parent demo = OmniConstructor.newInstance(Parent.class);
demo.getChild().setEnumChild(EnumChild.VAL2); demo.getChild().setEnumChild(EnumChild.VAL2);
assertEquals(EnumChild.VAL2, OmniAccessor.get(demo, "ec")); assertEquals(EnumChild.VAL2, OmniAccessor.getFirst(demo, "ec"));
assertEquals(EnumChild.VAL2, OmniAccessor.get(demo, "{EnumChild}")); assertEquals(EnumChild.VAL2, OmniAccessor.getFirst(demo, "{EnumChild}"));
assertEquals(EnumChild.VAL2, OmniAccessor.get(demo, "c/ec")); assertEquals(EnumChild.VAL2, OmniAccessor.getFirst(demo, "c/ec"));
demo.setChildren(new Child[]{ new Child() }); demo.setChildren(new Child[]{ OmniConstructor.newInstance(Child.class) });
assertEquals(EnumChild.VAL1, OmniAccessor.get(demo, "cs[0]/ec")); assertEquals(EnumChild.VAL1, OmniAccessor.getFirst(demo, "cs[0]/ec"));
} }
@Test @Test

View File

@ -38,8 +38,8 @@ public class OmniAccessor {
* @return 返回目标成员若不存在则返回null * @return 返回目标成员若不存在则返回null
*/ */
public static <T> T getFirst(Object target, String queryPath) { public static <T> T getFirst(Object target, String queryPath) {
T[] values = get(target, queryPath); List<T> values = get(target, queryPath);
return values.length == 0 ? null : values[0]; return values.isEmpty() ? null : values.get(0);
} }
/** /**
@ -49,14 +49,14 @@ public class OmniAccessor {
* @param queryPath 搜索路径 * @param queryPath 搜索路径
* @return 返回所有匹配的成员 * @return 返回所有匹配的成员
*/ */
public static <T> T[] get(Object target, String queryPath) { public static <T> List<T> get(Object target, String queryPath) {
List<T> values = new ArrayList<T>(); List<T> values = new ArrayList<T>();
for (String memberPath : MEMBER_INDEXES.getOrElse(target.getClass(), generateMemberIndex(target.getClass()))) { for (String memberPath : MEMBER_INDEXES.getOrElse(target.getClass(), generateMemberIndex(target.getClass()))) {
if (memberPath.matches(toPattern(queryPath))) { if (memberPath.matches(toPattern(queryPath))) {
try { try {
T val = (T)getByPath(target, memberPath, queryPath); List<T> elements = getByPath(target, memberPath, queryPath);
if (val != null) { if (!elements.isEmpty()) {
values.add(val); values.addAll(elements);
} }
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
// continue // continue
@ -65,7 +65,7 @@ public class OmniAccessor {
} }
} }
} }
return (T[])values.toArray(); return values;
} }
/** /**
@ -81,9 +81,11 @@ public class OmniAccessor {
for (String memberPath : MEMBER_INDEXES.getOrElse(target.getClass(), generateMemberIndex(target.getClass()))) { for (String memberPath : MEMBER_INDEXES.getOrElse(target.getClass(), generateMemberIndex(target.getClass()))) {
if (memberPath.matches(toPattern(queryPath))) { if (memberPath.matches(toPattern(queryPath))) {
try { try {
Object parent = getByPath(target, toParent(memberPath), toParent(queryPath)); List<Object> parent = getByPath(target, toParent(memberPath), toParent(queryPath));
if (parent != null) { if (parent.isEmpty()) {
setByPathSegment(parent, toChild(memberPath), toChild(queryPath), value); for (Object p : parent) {
setByPathSegment(p, toChild(memberPath), toChild(queryPath), value);
}
count++; count++;
} }
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
@ -104,9 +106,8 @@ public class OmniAccessor {
if (clazz.isEnum()) { if (clazz.isEnum()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<Field> fields = TypeUtil.getAllFields(clazz);
List<String> paths = new ArrayList<String>(); List<String> paths = new ArrayList<String>();
for (Field f : fields) { for (Field f : TypeUtil.getAllFields(clazz)) {
if (!f.getName().startsWith(THIS_REF_PREFIX)) { if (!f.getName().startsWith(THIS_REF_PREFIX)) {
String fullPath = basePath + SLASH + toPath(f); String fullPath = basePath + SLASH + toPath(f);
paths.add(fullPath); paths.add(fullPath);
@ -126,7 +127,7 @@ public class OmniAccessor {
for (int i = 0; i < querySegments.length; i++) { for (int i = 0; i < querySegments.length; i++) {
patternSegments[i] = toSinglePattern(querySegments[i]); patternSegments[i] = toSinglePattern(querySegments[i]);
} }
return StringUtils.join(Arrays.asList(patternSegments), SLASH); return ".*/" + StringUtils.join(Arrays.asList(patternSegments), SLASH);
} }
private static String toSinglePattern(String querySegment) { private static String toSinglePattern(String querySegment) {
@ -153,53 +154,78 @@ public class OmniAccessor {
return memberPath.contains(SLASH) ? memberPath.substring(0, memberPath.lastIndexOf(SLASH)) : ""; return memberPath.contains(SLASH) ? memberPath.substring(0, memberPath.lastIndexOf(SLASH)) : "";
} }
private static String extraNameFromMemberRecord(String memberSegment) {
return memberSegment.substring(0, memberSegment.indexOf(BRACE_START));
}
private static int extraIndexFromQuery(String query) { private static int extraIndexFromQuery(String query) {
return query.endsWith(BRACKET_END) return query.endsWith(BRACKET_END)
? Integer.parseInt(query.substring(query.lastIndexOf(BRACKET_START) + 1, query.length() - 1)) ? Integer.parseInt(query.substring(query.lastIndexOf(BRACKET_START) + 1, query.length() - 1))
: -1; : -1;
} }
private static Object getByPath(Object target, String memberPath, String queryPath) private static <T> List<T> getByPath(Object target, String memberPath, String queryPath)
throws NoSuchFieldException, IllegalAccessException { throws NoSuchFieldException, IllegalAccessException {
String[] memberSegments = memberPath.split(SLASH); String[] memberSegments = memberPath.substring(1).split(SLASH);
String[] querySegments = calculateFullQueryPath(queryPath.split(SLASH), memberSegments); String[] querySegments = queryPath.split(SLASH);
Object obj = target; if (memberSegments.length < querySegments.length) {
String name; return Collections.emptyList();
int nth; }
Field field; String[] querySegmentsWithPadding = calculateFullQueryPath(querySegments, memberSegments);
for (int i = 0; i < memberSegments.length; i++) { return getBySegment(target, memberSegments, querySegmentsWithPadding, 0);
name = memberSegments[i].substring(0, memberSegments[i].indexOf(BRACE_START)); }
nth = extraIndexFromQuery(querySegments[i]);
field = TypeUtil.getFieldByName(obj.getClass(), name); private static <T> List<T> getBySegment(Object target, String[] memberSegments, String[] querySegments, int n)
field.setAccessible(true); throws IllegalAccessException {
if (field.getType().isArray() && nth >= 0) { if (target == null) {
Object f = field.get(obj); return Collections.emptyList();
obj = Array.get(f, nth); }
} else { if (memberSegments.length == n) {
obj = field.get(obj); int nth = extraIndexFromQuery(querySegments[n]);
} return Collections.singletonList((T)(nth > 0 ? Array.get(target, nth) : target));
}
String name = extraNameFromMemberRecord(memberSegments[n]);
int nth = extraIndexFromQuery(querySegments[n]);
List<T> nexts = new ArrayList<T>();
if (target.getClass().isArray()) {
for (int i = 0; i < Array.getLength(target); i++) {
List<T> all = getBySegment(getField(Array.get(target, i), name, nth), memberSegments, querySegments, n + 1);
nexts.addAll(all);
}
} else {
List <T> all = getBySegment(getField(target, name, nth), memberSegments, querySegments, n + 1);
nexts.addAll(all);
}
return nexts;
}
private static Object getField(Object obj, String name, int nth) throws IllegalAccessException {
if (obj == null) {
return null;
}
Field field = TypeUtil.getFieldByName(obj.getClass(), name);
field.setAccessible(true);
if (field.getType().isArray() && nth >= 0) {
Object f = field.get(obj);
return Array.get(f, nth);
} else {
return field.get(obj);
} }
return obj;
} }
private static String[] calculateFullQueryPath(String[] querySegments, String[] memberSegments) { private static String[] calculateFullQueryPath(String[] querySegments, String[] memberSegments) {
assert memberSegments.length >= querySegments.length; String[] fullQuerySegments = new String[memberSegments.length + 1];
; for (int i = 0; i <= memberSegments.length - querySegments.length; i++) {
if (memberSegments.length > querySegments.length) { fullQuerySegments[i] = "";
String[] fullQuerySegments = new String[memberSegments.length];
for (int i = 0; i < querySegments.length; i++) {
fullQuerySegments[i] = "";
}
System.arraycopy(querySegments, 0, fullQuerySegments, querySegments.length,
memberSegments.length - querySegments.length);
return fullQuerySegments;
} }
return querySegments; System.arraycopy(querySegments, 0, fullQuerySegments, memberSegments.length - querySegments.length + 1,
querySegments.length);
return fullQuerySegments;
} }
private static void setByPathSegment(Object target, String memberSegment, String querySegment, Object value) private static void setByPathSegment(Object target, String memberSegment, String querySegment, Object value)
throws IllegalAccessException { throws IllegalAccessException {
String name = memberSegment.substring(0, memberSegment.indexOf(BRACE_START)); String name = extraNameFromMemberRecord(memberSegment);
int nth = extraIndexFromQuery(querySegment); int nth = extraIndexFromQuery(querySegment);
Field field = TypeUtil.getFieldByName(target.getClass(), name); Field field = TypeUtil.getFieldByName(target.getClass(), name);
field.setAccessible(true); field.setAccessible(true);

View File

@ -76,29 +76,30 @@ class OmniAccessorTest {
String[] querySegments = new String[] { "c", "d" }; String[] querySegments = new String[] { "c", "d" };
String[] memberSegments = new String[] { "a{A}", "b{B}", "c{C}", "d{D}" }; String[] memberSegments = new String[] { "a{A}", "b{B}", "c{C}", "d{D}" };
String[] fullQuerySegments = PrivateAccessor.invokeStatic(OmniAccessor.class, "calculateFullQueryPath", querySegments, memberSegments); String[] fullQuerySegments = PrivateAccessor.invokeStatic(OmniAccessor.class, "calculateFullQueryPath", querySegments, memberSegments);
assertEquals(4, fullQuerySegments.length); assertEquals(5, fullQuerySegments.length);
assertEquals("", fullQuerySegments[0]); assertEquals("", fullQuerySegments[0]);
assertEquals("", fullQuerySegments[1]); assertEquals("", fullQuerySegments[1]);
assertEquals("c", fullQuerySegments[2]); assertEquals("", fullQuerySegments[2]);
assertEquals("d", fullQuerySegments[3]); assertEquals("c", fullQuerySegments[3]);
assertEquals("d", fullQuerySegments[4]);
} }
@Test @Test
void should_get_by_path() { void should_get_by_path() {
DemoParent parent = prepareParentObject(); DemoParent parent = prepareParentObject();
Object obj = PrivateAccessor.<String>invokeStatic(OmniAccessor.class, "getByPath", parent, "/c{DemoChild}/gc{DemoGrandChild}", "c/gc"); List<Object> obj = PrivateAccessor.invokeStatic(OmniAccessor.class, "getByPath", parent, "/c{DemoChild}/gc{DemoGrandChild}", "c/gc");
assertTrue(obj instanceof DemoGrandChild); assertTrue(obj.get(0) instanceof DemoGrandChild);
assertEquals(0, ((DemoGrandChild)obj).get()); assertEquals(0, ((DemoGrandChild)obj.get(0)).get());
PrivateAccessor.set(parent.c, "gcs", new DemoGrandChild[] { new DemoGrandChild(4), new DemoGrandChild(6) }); PrivateAccessor.set(parent.c, "gcs", new DemoGrandChild[] { new DemoGrandChild(4), new DemoGrandChild(6) });
obj = PrivateAccessor.<String>invokeStatic(OmniAccessor.class, "getByPath", parent, "/c{DemoChild}/gcs{DemoGrandChild[]}", "c/gcs"); obj = PrivateAccessor.invokeStatic(OmniAccessor.class, "getByPath", parent, "/c{DemoChild}/gcs{DemoGrandChild[]}", "c/gcs");
assertTrue(obj instanceof DemoGrandChild[]); assertTrue(obj.get(0) instanceof DemoGrandChild[]);
assertEquals(2, ((DemoGrandChild[])obj).length); assertEquals(2, ((DemoGrandChild[])obj.get(0)).length);
obj = PrivateAccessor.<String>invokeStatic(OmniAccessor.class, "getByPath", parent, "/c{DemoChild}/gcs{DemoGrandChild[]}", "c/gcs[1]"); obj = PrivateAccessor.invokeStatic(OmniAccessor.class, "getByPath", parent, "/c{DemoChild}/gcs{DemoGrandChild[]}", "c/gcs[1]");
assertTrue(obj instanceof DemoGrandChild); assertTrue(obj.get(0) instanceof DemoGrandChild);
assertEquals(6, ((DemoGrandChild)obj).get()); assertEquals(6, ((DemoGrandChild)obj.get(0)).get());
parent.cs = new DemoChild[] { null, prepareChildObject() }; parent.cs = new DemoChild[] { null, prepareChildObject() };
obj = PrivateAccessor.<String>invokeStatic(OmniAccessor.class, "getByPath", parent, "/cs{DemoChild[]}/gcs{DemoGrandChild[]}/i{int}", "c[1]/gcs[1]/i"); obj = PrivateAccessor.invokeStatic(OmniAccessor.class, "getByPath", parent, "/cs{DemoChild[]}/gcs{DemoGrandChild[]}/i{int}", "c[1]/gcs[1]/i");
assertEquals(3, obj); assertEquals(3, obj.get(0));
} }
@Test @Test