Source code: com/aendvari/common/model/ModelQueryPath.java
1 /*
2 * ModelQueryPath.java
3 *
4 * Copyright (c) 2001, 2002 Aendvari, Ltd. All Rights Reserved.
5 *
6 * See the file LICENSE for terms of use.
7 *
8 */
9
10 package com.aendvari.common.model;
11
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.StringTokenizer;
17
18 /**
19 * <p>Provides the ability to access nodes within an Model Tree using
20 * an expression.</p>
21 *
22 * <p>This class allows the selection of single and multiple nodes.</p>
23 *
24 * @author Trevor Milne
25 *
26 */
27
28 public class ModelQueryPath
29 {
30 /* Attributes. */
31
32
33 /** Contains the compiled query expression. */
34 private List operations;
35
36
37 /* Constructors. */
38
39
40 /**
41 * Constructs an <code>ModelQueryPath</code> instance.
42 *
43 * @param expression Query expression for node selection.
44 *
45 */
46
47 public ModelQueryPath(String expression)
48 {
49 // compile the query expression
50 compile(expression);
51 }
52
53
54 /* Expressions. */
55
56
57 /**
58 * Selects a single node using the compiled expression.
59 *
60 * @param context The context node specifying the root of the expression.
61 *
62 * @return The node found using the expression, null if a node cannot be found.
63 *
64 */
65
66 public ModelNode selectNode(ModelNode context)
67 {
68 // select node using expression
69 return selectNode(context, 0);
70 }
71
72 /**
73 * Selects multiple nodes using the compiled expression.
74 *
75 * @param context The context node specifying the root of the expression.
76 *
77 * @return A <code>Collection</code> of the selected {@link ModelNode ModelNodes}. Will not be null.
78 *
79 */
80
81 public Collection selectNodes(ModelNode context)
82 {
83 ArrayList nodes = new ArrayList();
84
85 // select nodes
86 selectNodes(context, 0, nodes);
87
88 return nodes;
89 }
90
91 /**
92 * Uses the expression to select a single node.
93 *
94 * @param context The context node specifying the root of the expression.
95 * @param expression Query expression to select the node.
96 *
97 * @return The node found using the expression, null if a node cannot be found.
98 *
99 */
100
101 public static ModelNode selectNode(ModelNode context, String expression)
102 {
103 ModelQueryPath query = new ModelQueryPath(expression);
104
105 return query.selectNode(context);
106 }
107
108 /**
109 * Uses the expression to select multiple nodes.
110 *
111 * @param context The context node specifying the root of the expression.
112 * @param expression Query expression to select the nodes.
113 *
114 * @return A <code>Collection</code> of the selected {@link ModelNode ModelNodes}. Can not be null.
115 *
116 */
117
118 public static Collection selectNodes(ModelNode context, String expression)
119 {
120 ModelQueryPath query = new ModelQueryPath(expression);
121
122 return query.selectNodes(context);
123 }
124
125
126 /* Single selection */
127
128
129 /**
130 * Selects a child node based on its name.
131 *
132 * @param operator The current operation.
133 * @param context The node being searched.
134 * @param operationIndex The current operation.
135 *
136 */
137
138 protected ModelNode selectNodeByName(Operator operator, ModelNode context, int operationIndex)
139 {
140 String segment = operator.stringParameter;
141 boolean any = segment.equals("*");
142
143 // get array of children
144 Collection childNodes = context.getChildNodes();
145 ModelNode children[] = new ModelNode[childNodes.size()];
146 childNodes.toArray(children);
147
148 // search children for name
149 int childIndex;
150 for (childIndex = 0; childIndex < children.length; childIndex++)
151 {
152 ModelNode child = children[childIndex];
153
154 // pick either the first in the group, or the first name match
155 if (any || child.getNodeName().equals(segment))
156 {
157 // continue evaluating expression on the selected node
158 return selectNode(child, (operationIndex + 1));
159 }
160 }
161
162 // no match found
163 return null;
164 }
165
166 /**
167 * Selects a child node based on its position.
168 *
169 * @param operator The current operation.
170 * @param context The node being searched.
171 * @param operationIndex The current operation.
172 *
173 */
174
175 protected ModelNode selectNodeByIndex(Operator operator, ModelNode context, int operationIndex)
176 {
177 String segment = operator.stringParameter;
178
179 // find the n'th node
180 int nodeIndex = 1;
181
182 // get array of children
183 Collection childNodes = context.getChildNodes();
184 ModelNode children[] = new ModelNode[childNodes.size()];
185 childNodes.toArray(children);
186
187 int childIndex;
188 for (childIndex = 0; childIndex < children.length; childIndex++)
189 {
190 ModelNode child = children[childIndex];
191
192 // check each node matching the segment
193 if (child.getNodeName().equals(segment))
194 {
195 // continue until the n'th named node is found
196 if (nodeIndex == operator.intParameter)
197 {
198 // continue evaluating expression on the selected node
199 return selectNode(child, (operationIndex + 1));
200 }
201
202 nodeIndex++;
203 }
204 }
205
206 // no match found
207 return null;
208 }
209
210 /**
211 * Performs the query expression operations for a single node selection.
212 *
213 * @param context The node being searched.
214 * @param operationIndex The current operation.
215 *
216 */
217
218 protected ModelNode selectNode(ModelNode context, int operationIndex)
219 {
220 // if at the end of the expression, select the context node
221 if (operationIndex == operations.size())
222 {
223 return context;
224 }
225
226 // get the next operator
227 Operator operator = (Operator)operations.get(operationIndex);
228
229 // select by name
230 if (operator.type == Operator.Type.SelectNode)
231 {
232 return selectNodeByName(operator, context, operationIndex);
233 }
234 else
235 // select by index
236 if (operator.type == Operator.Type.SelectIndex)
237 {
238 return selectNodeByIndex(operator, context, operationIndex);
239 }
240
241 // bad operation
242 return null;
243 }
244
245
246 /* Multiple selection */
247
248
249 /**
250 * Selects child nodes based on their name.
251 *
252 * @param operator The current operation.
253 * @param context The node being searched.
254 * @param operationIndex The current operation.
255 * @param nodes The collection to store selected nodes.
256 *
257 */
258
259 protected void selectNodesByName(Operator operator, ModelNode context, int operationIndex, Collection nodes)
260 {
261 String segment = operator.stringParameter;
262 boolean any = segment.equals("*");
263
264 // get array of children
265 Collection childNodes = context.getChildNodes();
266 ModelNode children[] = new ModelNode[childNodes.size()];
267 childNodes.toArray(children);
268
269 // search children for name
270 int childIndex;
271 for (childIndex = 0; childIndex < children.length; childIndex++)
272 {
273 ModelNode child = children[childIndex];
274
275 // traverse down each node matching the segment
276 if (any || child.getNodeName().equals(segment))
277 {
278 // continue evaluating expression on the selected node
279 selectNodes(child, (operationIndex + 1), nodes);
280 }
281 }
282 }
283
284 /**
285 * Selects a child node based on its position.
286 *
287 * @param operator The current operation.
288 * @param context The node being searched.
289 * @param operationIndex The current operation.
290 * @param nodes The collection to store selected nodes.
291 *
292 */
293
294 protected void selectNodesByIndex(Operator operator, ModelNode context, int operationIndex, Collection nodes)
295 {
296 String segment = operator.stringParameter;
297
298 // find the n'th node
299 int nodeIndex = 1;
300
301 // get array of children
302 Collection childNodes = context.getChildNodes();
303 ModelNode children[] = new ModelNode[childNodes.size()];
304 childNodes.toArray(children);
305
306 // search children for name
307 int childIndex;
308 for (childIndex = 0; childIndex < children.length; childIndex++)
309 {
310 ModelNode child = children[childIndex];
311
312 // check each node matching the segment
313 if (child.getNodeName().equals(segment))
314 {
315 // continue until the n'th named node is found
316 if (nodeIndex == operator.intParameter)
317 {
318 // continue evaluating expression on the selected node
319 selectNodes(child, (operationIndex + 1), nodes);
320
321 break;
322 }
323
324 nodeIndex++;
325 }
326 }
327 }
328
329 /**
330 * Performs the query expression operations for multiple node selection.
331 *
332 * @param context The node being searched.
333 * @param operationIndex The current operation.
334 * @param nodes The collection to store selected nodes.
335 *
336 */
337
338 protected void selectNodes(ModelNode context, int operationIndex, Collection nodes)
339 {
340 // if at the end of the expression, select the context node
341 if (operationIndex == operations.size())
342 {
343 nodes.add(context);
344 return;
345 }
346
347 // get the next operator
348 Operator operator = (Operator)operations.get(operationIndex);
349
350 // select by name
351 if (operator.type == Operator.Type.SelectNode)
352 {
353 selectNodesByName(operator, context, operationIndex, nodes);
354 }
355 else
356 // select by index
357 if (operator.type == Operator.Type.SelectIndex)
358 {
359 selectNodesByIndex(operator, context, operationIndex, nodes);
360 }
361 }
362
363
364 /* Expression compiling. */
365
366
367 /**
368 * Represents a single operation to perform during expression evaluation.
369 *
370 */
371
372 protected static class Operator
373 {
374 /** The operation to perform. */
375 public int type;
376
377 /** A string parameter for the operation. */
378 public String stringParameter;
379
380 /** An integer parameter for the operation. */
381 public int intParameter;
382
383 /* Constants. */
384
385
386 /** Defines the constants for the operation <code>type</code>. */
387 public interface Type
388 {
389 /** Select a node by its name. */
390 public static final int SelectNode = 1;
391
392 /** Select a node by its position. */
393 public static final int SelectIndex = 2;
394 }
395
396
397 /* Constructors. */
398
399
400 /**
401 * Creates an <code>Operator</code> instance.
402 *
403 */
404
405 public Operator(int setType, String setStringParameter, int setIntParameter)
406 {
407 type = setType;
408 stringParameter = setStringParameter;
409 intParameter = setIntParameter;
410 }
411 }
412
413 /**
414 * Parses a query expressions.
415 *
416 */
417
418 protected static class Parser
419 {
420 /** The current position in the string. */
421 protected int position;
422
423 /** The string to parse. */
424 protected String expression;
425
426
427 /* Constants. */
428
429
430 /** Constants for operator tokens. */
431 public interface Operators
432 {
433 /** Null operation. */
434 final static char Null = 'n';
435
436 /** Node select operation. */
437 final static char SelectNodeDot = '.';
438
439 /** Wilcard select operation. */
440 final static char SelectNodeWild = '*';
441
442 /** Node select operation. */
443 final static char SelectNodeSlash = '/';
444
445 /** Node index operation, start. */
446 final static char SelectIndexStart = '[';
447
448 /** Node index operation, end. */
449 final static char SelectIndexEnd = ']';
450 }
451
452
453 /* Construction */
454
455
456 /**
457 * Creates a <code>Tokenizer</code> to parse the supplied string.
458 *
459 * @param setExpression The expression to parse.
460 *
461 */
462
463 Parser(String setExpression)
464 {
465 expression = setExpression;
466 position = 0;
467 }
468
469
470 /* Parsing */
471
472 /**
473 * Returns true if more tokens are available.
474 *
475 * @return True if more tokens, false otherwise.
476 *
477 */
478
479 boolean hasMoreTokens()
480 {
481 // check for end of value
482 return (position < expression.length());
483 }
484
485 /**
486 * Returns the next available string.
487 *
488 * @return The next available string token, null if no tokens remain.
489 *
490 */
491
492 String getStringToken()
493 {
494 // check for end of value
495 if (position >= expression.length()) return null;
496
497 // search for end of next token
498 int nextPosition = position;
499
500 while (nextPosition < expression.length())
501 {
502 char letter = expression.charAt(nextPosition);
503
504 // check for operator
505 if ((letter == Operators.SelectNodeDot) ||
506 (letter == Operators.SelectNodeSlash) ||
507 (letter == Operators.SelectIndexStart) ||
508 (letter == Operators.SelectIndexEnd))
509 {
510 break;
511 }
512
513 nextPosition++;
514 }
515
516 // extract token
517 String token = expression.substring(position, nextPosition);
518
519 // start next token search at this delimiter
520 position = nextPosition;
521
522 return token;
523 }
524
525 /**
526 * Returns the next available operator.
527 *
528 * @return The next available operator token, null if no tokens remain.
529 *
530 */
531
532 char getOperatorToken()
533 {
534 // check for end of value
535 if (position >= expression.length()) return Operators.Null;
536
537 // retrieve operator
538 char token = expression.charAt(position);
539 position++;
540
541 return token;
542 }
543 }
544
545 /**
546 * Parses an index selection operator.
547 *
548 * @param parser The {@link Parser} being used for the expression.
549 * @param segment The segment that the index belongs to.
550 *
551 */
552
553 protected void parseIndexOperator(Parser parser, String segment)
554 {
555 // set to index operator
556 int type = Operator.Type.SelectIndex;
557
558 // get index, [n]
559 int index = Integer.parseInt(parser.getStringToken());
560
561 // get closing ']'
562 char op = parser.getOperatorToken();
563
564 // get next operator, if any
565 parser.getOperatorToken();
566
567 // create operator
568 operations.add(new Operator(type, segment, index));
569 }
570
571 /**
572 * Compiles the supplied query expression.
573 *
574 * @param expression Query expression to select the nodes.
575 *
576 */
577
578 protected void compile(String expression)
579 {
580 // create storage for compiled expression
581 operations = new ArrayList();
582
583 // initialize parser
584 Parser parser = new Parser(expression);
585
586 // check for initial operator
587 char start = expression.charAt(0);
588
589 if ((start == '.') || (start == '/'))
590 {
591 parser.getOperatorToken();
592 }
593
594 // parse expression
595 while (parser.hasMoreTokens())
596 {
597 // get node name
598 String segment = parser.getStringToken();
599
600 int type = Operator.Type.SelectNode;
601 int index = 0;
602
603 // read next operator
604 if (parser.hasMoreTokens())
605 {
606 char operator = parser.getOperatorToken();
607
608 // check if index operator follows
609 if (operator == Parser.Operators.SelectIndexStart)
610 {
611 parseIndexOperator(parser, segment);
612 }
613 else
614 {
615 // create "node" operator for previous operator
616 operations.add(new Operator(type, segment, index));
617 }
618 }
619 else
620 {
621 // create "node" operator for last operator
622 operations.add(new Operator(type, segment, index));
623 }
624 }
625 }
626 }
627