JavaPath脚本语言,用于访问复杂的数据结构

本文讨论了声明性脚本语言JavaPath作为使用Java Reflection的替代方法,以及在使用复杂数据结构的独立应用程序中避免“服务地狱”的方法。


问题描述


考虑一个深层嵌套的结构


class A{
   B b;
}
class B{
   C c;
}
class C{
   String name;
}

如果我们需要在不直接访问实例A的情况下为类C的名称字段分配值,则通常可以使用服务API的中间层。


private A a;
public void setNameService(String name) {
    a.b.c.name = name;
}

, , . , .


private A a;
public void setNameService(String name) {
    if(a == null) {
        a = new A();
    }
    if(a.b == null) {
        a.b = new B();
    }
    if(a.b.c == null) {
        a.b.c = new C();
    }
    a.b.c.name = name;
}

, , setNameService(String name) , , a, b c, , .


API, , , API. . , . , , 'C' name, 'B' 'A' .


, - . , , , . - - . , . , , , , .


JavaPath , .


JavaPath


JavaPath.


private A a = new A();
private final JavaPath javaPath = new JavaPath(A.class);

public void setNameService(String name) {
    javaPath.evalPath("b.c.name", a, name);
}

setNameService.


, JavaPath — , Java. JavaPath . , . JavaPath . -public. . JavaPath private final .


: — JavaPath!


final String str = "VALUE";
JavaPath javaPath = new JavaPath(String.class);
assertEquals("VALUE",str);
//  private final byte[] value;  String 
javaPath.evalPath("value",str,"THE HACK".getBytes());
assertEquals("THE HACK",str);

- , , JavaPath Java Reflection .


, JavaPath . . .


JavaPath


public Object evalPath(String path, Object root, Object... values);

evalPath


  • — Java
  • A — b
  • , . — — name

, . C. .


class C{
    String name;
    public void setName(String name) {
        this.name = name;
    }   
}

. .


A a = new A();
JavaPath javaPath = new JavaPath(A.class);
public void setNameService(String name) {
    javaPath.evalPath("b.c.setName", a, name);
}

JavaPath , , , :


    javaPath.evalPath("b.c.setName($)", a, name);

$ $0 — .



a0.a1.… .an



a0.a1.… .an-1


, , / null. an . , , , evalPath. , . JavaPath .


JavaPath


JavaPath , . . . , , , . , , ,
Java . , , , API.



语法图



JavaPath . . , , , . . , , , .. — .


$ #


特殊参数



, evalPath $[:digit:]* . $ , . .


.


JavaPath
"a($)",
"a($0)"
"a($1.name)"name,


. : #[:digit:]* # #0


.


JavaPath
"a(#)"
"a(#0.name)"name
"a.b(#1)"'a'

, -.


public class A {
    A parent;
    A child;
    String name;
    public A(A parent) {
        this.parent = parent;
    }
}

A a = new A(null);
JavaPath javaPath = new JavaPath(A.class);
javaPath.evalPath("name",a,"PARENT");
javaPath.evalPath("child(#0).name",a,"CHILD");
javaPath.evalPath("child(#0).child(#1).name",a,"GRAND-CHILD");
assertEquals("PARENT",a.name);
assertEquals("CHILD",a.child.name);
assertEquals("GRAND-CHILD",a.child.child.name);
assertEquals("CHILD",a.child.child.parent.name);
assertEquals("PARENT",a.child.child.parent.parent.name);

— .


"a.set('$0')" //  $0

InLine :


, JavaPath. , , . .


JavaPath
"a.b.c". , evalPath , .
"a.b.c($)", .
"a.b.c($0)"$0 $ .
"a.b.c('THE VALUE')"c 'THE VALUE' evalPath . .
"a().b().c",
"a().b.c".
"a.b(int 1024).c"
"a.b(Int 1024).c"Integer
"a.b(int 1024,' ').c",
a.setX(PhoneType CELL)enum PhoneType{HOME,CELL,WORK}, "CELL"

, , valueOf ( , enum ) .


, valueOf - , StringConverter, .



- null , . .


, , , . — . Java, .


"(T a).b"

: map .


public class A {
    Map<String,String> map;
}

A a = new A();
JavaPath javaPath = new JavaPath(A.class);
//       map
javaPath.evalPath("(HashMap map).put(firstName,$)", a, "John");
//    ,     .
javaPath.evalPath("map.put(lastName,$)", a, "Silver");

.


//  HashMap      .
//        0.8 
javaPath.evalPath("(HashMap map(int 100,float '0.8')).put(firstName,$)", a, "John");

, null. map .



enum PhoneType{HOME,CELL,WORK}
public static class A {
    String firstName;
    String lastName;
    Map<PhoneType, Set<String>> phones;
    Map<String, PhoneType> reversedPhones;
}

A a = new A();
ClassRegistry  classRegistry = new ClassRegistry();
classRegistry.registerClass(PhoneType.class,PhoneType.class.getSimpleName());
JavaPath javaPath = new JavaPath(A.class,classRegistry);

