1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.sequencer.ddl.node;
25
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Map;
33 import net.jcip.annotations.NotThreadSafe;
34 import org.modeshape.graph.ExecutionContext;
35 import org.modeshape.graph.property.Name;
36 import org.modeshape.graph.property.Path;
37 import org.modeshape.graph.property.PathFactory;
38 import org.modeshape.graph.property.Property;
39 import org.modeshape.graph.property.ValueFactory;
40 import org.modeshape.graph.property.basic.BasicMultiValueProperty;
41 import org.modeshape.graph.property.basic.BasicSingleValueProperty;
42
43 /**
44 * Utility object class designed to facilitate constructing an AST or Abstract Syntax Tree representing nodes and properties that
45 * are compatible with ModeShape graph component structure.
46 */
47 @NotThreadSafe
48 public class AstNode implements Iterable<AstNode> {
49
50 private AstNode parent;
51 private final Name name;
52 private final Map<Name, Property> properties = new HashMap<Name, Property>();
53 private final LinkedList<AstNode> children = new LinkedList<AstNode>();
54 private final List<AstNode> childrenView = Collections.unmodifiableList(children);
55
56 /**
57 * Construct a node with the supplied name but without a parent.
58 *
59 * @param name the name of the node; may not be null
60 */
61 public AstNode( Name name ) {
62 assert name != null;
63 this.name = name;
64 }
65
66 /**
67 * Construct a node with the supplied name and parent.
68 *
69 * @param parent the parent node; may be null if the new node is to be a root
70 * @param name the name of the node; may not be null
71 */
72 public AstNode( AstNode parent,
73 Name name ) {
74 assert name != null;
75 this.name = name;
76 if (parent != null) {
77 this.parent = parent;
78 this.parent.children.add(this);
79 }
80 }
81
82 /**
83 * Get the name of the node.
84 *
85 * @return the node's name; never null
86 */
87 public Name getName() {
88 return name;
89 }
90
91 /**
92 * Get the current same-name-sibling index.
93 *
94 * @return the SNS index, or 1 if this is the first sibling with the same name
95 */
96 public int getSameNameSiblingIndex() {
97 int snsIndex = Path.DEFAULT_INDEX;
98 if (this.parent == null) return snsIndex;
99 // Go through all the children ...
100 for (AstNode sibling : this.parent.getChildren()) {
101 if (sibling == this) break;
102 if (sibling.getName().equals(this.name)) ++snsIndex;
103 }
104 return snsIndex;
105 }
106
107 /**
108 * Get the current path of this node, using the supplied context.
109 *
110 * @param context the context; may not be null
111 * @return the path of this node; never null
112 */
113 public Path getPath( ExecutionContext context ) {
114 assert context != null;
115 PathFactory pathFactory = context.getValueFactories().getPathFactory();
116 Path parentPath = this.parent != null ? this.parent.getPath(context) : pathFactory.createRelativePath(); // StandardDdlLexicon
117 // .
118 // STATEMENTS_CONTAINER
119 return pathFactory.create(parentPath, name, getSameNameSiblingIndex());
120 }
121
122 /**
123 * Get the current path of this node, as if the current tree is below the supplied root path, using the supplied context.
124 *
125 * @param rootPath the path to the root of this AST tree; may not be null
126 * @param context the context; may not be null
127 * @return the path of this node; never null
128 */
129 public Path getPathRelativeTo( Path rootPath,
130 ExecutionContext context ) {
131 assert rootPath != null;
132 assert context != null;
133 PathFactory pathFactory = context.getValueFactories().getPathFactory();
134 Path parentPath = this.parent != null ? this.parent.getPathRelativeTo(rootPath, context) : rootPath;
135 return pathFactory.create(parentPath, name, getSameNameSiblingIndex());
136 }
137
138 /**
139 * Get the property with the supplied name.
140 *
141 * @param name the property name; never null
142 * @return the property, or null if no such property exists on the node
143 */
144 public Property getProperty( Name name ) {
145 return properties.get(name);
146 }
147
148 /**
149 * Set the property with the given name to the supplied value. Any existing property with the same name will be replaced.
150 *
151 * @param name the name of the property; may not be null
152 * @param value the value of the property; may not be null
153 * @return this node, for method chaining purposes
154 */
155 public AstNode setProperty( Name name,
156 Object value ) {
157 assert name != null;
158 assert value != null;
159 properties.put(name, new BasicSingleValueProperty(name, value));
160 return this;
161 }
162
163 /**
164 * Set the property with the given name to the supplied values. If there is at least one value, the new property will replace
165 * any existing property with the same name. This method does nothing if zero values are supplied.
166 *
167 * @param name the name of the property; may not be null
168 * @param values the values of the property
169 * @return this node, for method chaining purposes
170 * @see #removeProperty(Name)
171 */
172 public AstNode setProperty( Name name,
173 Object... values ) {
174 assert name != null;
175 assert values != null;
176 if (values.length != 0) {
177 properties.put(name, new BasicMultiValueProperty(name, values));
178 }
179 return this;
180 }
181
182 /**
183 * Remove and return the property with the supplied name.
184 *
185 * @param name the property name; may not be null
186 * @return the property that was removed, or null if there was no such property
187 */
188 public Property removeProperty( Name name ) {
189 return properties.remove(name);
190 }
191
192 /**
193 * Return the list of properties for this node.
194 *
195 * @return the list of properties for this node.
196 */
197 public List<Property> getProperties() {
198 return new ArrayList<Property>(properties.values());
199 }
200
201 /**
202 * Get the parent of this node.
203 *
204 * @return the parent node, or null if this node has no parent
205 */
206 public AstNode getParent() {
207 return parent;
208 }
209
210 /**
211 * Set the parent for this node. If this node already has a parent, this method will remove this node from the current parent.
212 * If the supplied parent is not null, then this node will be added to the supplied parent's children.
213 *
214 * @param parent the new parent, or null if this node is to have no parent
215 */
216 public void setParent( AstNode parent ) {
217 removeFromParent();
218 if (parent != null) {
219 this.parent = parent;
220 this.parent.children.add(this);
221 }
222 }
223
224 /**
225 * Insert the supplied node into the plan node tree immediately above this node. If this node has a parent when this method is
226 * called, the new parent essentially takes the place of this node within the list of children of the old parent. This method
227 * does nothing if the supplied new parent is null.
228 * <p>
229 * For example, consider a plan node tree before this method is called:
230 *
231 * <pre>
232 * A
233 * / | \
234 * / | \
235 * B C D
236 * </pre>
237 *
238 * Then after this method is called with <code>c.insertAsParent(e)</code>, the resulting plan node tree will be:
239 *
240 * <pre>
241 * A
242 * / | \
243 * / | \
244 * B E D
245 * |
246 * |
247 * C
248 * </pre>
249 *
250 * </p>
251 * <p>
252 * Also note that the node on which this method is called ('C' in the example above) will always be added as the
253 * {@link #addLastChild(AstNode) last child} to the new parent. This allows the new parent to already have children before
254 * this method is called.
255 * </p>
256 *
257 * @param newParent the new parent; method does nothing if this is null
258 */
259 public void insertAsParent( AstNode newParent ) {
260 if (newParent == null) return;
261 newParent.removeFromParent();
262 if (this.parent != null) {
263 this.parent.replaceChild(this, newParent);
264 }
265 newParent.addLastChild(this);
266 }
267
268 /**
269 * Remove this node from its parent, and return the node that used to be the parent of this node. Note that this method
270 * removes the entire subgraph under this node.
271 *
272 * @return the node that was the parent of this node, or null if this node had no parent
273 * @see #extractChild(AstNode)
274 * @see #extractFromParent()
275 */
276 public AstNode removeFromParent() {
277 AstNode result = this.parent;
278 if (this.parent != null) {
279 // Remove this node from its current parent ...
280 this.parent.children.remove(this);
281 this.parent = null;
282 }
283 return result;
284 }
285
286 /**
287 * Replace the supplied child with another node. If the replacement is already a child of this node, this method effectively
288 * swaps the position of the child and replacement nodes.
289 *
290 * @param child the node that is already a child and that is to be replaced; may not be null and must be a child
291 * @param replacement the node that is to replace the 'child' node; may not be null
292 * @return true if the child was successfully replaced
293 */
294 public boolean replaceChild( AstNode child,
295 AstNode replacement ) {
296 assert child != null;
297 assert replacement != null;
298 if (child.parent == this) {
299 int i = this.children.indexOf(child);
300 if (replacement.parent == this) {
301 // Swapping the positions ...
302 int j = this.children.indexOf(replacement);
303 this.children.set(i, replacement);
304 this.children.set(j, child);
305 return true;
306 }
307 // The replacement is not yet a child ...
308 this.children.set(i, replacement);
309 replacement.removeFromParent();
310 replacement.parent = this;
311 child.parent = null;
312 return true;
313 }
314 return false;
315 }
316
317 /**
318 * Get the number of child nodes.
319 *
320 * @return the number of children; never negative
321 */
322 public int getChildCount() {
323 return this.children.size();
324 }
325
326 /**
327 * Get the first child.
328 *
329 * @return the first child, or null if there are no children
330 */
331 public AstNode getFirstChild() {
332 return this.children.isEmpty() ? null : this.children.getFirst();
333 }
334
335 /**
336 * Get the last child.
337 *
338 * @return the last child, or null if there are no children
339 */
340 public AstNode getLastChild() {
341 return this.children.isEmpty() ? null : this.children.getLast();
342 }
343
344 /**
345 * Get the child at the supplied index.
346 *
347 * @param index the index
348 * @return the child, or null if there are no children
349 * @throws IndexOutOfBoundsException if the index is not valid given the number of children
350 */
351 public AstNode getChild( int index ) {
352 return this.children.isEmpty() ? null : this.children.get(index);
353 }
354
355 /**
356 * Add the supplied node to the front of the list of children.
357 *
358 * @param child the node that should be added as the first child; may not be null
359 */
360 public void addFirstChild( AstNode child ) {
361 assert child != null;
362 this.children.addFirst(child);
363 child.removeFromParent();
364 child.parent = this;
365 }
366
367 /**
368 * Add the supplied node to the end of the list of children.
369 *
370 * @param child the node that should be added as the last child; may not be null
371 */
372 public void addLastChild( AstNode child ) {
373 assert child != null;
374 this.children.addLast(child);
375 child.removeFromParent();
376 child.parent = this;
377 }
378
379 /**
380 * Add the supplied nodes at the end of the list of children.
381 *
382 * @param otherChildren the children to add; may not be null
383 */
384 public void addChildren( Iterable<AstNode> otherChildren ) {
385 assert otherChildren != null;
386 for (AstNode planNode : otherChildren) {
387 this.addLastChild(planNode);
388 }
389 }
390
391 /**
392 * Add the supplied nodes at the end of the list of children.
393 *
394 * @param first the first child to add
395 * @param second the second child to add
396 */
397 public void addChildren( AstNode first,
398 AstNode second ) {
399 if (first != null) this.addLastChild(first);
400 if (second != null) this.addLastChild(second);
401 }
402
403 /**
404 * Add the supplied nodes at the end of the list of children.
405 *
406 * @param first the first child to add
407 * @param second the second child to add
408 * @param third the third child to add
409 */
410 public void addChildren( AstNode first,
411 AstNode second,
412 AstNode third ) {
413 if (first != null) this.addLastChild(first);
414 if (second != null) this.addLastChild(second);
415 if (third != null) this.addLastChild(third);
416 }
417
418 /**
419 * Remove the node from this node.
420 *
421 * @param child the child node; may not be null
422 * @return true if the child was removed from this node, or false if the supplied node was not a child of this node
423 */
424 public boolean removeChild( AstNode child ) {
425 boolean result = this.children.remove(child);
426 if (result) {
427 child.parent = null;
428 }
429 return result;
430 }
431
432 /**
433 * Remove the child node from this node, and replace that child with its first child (if there is one).
434 *
435 * @param child the child to be extracted; may not be null and must have at most 1 child
436 * @see #extractFromParent()
437 */
438 public void extractChild( AstNode child ) {
439 if (child.getChildCount() == 0) {
440 removeChild(child);
441 } else {
442 AstNode grandChild = child.getFirstChild();
443 replaceChild(child, grandChild);
444 }
445 }
446
447 /**
448 * Extract this node from its parent, but replace this node with its child (if there is one).
449 *
450 * @see #extractChild(AstNode)
451 */
452 public void extractFromParent() {
453 this.parent.extractChild(this);
454 }
455
456 /**
457 * Get the unmodifiable list of child nodes. This list will immediately reflect any changes made to the children (via other
458 * methods), but this list cannot be used to add or remove children.
459 *
460 * @return the list of children, which immediately reflects changes but which cannot be modified directly; never null
461 */
462 public List<AstNode> getChildren() {
463 return childrenView;
464 }
465
466 /**
467 * {@inheritDoc}
468 * <p>
469 * This iterator is immutable.
470 * </p>
471 *
472 * @see java.lang.Iterable#iterator()
473 */
474 public Iterator<AstNode> iterator() {
475 return childrenView.iterator();
476 }
477
478 /**
479 * Remove all children from this node. All nodes immediately become orphaned. The resulting list will be mutable.
480 *
481 * @return a copy of all the of the children that were removed (and which have no parent); never null
482 */
483 public List<AstNode> removeAllChildren() {
484 if (this.children.isEmpty()) {
485 return new ArrayList<AstNode>(0);
486 }
487 List<AstNode> copyOfChildren = new ArrayList<AstNode>(this.children);
488 for (Iterator<AstNode> childIter = this.children.iterator(); childIter.hasNext();) {
489 AstNode child = childIter.next();
490 childIter.remove();
491 child.parent = null;
492 }
493 return copyOfChildren;
494 }
495
496 /**
497 * {@inheritDoc}
498 *
499 * @see java.lang.Object#toString()
500 */
501 @Override
502 public String toString() {
503 return getString(ExecutionContext.DEFAULT_CONTEXT);
504 }
505
506 /**
507 * {@inheritDoc}
508 *
509 * @see java.lang.Object#equals(java.lang.Object)
510 */
511 @Override
512 public final boolean equals( Object obj ) {
513 // Quite a few methods rely upon instance/reference equality ...
514 return super.equals(obj);
515 }
516
517 /**
518 * {@inheritDoc}
519 * <p>
520 * This class returns a new clone of the plan tree rooted at this node. However, the top node of the resulting plan tree (that
521 * is, the node returned from this method) has no parent.
522 * </p>
523 *
524 * @see java.lang.Object#clone()
525 */
526 @Override
527 public AstNode clone() {
528 return cloneWithoutNewParent();
529 }
530
531 protected AstNode cloneWithoutNewParent() {
532 AstNode result = new AstNode(this.name);
533 result.properties.putAll(this.properties);
534 // Clone the children ...
535 for (AstNode child : children) {
536 AstNode childClone = child.cloneWithoutNewParent();
537 // The child has no parent, so add the child to the new result ...
538 result.addLastChild(childClone);
539 }
540 return result;
541 }
542
543 /**
544 * Determine whether the supplied plan is equivalent to this plan.
545 *
546 * @param other the other plan to compare with this instance
547 * @return true if the two plans are equivalent, or false otherwise
548 */
549 public boolean isSameAs( AstNode other ) {
550 if (other == null) return false;
551 if (!this.name.equals(other.name)) return false;
552 if (!this.properties.equals(other.properties)) return false;
553 if (this.getChildCount() != other.getChildCount()) return false;
554 Iterator<AstNode> thisChildren = this.getChildren().iterator();
555 Iterator<AstNode> thatChildren = other.getChildren().iterator();
556 while (thisChildren.hasNext() && thatChildren.hasNext()) {
557 if (!thisChildren.next().isSameAs(thatChildren.next())) return false;
558 }
559 return true;
560 }
561
562 /**
563 * Get the string representation of this query object.
564 *
565 * @param context the execution context in which the conversion is to take place
566 * @return the string representation; never null
567 */
568 public String getString( ExecutionContext context ) {
569 StringBuilder sb = new StringBuilder();
570 getRecursiveString(context, sb, 0);
571 return sb.toString();
572 }
573
574 private void getRecursiveString( ExecutionContext context,
575 StringBuilder str,
576 int indentLevel ) {
577 for (int i = 0; i < indentLevel; ++i) {
578 str.append(" ");
579 }
580 getNodeString(context, str).append('\n');
581
582 // Recursively add children at one greater tab level
583 for (AstNode child : this) {
584 child.getRecursiveString(context, str, indentLevel + 1);
585 }
586 }
587
588 private StringBuilder getNodeString( ExecutionContext context,
589 StringBuilder str ) {
590 ValueFactory<String> strings = context.getValueFactories().getStringFactory();
591 str.append(strings.create(this.name));
592 if (properties != null && !properties.isEmpty()) {
593 str.append(" <");
594 boolean first = true;
595 for (Map.Entry<Name, Property> entry : properties.entrySet()) {
596 if (first) first = false;
597 else str.append(", ");
598 str.append(entry.getValue().getString(context.getNamespaceRegistry(), null, null));
599 }
600 str.append('>');
601 }
602 return str;
603 }
604
605 }