22
33import static org .basex .query .QueryError .*;
44
5+ import java .util .*;
6+
57import org .basex .query .*;
68import org .basex .query .expr .*;
79import org .basex .query .func .*;
@@ -34,79 +36,92 @@ public static class PathOptions extends Options {
3436 /** Option. */
3537 public static final BooleanOption INDEXES = new BooleanOption ("indexes" , true );
3638 }
39+ /** LRU step cache. */
40+ private final Map <Long , byte []> cachedSteps = Collections .synchronizedMap (new StepCache ());
3741
3842 @ Override
3943 public Item item (final QueryContext qc , final InputInfo ii ) throws QueryException {
4044 GNode node = toGNodeOrNull (context (qc ), qc );
41- final PathOptions options = toOptions (arg (1 ), new PathOptions (), qc );
45+ final XQMap map = toEmptyMap (arg (1 ), qc );
46+ final PathOptions options = toOptions (map , new PathOptions (), qc );
4247 if (node == null ) return Empty .VALUE ;
4348
44- final TokenBuilder tb = new TokenBuilder ();
45- final TokenList steps = new TokenList ();
4649 final boolean indexes = options .get (PathOptions .INDEXES );
4750 final Value ns = options .get (PathOptions .NAMESPACES );
4851 final XQMap namespaces = ns .isEmpty () ? XQMap .empty () : toMap (ns , qc );
4952 final boolean lexical = options .get (PathOptions .LEXICAL );
5053 final Value origin = options .get (PathOptions .ORIGIN );
54+ final boolean cache = map .structSize () == 0 ;
5155
56+ final TokenList steps = new TokenList ();
57+ final TokenBuilder tb = new TokenBuilder ();
5258 boolean relative = false ;
5359 while (true ) {
60+ // check if string representation of step was cached
61+ final Long cacheKey = cache && node instanceof final DBNode dbnode ?
62+ (long ) dbnode .data ().dbid << 32L | dbnode .pre () : null ;
63+ byte [] step = cacheKey != null ? cachedSteps .get (cacheKey ) : null ;
64+
5465 final GNode parent = node .parent ();
55- final Type type = node .type ;
56- final Kind kind = type instanceof final NodeType nt ? nt .kind () : null ;
57- if (parent == null ) {
58- if (!kind .oneOf (Kind .DOCUMENT , Kind .JNODE )) {
59- tb .add (name (Function .ROOT .definition ().name , false , lexical , namespaces , qc )).add ("()" );
66+ if (step == null ) {
67+ final Type type = node .type ;
68+ final Kind kind = type instanceof final NodeType nt ? nt .kind () : null ;
69+ if (parent == null ) {
70+ if (!kind .oneOf (Kind .DOCUMENT , Kind .JNODE )) {
71+ tb .add (name (Function .ROOT .definition ().name , false , lexical , namespaces , qc )).add ("()" );
72+ }
73+ break ;
6074 }
61- break ;
62- }
63- // step: name/type
64- final QNm qname = node .qname ();
65- if (kind == Kind .ATTRIBUTE ) {
66- tb .add ('@' ).add (name (qname , true , lexical , namespaces , qc ));
67- } else if (kind == Kind .ELEMENT ) {
68- tb .add (name (qname , false , lexical , namespaces , qc ));
69- } else if (kind == Kind .PROCESSING_INSTRUCTION ) {
70- tb .add (kind .toString (Token .string (qname .local ())));
71- } else if (kind .oneOf (Kind .COMMENT , Kind .TEXT )) {
72- tb .add (type .toString ());
73- } else if (parent instanceof final JNode jparent ) {
74- final JNode jnode = (JNode ) node ;
75- final Item key = jnode .key ;
76- byte [] get = null ;
77- final byte [] string = key .string (info );
78- if (jparent .value instanceof XQArray ) {
79- tb .add ("*[" ).add (key ).add ("]" );
80- } else if (key instanceof AStr || key instanceof Atm || key instanceof Uri ) {
81- if (XMLToken .isNCName (string )) {
82- tb .add (string );
75+ // step: name/type
76+ final QNm qname = node .qname ();
77+ if (kind == Kind .ATTRIBUTE ) {
78+ tb .add ('@' ).add (name (qname , true , lexical , namespaces , qc ));
79+ } else if (kind == Kind .ELEMENT ) {
80+ tb .add (name (qname , false , lexical , namespaces , qc ));
81+ } else if (kind == Kind .PROCESSING_INSTRUCTION ) {
82+ tb .add (kind .toString (Token .string (qname .local ())));
83+ } else if (kind .oneOf (Kind .COMMENT , Kind .TEXT )) {
84+ tb .add (type .toString ());
85+ } else if (parent instanceof final JNode jparent ) {
86+ final JNode jnode = (JNode ) node ;
87+ final Item key = jnode .key ;
88+ byte [] get = null ;
89+ final byte [] string = key .string (info );
90+ if (jparent .value instanceof XQArray ) {
91+ tb .add ("*[" ).add (key ).add ("]" );
92+ } else if (key instanceof AStr || key instanceof Atm || key instanceof Uri ) {
93+ if (XMLToken .isNCName (string )) {
94+ tb .add (string );
95+ } else {
96+ get = QueryString .toQuoted (string );
97+ }
98+ } else if (key instanceof ANum ) {
99+ get = string ;
100+ } else if (key instanceof final QNm qnm ) {
101+ tb .add (qnm .eqName ());
102+ } else if (key instanceof Bln ) {
103+ get = Token .concat (string , "()" );
83104 } else {
84- get = QueryString .toQuoted (string );
105+ get = Token . concat ( key . type . toString (), '(' , QueryString .toQuoted (string ), ')' );
85106 }
86- } else if (key instanceof ANum ) {
87- get = string ;
88- } else if (key instanceof final QNm qnm ) {
89- tb .add (qnm .eqName ());
90- } else if (key instanceof Bln ) {
91- get = Token .concat (string , "()" );
92- } else {
93- get = Token .concat (key .type .toString (), '(' , QueryString .toQuoted (string ), ')' );
107+ if (get != null ) tb .add ("get(" ).add (get ).add (')' );
94108 }
95- if (get != null ) tb .add ("get(" ).add (get ).add (')' );
96- }
97- // optional index
98- if (indexes && !kind .oneOf (Kind .ATTRIBUTE , Kind .JNODE )) {
99- int p = 1 ;
100- for (final GNode nd : node .precedingSiblingIter (false )) {
101- qc .checkStop ();
102- final QNm qnm = nd .qname ();
103- if (nd .kind () == kind && (qnm == null || nd .qname ().eq (qname ))) {
104- p ++;
109+ // optional index
110+ if (indexes && !kind .oneOf (Kind .ATTRIBUTE , Kind .JNODE )) {
111+ int index = 1 ;
112+ for (final GNode nd : node .precedingSiblingIter (false )) {
113+ qc .checkStop ();
114+ final QNm qnm = nd .qname ();
115+ if (nd .kind () == kind && (qnm == null || nd .qname ().eq (qname ))) {
116+ index ++;
117+ }
105118 }
119+ tb .add ('[' ).addInt (index ).add (']' );
106120 }
107- tb .add ('[' ).addInt (p ).add (']' );
121+ step = tb .next ();
122+ if (cacheKey != null ) cachedSteps .put (cacheKey , step );
108123 }
109- steps .add (tb . next () );
124+ steps .add (step );
110125 node = parent ;
111126
112127 // root node: finalize traversal
@@ -117,7 +132,7 @@ public Item item(final QueryContext qc, final InputInfo ii) throws QueryExceptio
117132 }
118133 if (origin instanceof GNode && !relative ) throw PATH_X .get (info , origin );
119134
120- // add all steps in reverse order; cache element paths
135+ // add all steps in reverse order
121136 for (int s = steps .size () - 1 ; s >= 0 ; --s ) {
122137 if (!(tb .isEmpty () && origin instanceof GNode )) tb .add ('/' );
123138 tb .add (steps .get (s ));
@@ -151,4 +166,21 @@ private byte[] name(final QNm qnm, final boolean attr, final boolean lexical,
151166 protected Expr opt (final CompileContext cc ) {
152167 return optFirst (true , false , cc .qc .focus .value );
153168 }
169+
170+ /**
171+ * Step cache.
172+ * @author BaseX Team, BSD License
173+ * @author Christian Gruen
174+ */
175+ private static final class StepCache extends LinkedHashMap <Long , byte []> {
176+ /** Constructor. */
177+ private StepCache () {
178+ super (16 , 0.75f , true );
179+ }
180+
181+ @ Override
182+ protected boolean removeEldestEntry (final Map .Entry <Long , byte []> eldest ) {
183+ return size () > 1000 ;
184+ }
185+ }
154186}
0 commit comments