L'article décrit le langage de script déclaratif JavaPath comme alternative à l'utilisation de Java Reflection et un moyen d'éviter «l'enfer des services» dans les applications autonomes utilisant des structures de données complexes.
description du problĂšme
Considérons une structure profondément imbriquée
class A{
B b;
}
class B{
C c;
}
class C{
String name;
}
Si nous devons attribuer une valeur au champ de nom de la classe C sans avoir un accÚs direct à l'instance A, alors une couche intermédiaire de l'API de service vient généralement à la rescousse.
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:]* . $ , . .
.
. : #[:digit:]* # #0
.
, -.
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')"
InLine :
, JavaPath. , , . .
, , 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");
.
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 . .
::
, -, ::
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;
@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 .
:
Object instanceOfA = javaPath.initPath("(com.my.project.A #0).b", "test");
A instanceOfA = javaPath.initPath(A.class, "#.b", "test");
// , # #0
A instanceOfA = javaPath.initPath(A.class, "root.b", "test");
La méthode setEnablePathCaching (boolean enableCaching) de la classe JavaPath vous permet d'enregistrer le résultat de l'analyseur dans le cache. à ne pas confondre avec la mise en cache non désactivable des hiérarchies de champs et des méthodes de classe. Le cache des chemins est désactivé par défaut car il peut entraßner une consommation de mémoire incontrÎlée si les chemins sont calculés dynamiquement.
Exemple - il y aura trois chemins différents dans le cache:
evalPath("user.name('John'));"
evalPath("user.name('Peter'));"
evalPath("user.name('Mike'));"
Utilisez plutĂŽt des valeurs de variable qui passent explicitement. L'exemple du bas enregistre un chemin.
evalPath("user.name($0)","John");
evalPath("user.name($0)","Peter");
evalPath("user.name($0)","Mike");
Dépendances
Java8 et versions antérieures
DépÎt Maven
<dependency>
<groupId>com.aegisql</groupId>
<artifactId>java-path</artifactId>
<version>0.2.0</version>
</dependency>