View Javadoc

1   package org.apache.torque.generator.source;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.AbstractList;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  /**
32   * Implementation of the RichSourceElement interface.
33   */
34  public class SourceElement
35  {
36      /**
37       * The name of the source element.
38       */
39      private String name;
40  
41      /**
42       * The parents of this  element; may be empty but not null.
43       */
44      private List<SourceElement> parents = new ParentList(this);
45  
46      /**
47       * All children elements.
48       */
49      private List<SourceElement> children = new ChildList(this);
50  
51      /**
52       * the source element's attributes.
53       */
54      private Map<String, Object> attributes = new HashMap<String, Object>();
55  
56      /**
57       * Constructor.
58       *
59       * @param name the name of the element, not null.
60       *
61       * @throws NullPointerException if name is null.
62       */
63      public SourceElement(String name)
64      {
65          if (name == null)
66          {
67              throw new NullPointerException("name must not be null");
68          }
69          this.name = name;
70      }
71  
72      /**
73       * Constructor.
74       *
75       * @param name sourceElementName name of the element, not null.
76       *
77       * @throws NullPointerException if sourceElementName is null.
78       */
79      public SourceElement(SourceElementName sourceElementName)
80      {
81          this(sourceElementName.getName());
82      }
83  
84      /**
85       * Returns the name of this source element.
86       *
87       * @return the name of this source element, never null.
88       */
89      public String getName()
90      {
91          return name;
92      }
93  
94      /**
95       * Returns the primary parent of this SourceElement.
96       *
97       * @return the primary parent of this SourceElement,
98       *         or null if this is a root element of the source graph.
99       */
100     public SourceElement getParent()
101     {
102         if (parents.size() == 0)
103         {
104             return null;
105         }
106         return parents.get(0);
107     }
108 
109     /**
110      * Returns the list of parents of this SourceElement.
111      * Parents can be added and removed via the methods exposed by
112      * the returned list.
113      *
114      * @return the list of parents of this source element, never null.
115      */
116     public List<SourceElement> getParents()
117     {
118         return parents;
119     }
120 
121     /**
122      * Returns all children of this SourceElement.
123      * Children can be added and removed via the methods exposed by
124      * the returned list.
125      *
126      * @return the list of children of this source element, never null.
127      */
128     public List<SourceElement> getChildren()
129     {
130         return children;
131     }
132 
133     /**
134      * Returns all children of this SourceElement which have the given name.
135      * Modifications on the returned list have no effect
136      * on the list of children of this SourceElement.
137      *
138      * @param name the name of the children to select, not null.
139      *
140      * @return the list of children of this source element with the given name,
141      *         never null.
142      *
143      * @throws NullPointerException if name is null.
144      */
145     public List<SourceElement> getChildren(String name)
146     {
147         if (name == null)
148         {
149             throw new NullPointerException("name must not be null");
150         }
151         List<SourceElement> result = new ArrayList<SourceElement>();
152         for (SourceElement sourceElement : children)
153         {
154             if (name.equals(sourceElement.getName()))
155             {
156                 result.add(sourceElement);
157             }
158         }
159         return result;
160     }
161 
162     /**
163      * Returns all children of this SourceElement which have the given name.
164      * Modifications on the returned list have no effect
165      * on the list of children of this SourceElement.
166      *
167      * @param sourceElementName contains the name of the child to select,
168      *        not null.
169      *
170      * @return the list of children of this source element with the given name,
171      *         never null.
172      *
173      * @throws NullPointerException if sourceElementName is null.
174      */
175     public List<SourceElement> getChildren(SourceElementName sourceElementName)
176     {
177         return getChildren(sourceElementName.getName());
178     }
179 
180     /**
181      * Returns the first child of this SourceElement which has
182      * the given name.
183      *
184      * @param name the name of the child to select, not null.
185      *
186      * @return the first child with the given name, or null if no child with
187      *         the given name exits.
188      *
189      * @throws NullPointerException if name is null.
190      */
191     public SourceElement getChild(String name)
192     {
193         for (SourceElement sourceElement : children)
194         {
195             if (name.equals(sourceElement.getName()))
196             {
197                 return sourceElement;
198             }
199         }
200         return null;
201     }
202 
203     /**
204      * Returns the first child of this SourceElement which has
205      * the given name.
206      *
207      * @param sourceElementName contains the name of the child to select,
208      *        not null.
209      *
210      * @return the first child with the given name, or null if no child with
211      *         the given name exits.
212      *
213      * @throws NullPointerException if sourceElementName is null.
214      */
215     public SourceElement getChild(SourceElementName sourceElementName)
216     {
217         return getChild(sourceElementName.getName());
218     }
219 
220     /**
221      * Returns whether children with the given name exist.
222      *
223      * @param name the name of the child element, not null.
224      *
225      * @return true if children with the given name exist, false otherwise.
226      *
227      * @throws NullPointerException if name is null.
228      */
229     public boolean hasChild(String name)
230     {
231         return SourcePath.hasChild(this, name);
232     }
233 
234 
235     /**
236      * Returns all the following elements after this element
237      * with the given name.
238      * If name is null, all following elements are returned.
239      * If this element has no parent, an empty list is returned.
240      *
241      * @param name the name of the following elements to select,
242      *        or null to select all following elements.
243      * @return a list containing the following elements with the given name,
244      *         never null.
245      *
246      * @see <a href="http://www.w3.org/TR/xpath#axes"></a>
247      */
248     public List<SourceElement> getFollowing(String name)
249     {
250         return SourcePath.getFollowing(this, name);
251     }
252 
253     /**
254      * Returns whether a following element exists as a child of the parent of
255      * this element.
256      *
257      * @return true if a following element exists, false if not.
258      */
259     public boolean hasFollowing()
260     {
261         return SourcePath.hasFollowing(this);
262     }
263 
264     /**
265      * Returns whether an preceding exists as a child of the parent of
266      * this element.
267      *
268      * @return true if a preceding element exists, false if not.
269      */
270     public boolean hasPreceding()
271     {
272         return SourcePath.hasPreceding(this);
273     }
274 
275     /**
276      * Returns whether a following element exists as a child of the parent of
277      * this element, which has the same name as this source element.
278      *
279      * @return true if a following sibling exists, false if not.
280      */
281     public boolean hasFollowingSibling()
282     {
283         return SourcePath.hasFollowingSibling(this);
284     }
285 
286     /**
287      * Returns whether an preceding exists as a child of the parent of
288      * this element, which has the same name as this source element.
289      *
290      * @return true if a preceding sibling exists, false if not.
291      */
292     public boolean hasPrecedingSibling()
293     {
294         return SourcePath.hasPrecedingSibling(this);
295     }
296 
297     /**
298      * Returns all the preceding elements before this element
299      * with the given name.
300      * If name is null, all preceding elements are returned.
301      * If this element has no parent, an empty list is returned.
302      *
303      * @param name the name of the preceding elements to select,
304      *        or null to select all preceding elements.
305      * @return a list containing the following elements with the given name,
306      *         never null.
307      *
308      * @see <a href="http://www.w3.org/TR/xpath#axes"></a>
309      */
310     public List<SourceElement> getPreceding(String name)
311     {
312         return SourcePath.getPreceding(this, name);
313     }
314 
315     /**
316      * Returns the object stored in the attribute with key null.
317      *
318      * @return the stored object, or null if no object is stored
319      *         under the key null.
320      */
321     public Object getTextAttribute()
322     {
323         return attributes.get(null);
324     }
325 
326     /**
327      * Returns the object stored in a given attribute.
328      *
329      * @param name the name of the attribute, can be null.
330      *
331      * @return the stored object, or null if no object is stored under that key.
332      */
333     public Object getAttribute(String name)
334     {
335         return attributes.get(name);
336     }
337 
338     /**
339      * Returns the object stored in a given attribute.
340      *
341      * @param sourceAttributeName contains the name of the attribute, not null.
342      *
343      * @return the stored object, or null if no object is stored under that key.
344      *
345      * @throws NullPointerException if sourceAttributeName is null.
346      */
347     public Object getAttribute(SourceAttributeName sourceAttributeName)
348     {
349         return getAttribute(sourceAttributeName.getName());
350     }
351 
352     /**
353      * Sets the attribute of a Source element.
354      *
355      * @param name the name of the attribute.
356      * @param value the value of the attribute,
357      *        or null to remove the attribute.
358      *
359      * @return the previous value of this attribute.
360      */
361     public Object setAttribute(String name, Object value)
362     {
363         if (value == null)
364         {
365             return attributes.remove(name);
366         }
367         return attributes.put(name, value);
368     }
369 
370     /**
371      * Sets the attribute of a Source element.
372      *
373      * @param sourceAttributeName contains the name of the attribute, not null.
374      * @param value the value of the attribute,
375      *        or null to remove the attribute.
376      *
377      * @return the previous value of this attribute.
378      */
379     public Object setAttribute(
380             SourceAttributeName sourceAttributeName,
381             Object value)
382     {
383         return setAttribute(sourceAttributeName.getName(), value);
384     }
385 
386     /**
387      * Returns the name of all set attributes. Note : null may be contained
388      * in the set.
389      *
390      * @return the name of all set values.
391      */
392     public Set<String> getAttributeNames()
393     {
394         return attributes.keySet();
395     }
396 
397     /**
398      * Creates a deep copy of this RichSourceelementImpl object.
399      * All the elements in the source graph of this Element are copied as well
400      * (i.e the copy contains the children, the children's children, ....,
401      * the parents, the parent's parents...)
402      *
403      * @return the copy, not null.
404      */
405     public SourceElement copy()
406     {
407         Map<SourceElement, SourceElement> copied
408                 = new HashMap<SourceElement, SourceElement>();
409         return copy(this, copied);
410     }
411 
412     /**
413      * Deep copies the content of one RichSourceElementImpl object into another.
414      *
415      * @param toCopy the source element, not null.
416      * @param copiedElements Map containing all source elements which are
417      *        already copied.
418      *
419      * @return the copy of the source, not null.
420      */
421     private SourceElement copy(SourceElement toCopy,
422             Map<SourceElement, SourceElement> copiedElements)
423     {
424         SourceElement copied = copiedElements.get(toCopy);
425         if (copied != null)
426         {
427             return copied;
428         }
429         copied = new SourceElement(toCopy.getName());
430         copiedElements.put(toCopy, copied);
431 
432         for (String attributeName : toCopy.getAttributeNames())
433         {
434             copied.setAttribute(attributeName, toCopy.getAttribute(attributeName));
435         }
436 
437         {
438             List<SourceElement> childrenOfCopied = copied.getChildren();
439             for (SourceElement child : toCopy.getChildren())
440             {
441                 SourceElement copiedChild = copy(child, copiedElements);
442                 if (!childrenOfCopied.contains(copiedChild))
443                 {
444                     childrenOfCopied.add(copiedChild);
445                 }
446             }
447         }
448 
449         {
450             List<SourceElement> parentsOfCopied = copied.getParents();
451             for (SourceElement parent : toCopy.getParents())
452             {
453                 SourceElement copiedParent
454                         = copy(parent, copiedElements);
455                 if (!parentsOfCopied.contains(copiedParent))
456                 {
457                     parentsOfCopied.add(copiedParent);
458                 }
459             }
460         }
461 
462         return copied;
463     }
464 
465     /**
466      * Checks whether the source element graph of this sourceElement,
467      * and its position therein, equals the source element graph
468      * and the position of the provided SourceElement.
469      * This is an expensive operation if the graphs are large.
470      *
471      * @param toCompare the source element to compare, may be null.
472      *
473      * @return true if all source elements in the toCompare tree have the equal
474      *          content as the source elements in this tree.
475      */
476     public boolean graphEquals(SourceElement toCompare)
477     {
478         Set<SourceElement> alreadyCompared = new HashSet<SourceElement>();
479         return graphEquals(this, toCompare, alreadyCompared);
480     }
481 
482     /**
483      * Checks whether the source element graph of one sourceElement,
484      * and its position therein, equals the source element graph
485      * and the position of another SourceElement.
486      * This is an expensive operation if the graphs are large.
487      *
488      * @param reference the reference element, may be null.
489      * @param toCompare the element which is to the referenced element,
490      *        may be null.
491      * @param compared a set of elements which are already compared
492      *        and which attributes and relative positions to the other
493      *        compared elements were equal so far.
494      * @return true if the elements are equal or if equality is currently
495      *          checked in another recursive iteration.
496      */
497     private boolean graphEquals(SourceElement reference,
498             SourceElement toCompare,
499             Set<SourceElement> compared)
500     {
501         if ((reference == null && toCompare != null)
502                 || (reference != null && toCompare == null))
503         {
504             return false;
505         }
506         if (reference == null && toCompare == null)
507         {
508             return true;
509         }
510 
511         if (compared.contains(reference))
512         {
513             // although it is not certain that reference is equal to toCompare
514             // if it is contained in compared, it does mean that equality
515             // was or is being checked and that place will return false
516             // if equality is not given; so we can return true here.
517             return true;
518         }
519 
520         compared.add(reference);
521 
522         if (!reference.getName().equals(toCompare.getName()))
523         {
524             return false;
525         }
526 
527         if (reference.getAttributeNames().size()
528                 != toCompare.getAttributeNames().size())
529         {
530             return false;
531         }
532 
533         for (String attributeName : reference.getAttributeNames())
534         {
535             Object referenceAttributeContent
536                     = reference.getAttribute(attributeName);
537             Object toCompareAttributeContent
538                     = toCompare.getAttribute(attributeName);
539             if (referenceAttributeContent == null)
540             {
541                 if (toCompareAttributeContent != null)
542                 {
543                     return false;
544                 }
545             }
546             else
547             {
548                 if (!referenceAttributeContent.equals(
549                         toCompareAttributeContent))
550                 {
551                     return false;
552                 }
553             }
554         }
555 
556         if (!graphEquals(reference.getParent(), toCompare.getParent(), compared))
557         {
558             return false;
559         }
560 
561         if (reference.getChildren().size()
562                 != toCompare.getChildren().size())
563         {
564             return false;
565         }
566 
567         Iterator<SourceElement> referenceChildIt
568                 = reference.getChildren().iterator();
569         Iterator<SourceElement> toCompareChildIt
570                 = toCompare.getChildren().iterator();
571         while (referenceChildIt.hasNext())
572         {
573             SourceElement referenceChild = referenceChildIt.next();
574             SourceElement toCompareChild = toCompareChildIt.next();
575             if (!graphEquals(referenceChild, toCompareChild, compared))
576             {
577                 return false;
578             }
579         }
580         return true;
581     }
582 
583     @Override
584     public String toString()
585     {
586         Set<SourceElement> alreadyProcessed = new HashSet<SourceElement>();
587         StringBuilder result = new StringBuilder();
588         toString(alreadyProcessed, result);
589         return result.toString();
590     }
591 
592     /**
593      * Creates a String representation of the element for debugging purposes.
594      * @param alreadyProcessed the elements which are already processed
595      *        (for avoiding loops). The current element is added to this set.
596      * @param result the String builder to which the string representation
597      *        should be appended.
598      */
599     private void toString(
600             Set<SourceElement> alreadyProcessed,
601             StringBuilder result)
602     {
603         alreadyProcessed.add(this);
604         result.append("(name=").append(name)
605                 .append(",attributes=(");
606         Iterator<Map.Entry<String, Object>> entryIt
607                 = attributes.entrySet().iterator();
608         while (entryIt.hasNext())
609         {
610             Map.Entry<String, Object> entry = entryIt.next();
611             result.append(entry.getKey()).append("=").append(entry.getValue());
612             if (entryIt.hasNext())
613             {
614                 result.append(",");
615             }
616         }
617         result.append("),children=(");
618         Iterator<SourceElement> childIt = children.iterator();
619         while (childIt.hasNext())
620         {
621             SourceElement child = childIt.next();
622             if (alreadyProcessed.contains(child))
623             {
624                 result.append("<<loop detected>>");
625             }
626             else
627             {
628                 child.toString(alreadyProcessed, result);
629             }
630             if (childIt.hasNext())
631             {
632                 result.append(",");
633             }
634         }
635         result.append("))");
636     }
637 
638     /**
639      * A list of children which overrides the add and remove methods
640      * such that the parents of the source element are updated as well.
641      */
642     private static class ChildList extends AbstractList<SourceElement>
643     {
644         /** The source element to which this child list belongs, not null. */
645         private SourceElement sourceElement;
646 
647         /** The children list, not null. */
648         private List<SourceElement> children
649                 = new ArrayList<SourceElement>();
650 
651         /**
652          * Constructor.
653          *
654          * @param sourceElement The source element to which
655          *        this child list belongs, not null.
656          *
657          * @throws NullPointerException if <code>sourceElement</code> is null.
658          */
659         public ChildList(SourceElement sourceElement)
660         {
661             if (sourceElement == null)
662             {
663                 throw new NullPointerException(
664                         "sourceElement must not be null");
665             }
666             this.sourceElement = sourceElement;
667         }
668 
669         @Override
670         public SourceElement get(int index)
671         {
672             return children.get(index);
673         }
674 
675         @Override
676         public int size()
677         {
678             return children.size();
679         }
680 
681         @Override
682         public void add(int position, SourceElement child)
683         {
684             if (children.contains(child))
685             {
686                 throw new IllegalArgumentException(
687                         "Element " + child + " is already a child of "
688                         + sourceElement);
689             }
690             children.add(position, child);
691             List<SourceElement> parents = child.getParents();
692             if (!parents.contains(sourceElement))
693             {
694                 parents.add(sourceElement);
695             }
696         }
697 
698         @Override
699         public SourceElement remove(int index)
700         {
701             SourceElement result = children.remove(index);
702             result.getParents().remove(sourceElement);
703             return result;
704         }
705 
706         @Override
707         public SourceElement set(int index, SourceElement child)
708         {
709             // allow setting an already contained child at the same position,
710             // but throw an error if the child is set at other position.
711             if (children.contains(child) && !children.get(index).equals(child))
712             {
713                 throw new IllegalArgumentException(
714                         "Element " + child + " is already a child of "
715                         + sourceElement);
716             }
717             SourceElement previousChild = children.set(index, child);
718             previousChild.getParents().remove(sourceElement);
719             List<SourceElement> parents = child.getParents();
720             if (!parents.contains(sourceElement))
721             {
722                 parents.add(sourceElement);
723             }
724             return previousChild;
725         }
726     }
727     /**
728      * Overrides the add and remove methods such that the children of the
729      * source element are updated as well.
730      */
731     private static class ParentList extends AbstractList<SourceElement>
732     {
733         /** The source element to which this parent list belongs, not null. */
734         private SourceElement sourceElement;
735 
736         /** The parent list, not null. */
737         private List<SourceElement> parents
738                 = new ArrayList<SourceElement>();
739 
740         /**
741          * Constructor.
742          *
743          * @param sourceElement The source element to which
744          *        this parent list belongs, not null.
745          *
746          * @throws NullPointerException if <code>sourceElement</code> is null.
747          */
748         public ParentList(SourceElement sourceElement)
749         {
750             if (sourceElement == null)
751             {
752                 throw new NullPointerException(
753                         "sourceElement must not be null");
754             }
755             this.sourceElement = sourceElement;
756         }
757 
758         @Override
759         public SourceElement get(int index)
760         {
761             return parents.get(index);
762         }
763 
764         @Override
765         public int size()
766         {
767             return parents.size();
768         }
769 
770         @Override
771         public void add(int position, SourceElement parent)
772         {
773             if (parents.contains(parent))
774             {
775                 throw new IllegalArgumentException(
776                         "Element " + parent + " is already a parent of "
777                         + sourceElement);
778             }
779             parents.add(position, parent);
780             List<SourceElement> children = parent.getChildren();
781             if (!children.contains(sourceElement))
782             {
783                 children.add(sourceElement);
784             }
785         }
786 
787         @Override
788         public SourceElement remove(int index)
789         {
790             SourceElement result = parents.remove(index);
791             result.getChildren().remove(sourceElement);
792             return result;
793         }
794 
795         @Override
796         public SourceElement set(int index, SourceElement parent)
797         {
798             // allow setting an already contained parent at the same position,
799             // but throw an error if the parent is set at other position.
800             if (parents.contains(parent) && !parents.get(index).equals(parent))
801             {
802                 throw new IllegalArgumentException(
803                         "Element " + parent + " is already a parent of "
804                         + sourceElement);
805             }
806             SourceElement previousParent = parents.set(index, parent);
807             previousParent.getChildren().remove(sourceElement);
808             List<SourceElement> children = parent.getChildren();
809             if (!children.contains(sourceElement))
810             {
811                 children.add(sourceElement);
812             }
813             return previousParent;
814         }
815     }
816 }