javaPath.evalPath("(map phones).put(PhoneType WORK)", a, new HashSet<>());
javaPath.evalPath("phones.computeIfAbsent(PhoneType HOME,key->new HashSet).@", a);
javaPath.evalPath("phones.computeIfAbsent(PhoneType CELL,key->new HashSet).@", a);
javaPath.evalPath("(map reversedPhones).@", a);

javaPath.evalPath("firstName", a, "John");
javaPath.evalPath("lastName", a, "Smith");

javaPath.evalPath("phones.get(PhoneType CELL).add", a, "1-101-111-2233");
javaPath.evalPath("phones.get(PhoneType HOME).add", a, "1-101-111-7865");
javaPath.evalPath("phones.get(PhoneType WORK).add", a, "1-105-333-1100");
javaPath.evalPath("phones.get(PhoneType WORK).add($)", a, "1-105-333-1104");

javaPath.evalPath("reversedPhones.put($,PhoneType CELL)", a, "1-101-111-2233");
javaPath.evalPath("reversedPhones.put($,PhoneType HOME)", a, "1-101-111-7865");
javaPath.evalPath("reversedPhones.put($,PhoneType WORK)", a, "1-105-333-1100");
javaPath.evalPath("reversedPhones.put($,PhoneType WORK)", a, "1-105-333-1104");

@


@ . , . .


||


null, JavaPath . .


JavaPath
"getA∣∣setA($1).name($0)"getA null setA, getA
"getA∣∣init.name($0)"

::


, -, ::


JavaPath
"(UserInfo::newInstance userInfo).phone.ext"UserInfo UserInfo.newInstance()
"(UserInfo::newInstance userInfo(John,Smith)).phone.ext"UserInfo UserInfo.newInstance(String a, String b)
"a.b(Integer::valueOf 100).c". .
"(HashMap::new map).get"- new . .

ClassRegistry


, , .. ClassRegistry , JavaPath.


ClassRegistry . ClassRegistry, JavaPath.


PhoneType


ClassRegistry  classRegistry = new ClassRegistry();
classRegistry.registerClass(PhoneType.class,PhoneType.class.getSimpleName(),"Phone");
JavaPath javaPath = new JavaPath(A.class,classRegistry);

PhoneType , , Phone.


ClassRegistry . , :: .


. ClassRegistry . , StringConverter — .


public class A {
...
static {
    ClassRegistry.registerGlobalStringConverter(A.class,A::stringToA); 
}
public static A stringToA(String str) {
       A a = new A("{"+str+"}"); // - 
       return a;
    }
}

public class B {
    A a;
}

JavaPath javaPath = new JavaPath(B.class); //  A::stringToA

@PathElement @NoPathElement


@PathElement .


public class A {
    String name; //       "name"
    @PathElement({"name","first_name","firstName"})
    public void setName(String name) {
        this.name = name;
    }
}

, setName "name", . .


@NoPathElement JavaPath.


public class A {
    StringBuilder stringBuilder = new StringBuilder();

    @NoPathElement
    private final String protectedField = "IMMUTABLE BY JAVA PATH!";

    public void add(String str) {
        stringBuilder.append(str == null ? "N/A" : str);
    }

    @NoPathElement
    public void add(Object val) {
        stringBuilder.append(val);
    }
}

.





JavaPath . .


public class A {
    String firstName;
    String lastName;
    int age;
}

A a = new A();
JavaPath javaPath = new JavaPath(A.class);
javaPath.evalPath("firstName; lastName; age", a, "John","Smith",55);
// $       ,    
javaPath.evalPath("firstName($); lastName($); age($)", a, "John","Smith",55);
//         .
javaPath.evalPath("firstName($0); lastName($1); age($2)", a, "John","Smith",55);

'$', , . , firstName($) "John" ( , ), age($) — 55 ( , ). .


, , .


. .




JavaPath


, . , ? initPath
.


JavaPath . , . , , root. # #0


initPath .


:


//      JavaPath.  
Object instanceOfA = javaPath.initPath("(com.my.project.A #0).b", "test");
//      initPath
A instanceOfA = javaPath.initPath(A.class, "#.b", "test");
//       , #  #0
A instanceOfA = javaPath.initPath(A.class, "root.b", "test");




JavaPath类的setEnablePathCaching(boolean enableCaching)方法允许您将解析器的结果保存在缓存中。不要与字段层次结构和类方法的不可停用的缓存混淆。默认情况下,路径缓存处于禁用状态,因为如果动态计算路径,则可能导致不受控制的内存消耗。


示例-缓存中将存在三个不同的路径:


evalPath("user.name('John'));"
evalPath("user.name('Peter'));"
evalPath("user.name('Mike'));"

而是使用显式传递的变量值。最下面的示例将保存一条路径。


evalPath("user.name($0)","John");
evalPath("user.name($0)","Peter");
evalPath("user.name($0)","Mike");

依存关系


Java8及更早版本


Maven仓库


<dependency>
    <groupId>com.aegisql</groupId>
    <artifactId>java-path</artifactId>
    <version>0.2.0</version>
</dependency>

All Articles