View Javadoc

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 }