View Javadoc

1   package org.apache.maven.shared.dependency.tree;
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.io.StringWriter;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import org.apache.maven.artifact.Artifact;
29  import org.apache.maven.artifact.versioning.ArtifactVersion;
30  import org.apache.maven.artifact.versioning.VersionRange;
31  import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
32  import org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor;
33  
34  /**
35   * Represents an artifact node within a Maven project's dependency tree.
36   * 
37   * @author Edwin Punzalan
38   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
39   * @version $Id: DependencyNode.java 1100703 2011-05-08 08:27:33Z hboutemy $
40   */
41  public class DependencyNode
42  {
43      // constants --------------------------------------------------------------
44  
45      /**
46       * State that represents an included dependency node.
47       * 
48       * @since 1.1
49       */
50      public static final int INCLUDED = 0;
51  
52      /**
53       * State that represents a dependency node that has been omitted for duplicating another dependency node.
54       * 
55       * @since 1.1
56       */
57      public static final int OMITTED_FOR_DUPLICATE = 1;
58  
59      /**
60       * State that represents a dependency node that has been omitted for conflicting with another dependency node.
61       * 
62       * @since 1.1
63       */
64      public static final int OMITTED_FOR_CONFLICT = 2;
65  
66      /**
67       * State that represents a dependency node that has been omitted for introducing a cycle into the dependency tree.
68       * 
69       * @since 1.1
70       */
71      public static final int OMITTED_FOR_CYCLE = 3;
72      
73      // classes ----------------------------------------------------------------
74      
75      /**
76       * Utility class to concatenate a number of parameters with separator tokens.   
77       */
78      private static class ItemAppender
79      {
80          private StringBuffer buffer;
81          
82          private String startToken;
83          
84          private String separatorToken;
85          
86          private String endToken;
87          
88          private boolean appended;
89          
90          public ItemAppender( StringBuffer buffer, String startToken, String separatorToken, String endToken )
91          {
92              this.buffer = buffer;
93              this.startToken = startToken;
94              this.separatorToken = separatorToken;
95              this.endToken = endToken;
96              
97              appended = false;
98          }
99  
100         public ItemAppender append( String item )
101         {
102             appendToken();
103             
104             buffer.append( item );
105             
106             return this;
107         }
108         
109         public ItemAppender append( String item1, String item2 )
110         {
111             appendToken();
112             
113             buffer.append( item1 ).append( item2 );
114             
115             return this;
116         }
117         
118         public void flush()
119         {
120             if ( appended )
121             {
122                 buffer.append( endToken );
123                 
124                 appended = false;
125             }
126         }
127         
128         private void appendToken()
129         {
130             buffer.append( appended ? separatorToken : startToken );
131             
132             appended = true;
133         }
134     }
135 
136     // fields -----------------------------------------------------------------
137 
138     /**
139      * The artifact that is attached to this dependency node.
140      */
141     private final Artifact artifact;
142 
143     /**
144      * The list of child dependency nodes of this dependency node.
145      */
146     private final List<DependencyNode> children;
147 
148     /**
149      * The parent dependency node of this dependency node.
150      */
151     private DependencyNode parent;
152 
153     /**
154      * The state of this dependency node. This can be either <code>INCLUDED</code>,
155      * <code>OMITTED_FOR_DUPLICATE</code>, <code>OMITTED_FOR_CONFLICT</code> or <code>OMITTED_FOR_CYCLE</code>.
156      * 
157      * @see #INCLUDED
158      * @see #OMITTED_FOR_DUPLICATE
159      * @see #OMITTED_FOR_CONFLICT
160      * @see #OMITTED_FOR_CYCLE
161      */
162     private int state;
163 
164     /**
165      * The artifact related to the state of this dependency node. For dependency nodes with a state of
166      * <code>OMITTED_FOR_DUPLICATE</code> or <code>OMITTED_FOR_CONFLICT</code>, this represents the artifact that
167      * was conflicted with. For dependency nodes of other states, this is always <code>null</code>.
168      */
169     private Artifact relatedArtifact;
170     
171     /**
172      * The scope of this node's artifact before it was updated due to conflicts, or <code>null</code> if the artifact
173      * scope has not been updated.
174      */
175     private String originalScope;
176 
177     /**
178      * The scope that this node's artifact was attempted to be updated to due to conflicts, or <code>null</code> if
179      * the artifact scope has not failed being updated.
180      */
181     private String failedUpdateScope;
182 
183     /**
184      * The version of this node's artifact before it was updated by dependency management, or <code>null</code> if the
185      * artifact version has not been managed.
186      */
187     private String premanagedVersion;
188     
189     /**
190      * The scope of this node's artifact before it was updated by dependency management, or <code>null</code> if the
191      * artifact scope has not been managed.
192      */
193     private String premanagedScope;
194 
195     private VersionRange versionSelectedFromRange;
196     
197     private List<ArtifactVersion> availableVersions;
198 
199     // constructors -----------------------------------------------------------
200 
201     /**
202      * Creates a new dependency node for the specified artifact with an included state.
203      * 
204      * @param artifact
205      *            the artifact attached to the new dependency node
206      * @throws IllegalArgumentException
207      *             if the parameter constraints were violated
208      * @since 1.1
209      */
210     public DependencyNode( Artifact artifact )
211     {
212         this( artifact, INCLUDED );
213     }
214 
215     /**
216      * Creates a new dependency node for the specified artifact with the specified state.
217      * 
218      * @param artifact
219      *            the artifact attached to the new dependency node
220      * @param state
221      *            the state of the new dependency node. This can be either <code>INCLUDED</code> or
222      *            <code>OMITTED_FOR_CYCLE</code>.
223      * @throws IllegalArgumentException
224      *             if the parameter constraints were violated
225      * @since 1.1
226      */
227     public DependencyNode( Artifact artifact, int state )
228     {
229         this( artifact, state, null );
230     }
231 
232     /**
233      * Creates a new dependency node for the specified artifact with the specified state and related artifact.
234      * 
235      * @param artifact
236      *            the artifact attached to the new dependency node
237      * @param state
238      *            the state of the new dependency node. This can be either <code>INCLUDED</code>,
239      *            <code>OMITTED_FOR_DUPLICATE</code>, <code>OMITTED_FOR_CONFLICT</code> or
240      *            <code>OMITTED_FOR_CYCLE</code>.
241      * @param relatedArtifact
242      *            the artifact related to the state of this dependency node. For dependency nodes with a state of
243      *            <code>OMITTED_FOR_DUPLICATE</code> or <code>OMITTED_FOR_CONFLICT</code>, this represents the
244      *            artifact that was conflicted with. For dependency nodes of other states, this should always be
245      *            <code>null</code>.
246      * @throws IllegalArgumentException
247      *             if the parameter constraints were violated
248      * @since 1.1
249      */
250     public DependencyNode( Artifact artifact, int state, Artifact relatedArtifact )
251     {
252         if ( artifact == null )
253         {
254             throw new IllegalArgumentException( "artifact cannot be null" );
255         }
256 
257         if ( state < INCLUDED || state > OMITTED_FOR_CYCLE )
258         {
259             throw new IllegalArgumentException( "Unknown state: " + state );
260         }
261 
262         boolean requiresRelatedArtifact = ( state == OMITTED_FOR_DUPLICATE || state == OMITTED_FOR_CONFLICT );
263 
264         if ( requiresRelatedArtifact && relatedArtifact == null )
265         {
266             throw new IllegalArgumentException( "Related artifact is required for states "
267                             + "OMITTED_FOR_DUPLICATE and OMITTED_FOR_CONFLICT" );
268         }
269 
270         if ( !requiresRelatedArtifact && relatedArtifact != null )
271         {
272             throw new IllegalArgumentException( "Related artifact is only required for states "
273                             + "OMITTED_FOR_DUPLICATE and OMITTED_FOR_CONFLICT" );
274         }
275 
276         this.artifact = artifact;
277         this.state = state;
278         this.relatedArtifact = relatedArtifact;
279 
280         children = new ArrayList<DependencyNode>();
281     }
282     
283     /**
284      * Creates a new dependency node.
285      * 
286      * @deprecated As of 1.1, replaced by {@link #DependencyNode(Artifact, int, Artifact)}
287      */
288     DependencyNode()
289     {
290         artifact = null;
291         children = new ArrayList<DependencyNode>();
292     }
293 
294     // public methods ---------------------------------------------------------
295 
296     /**
297      * Applies the specified dependency node visitor to this dependency node and its children.
298      * 
299      * @param visitor
300      *            the dependency node visitor to use
301      * @return the visitor result of ending the visit to this node
302      * @since 1.1
303      */
304     public boolean accept( DependencyNodeVisitor visitor )
305     {
306         if ( visitor.visit( this ) )
307         {
308             for ( DependencyNode child : getChildren() )
309             {
310                 if ( !child.accept( visitor ) )
311                 {
312                     break;
313                 }
314             }
315         }
316 
317         return visitor.endVisit( this );
318     }
319 
320     /**
321      * Adds the specified dependency node to this dependency node's children.
322      * 
323      * @param child
324      *            the child dependency node to add
325      * @since 1.1
326      */
327     public void addChild( DependencyNode child )
328     {
329         children.add( child );
330         child.parent = this;
331     }
332 
333     /**
334      * Removes the specified dependency node from this dependency node's children.
335      * 
336      * @param child
337      *            the child dependency node to remove
338      * @since 1.1
339      */
340     public void removeChild( DependencyNode child )
341     {
342         children.remove( child );
343         child.parent = null;
344     }
345 
346     /**
347      * Gets the parent dependency node of this dependency node.
348      * 
349      * @return the parent dependency node
350      */
351     public DependencyNode getParent()
352     {
353         return parent;
354     }
355 
356     /**
357      * Gets the artifact attached to this dependency node.
358      * 
359      * @return the artifact
360      */
361     public Artifact getArtifact()
362     {
363         return artifact;
364     }
365     
366     /**
367      * Gets the depth of this dependency node within its hierarchy.
368      * 
369      * @return the depth
370      * @deprecated As of 1.1, depth is computed by node hierarchy. With the introduction of node
371      *             visitors and filters this method can give misleading results. For example, consider
372      *             serializing a tree with a filter using a visitor: this method would return the
373      *             unfiltered depth of a node, whereas the correct depth would be calculated by the
374      *             visitor.
375      */
376     public int getDepth()
377     {
378         int depth = 0;
379         
380         DependencyNode node = getParent();
381         
382         while ( node != null )
383         {
384             depth++;
385             
386             node = node.getParent();
387         }
388         
389         return depth;
390     }
391 
392     /**
393      * Gets the list of child dependency nodes of this dependency node.
394      * 
395      * @return the list of child dependency nodes
396      */
397     public List<DependencyNode> getChildren()
398     {
399         return Collections.unmodifiableList( children );
400     }
401 
402     public boolean hasChildren()
403     {
404         return children.size() > 0;
405     }
406 
407     /**
408      * Gets the state of this dependency node.
409      * 
410      * @return the state: either <code>INCLUDED</code>, <code>OMITTED_FOR_DUPLICATE</code>,
411      *         <code>OMITTED_FOR_CONFLICT</code> or <code>OMITTED_FOR_CYCLE</code>.
412      * @since 1.1
413      */
414     public int getState()
415     {
416         return state;
417     }
418 
419     /**
420      * Gets the artifact related to the state of this dependency node. For dependency nodes with a state of
421      * <code>OMITTED_FOR_CONFLICT</code>, this represents the artifact that was conflicted with. For dependency nodes
422      * of other states, this is always <code>null</code>.
423      * 
424      * @return the related artifact
425      * @since 1.1
426      */
427     public Artifact getRelatedArtifact()
428     {
429         return relatedArtifact;
430     }
431     
432     /**
433      * Gets the scope of this node's artifact before it was updated due to conflicts.
434      * 
435      * @return the original scope, or <code>null</code> if the artifact scope has not been updated
436      * @since 1.1
437      */
438     public String getOriginalScope()
439     {
440         return originalScope;
441     }
442 
443     /**
444      * Sets the scope of this node's artifact before it was updated due to conflicts.
445      * 
446      * @param originalScope
447      *            the original scope, or <code>null</code> if the artifact scope has not been updated
448      * @since 1.1
449      */
450     public void setOriginalScope( String originalScope )
451     {
452         this.originalScope = originalScope;
453     }
454 
455     /**
456      * Gets the scope that this node's artifact was attempted to be updated to due to conflicts.
457      * 
458      * @return the failed update scope, or <code>null</code> if the artifact scope has not failed being updated
459      * @since 1.1
460      */
461     public String getFailedUpdateScope()
462     {
463         return failedUpdateScope;
464     }
465 
466     /**
467      * Sets the scope that this node's artifact was attempted to be updated to due to conflicts.
468      * 
469      * @param failedUpdateScope
470      *            the failed update scope, or <code>null</code> if the artifact scope has not failed being updated
471      * @since 1.1
472      */
473     public void setFailedUpdateScope( String failedUpdateScope )
474     {
475         this.failedUpdateScope = failedUpdateScope;
476     }
477     
478     /**
479      * Gets the version of this node's artifact before it was updated by dependency management.
480      * 
481      * @return the premanaged version, or <code>null</code> if the artifact version has not been managed
482      * @since 1.1
483      */
484     public String getPremanagedVersion()
485     {
486         return premanagedVersion;
487     }
488 
489     /**
490      * Sets the version of this node's artifact before it was updated by dependency management.
491      * 
492      * @param premanagedVersion
493      *            the premanaged version, or <code>null</code> if the artifact version has not been managed
494      * @since 1.1
495      */
496     public void setPremanagedVersion( String premanagedVersion )
497     {
498         this.premanagedVersion = premanagedVersion;
499     }
500     
501     /**
502      * Gets the scope of this node's artifact before it was updated by dependency management.
503      * 
504      * @return the premanaged scope, or <code>null</code> if the artifact scope has not been managed
505      * @since 1.1
506      */
507     public String getPremanagedScope()
508     {
509         return premanagedScope;
510     }
511     
512     /**
513      * Sets the scope of this node's artifact before it was updated by dependency management.
514      * 
515      * @param premanagedScope
516      *            the premanaged scope, or <code>null</code> if the artifact scope has not been managed
517      * @since 1.1
518      */
519     public void setPremanagedScope( String premanagedScope )
520     {
521         this.premanagedScope = premanagedScope;
522     }
523 
524     /**
525      * If the version was selected from a range this method will return the range.
526      * 
527      * @return the version range before a version was selected, or <code>null</code> if the artifact had a explicit
528      *         version.
529      * @since 1.2
530      */
531     public VersionRange getVersionSelectedFromRange()
532     {
533         return versionSelectedFromRange;
534     }
535     
536     public void setVersionSelectedFromRange( VersionRange versionSelectedFromRange )
537     {
538         this.versionSelectedFromRange = versionSelectedFromRange;
539     }
540 
541     /**
542      * If the version was selected from a range this method will return the available versions when making the decision.
543      * 
544      * @return {@link List} &lt; {@link String} > the versions available when a version was selected from a range, or
545      *         <code>null</code> if the artifact had a explicit version.
546      * @since 1.2
547      */
548     public List<ArtifactVersion> getAvailableVersions()
549     {
550         return availableVersions;
551     }
552     
553     public void setAvailableVersions( List<ArtifactVersion> availableVersions )
554     {
555         this.availableVersions = availableVersions;
556     }
557 
558     /**
559      * Changes the state of this dependency node to be omitted for conflict or duplication, depending on the specified
560      * related artifact.
561      * 
562      * <p>
563      * If the related artifact has a version equal to this dependency node's artifact, then this dependency node's state
564      * is changed to <code>OMITTED_FOR_DUPLICATE</code>, otherwise it is changed to <code>OMITTED_FOR_CONFLICT</code>.
565      * Omitting this dependency node also removes all of its children.
566      * </p>
567      * 
568      * @param relatedArtifact
569      *            the artifact that this dependency node conflicted with
570      * @throws IllegalStateException
571      *             if this dependency node's state is not <code>INCLUDED</code>
572      * @throws IllegalArgumentException
573      *             if the related artifact was <code>null</code> or had a different dependency conflict id to this
574      *             dependency node's artifact
575      * @see #OMITTED_FOR_DUPLICATE
576      * @see #OMITTED_FOR_CONFLICT
577      * @since 1.1
578      */
579     public void omitForConflict( Artifact relatedArtifact )
580     {
581         if ( getState() != INCLUDED )
582         {
583             throw new IllegalStateException( "Only INCLUDED dependency nodes can be omitted for conflict" );
584         }
585 
586         if ( relatedArtifact == null )
587         {
588             throw new IllegalArgumentException( "Related artifact cannot be null" );
589         }
590 
591         if ( !relatedArtifact.getDependencyConflictId().equals( getArtifact().getDependencyConflictId() ) )
592         {
593             throw new IllegalArgumentException( "Related artifact has a different dependency conflict id" );
594         }
595 
596         this.relatedArtifact = relatedArtifact;
597 
598         boolean duplicate = false;
599         if ( getArtifact().getVersion() != null )
600         {
601             duplicate = getArtifact().getVersion().equals( relatedArtifact.getVersion() );
602         }
603         else if ( getArtifact().getVersionRange() != null )
604         {
605             duplicate = getArtifact().getVersionRange().equals( relatedArtifact.getVersionRange() );
606         }
607         else
608         {
609             throw new RuntimeException( "Artifact version and version range is null: " + getArtifact() );
610         }
611 
612         state = duplicate ? OMITTED_FOR_DUPLICATE : OMITTED_FOR_CONFLICT;
613 
614         removeAllChildren();
615     }
616 
617     /**
618      * Changes the state of this dependency node to be omitted for a cycle in the dependency tree.
619      * 
620      * <p>
621      * Omitting this node sets its state to <code>OMITTED_FOR_CYCLE</code> and removes all of its children.
622      * </p>
623      * 
624      * @throws IllegalStateException
625      *             if this dependency node's state is not <code>INCLUDED</code>
626      * @see #OMITTED_FOR_CYCLE
627      * @since 1.1
628      */
629     public void omitForCycle()
630     {
631         if ( getState() != INCLUDED )
632         {
633             throw new IllegalStateException( "Only INCLUDED dependency nodes can be omitted for cycle" );
634         }
635 
636         state = OMITTED_FOR_CYCLE;
637 
638         removeAllChildren();
639     }
640     
641     /**
642      * Gets an iterator that returns this dependency node and it's children in preorder traversal.
643      * 
644      * @return the preorder traversal iterator
645      * @see #preorderIterator()
646      */
647     public Iterator<DependencyNode> iterator()
648     {
649         return preorderIterator();
650     }
651 
652     /**
653      * Gets an iterator that returns this dependency node and it's children in preorder traversal.
654      * 
655      * @return the preorder traversal iterator
656      * @see DependencyTreePreorderIterator
657      */
658     public Iterator<DependencyNode> preorderIterator()
659     {
660         return new DependencyTreePreorderIterator( this );
661     }
662 
663     /**
664      * Gets an iterator that returns this dependency node and it's children in postorder traversal.
665      * 
666      * @return the postorder traversal iterator
667      * @see DependencyTreeInverseIterator
668      */
669     public Iterator<DependencyNode> inverseIterator()
670     {
671         return new DependencyTreeInverseIterator( this );
672     }
673 
674     /**
675      * Returns a string representation of this dependency node.
676      * 
677      * @return the string representation
678      * @see #toString()
679      * @since 1.1
680      */
681     public String toNodeString()
682     {
683         StringBuffer buffer = new StringBuffer();
684 
685         boolean included = ( getState() == INCLUDED );
686 
687         if ( !included )
688         {
689             buffer.append( '(' );
690         }
691 
692         buffer.append( artifact );
693         
694         ItemAppender appender = new ItemAppender( buffer, included ? " (" : " - ", "; ", included ? ")" : "" );
695 
696         if ( getPremanagedVersion() != null )
697         {
698             appender.append( "version managed from ", getPremanagedVersion() );
699         }
700             
701         if ( getPremanagedScope() != null )
702         {
703             appender.append( "scope managed from ", getPremanagedScope() );
704         }
705         
706         if ( getOriginalScope() != null )
707         {
708             appender.append( "scope updated from ", getOriginalScope() );
709         }
710         
711         if ( getFailedUpdateScope() != null )
712         {
713             appender.append( "scope not updated to ", getFailedUpdateScope() );
714         }
715         
716         if ( getVersionSelectedFromRange() != null )
717         {
718             appender.append( "version selected from range ", getVersionSelectedFromRange().toString() );
719             appender.append( "available versions ", getAvailableVersions().toString() );
720         }
721         
722         switch ( state )
723         {
724             case INCLUDED:
725                 break;
726                 
727             case OMITTED_FOR_DUPLICATE:
728                 appender.append( "omitted for duplicate" );
729                 break;
730 
731             case OMITTED_FOR_CONFLICT:
732                 appender.append( "omitted for conflict with ", relatedArtifact.getVersion() );
733                 break;
734 
735             case OMITTED_FOR_CYCLE:
736                 appender.append( "omitted for cycle" );
737                 break;
738         }
739         
740         appender.flush();
741         
742         if ( !included )
743         {
744             buffer.append( ')' );
745         }
746 
747         return buffer.toString();
748     }
749     
750     /**
751      * Returns a string representation of this dependency node and its children, indented to the specified depth.
752      * 
753      * <p>
754      * As of 1.1, this method ignores the indentation depth and simply delegates to <code>toString()</code>.
755      * </p>
756      * 
757      * @param indentDepth
758      *            the indentation depth
759      * @return the string representation
760      * @deprecated As of 1.1, replaced by {@link #toString()}
761      */
762     public String toString( int indentDepth )
763     {
764         return toString();
765     }
766     
767     // Object methods ---------------------------------------------------------
768 
769     /**
770      * {@inheritDoc}
771      */
772     public int hashCode()
773     {
774         // TODO: probably better using commons-lang HashCodeBuilder
775         
776         int hashCode = 1;
777         
778         hashCode = hashCode * 31 + getArtifact().hashCode();
779         // DefaultArtifact.hashCode does not consider scope
780         hashCode = hashCode * 31 + nullHashCode( getArtifact().getScope() );
781 
782         // TODO: use parent's artifact to prevent recursion - how can we improve this?
783         hashCode = hashCode * 31 + nullHashCode( nullGetArtifact( getParent() ) );
784         
785         hashCode = hashCode * 31 + getChildren().hashCode();
786         hashCode = hashCode * 31 + getState();
787         hashCode = hashCode * 31 + nullHashCode( getRelatedArtifact() );
788         hashCode = hashCode * 31 + nullHashCode( getPremanagedVersion() );
789         hashCode = hashCode * 31 + nullHashCode( getPremanagedScope() );
790         hashCode = hashCode * 31 + nullHashCode( getOriginalScope() );
791         hashCode = hashCode * 31 + nullHashCode( getFailedUpdateScope() );
792         hashCode = hashCode * 31 + nullHashCode( getVersionSelectedFromRange() );
793         hashCode = hashCode * 31 + nullHashCode( getAvailableVersions() );
794 
795         return hashCode;
796     }
797 
798     @Override
799     public boolean equals( Object object )
800     {
801         // TODO: probably better using commons-lang EqualsBuilder
802         
803         boolean equal;
804 
805         if ( object instanceof DependencyNode )
806         {
807             DependencyNode node = (DependencyNode) object;
808 
809             equal = getArtifact().equals( node.getArtifact() );
810             // DefaultArtifact.hashCode does not consider scope
811             equal &= nullEquals( getArtifact().getScope(), node.getArtifact().getScope() );
812             
813             // TODO: use parent's artifact to prevent recursion - how can we improve this?
814             equal &= nullEquals( nullGetArtifact( getParent() ), nullGetArtifact( node.getParent() ) );
815             
816             equal &= getChildren().equals( node.getChildren() );
817             equal &= getState() == node.getState();
818             equal &= nullEquals( getRelatedArtifact(), node.getRelatedArtifact() );
819             equal &= nullEquals( getPremanagedVersion(), node.getPremanagedVersion() );
820             equal &= nullEquals( getPremanagedScope(), node.getPremanagedScope() );
821             equal &= nullEquals( getOriginalScope(), node.getOriginalScope() );
822             equal &= nullEquals( getFailedUpdateScope(), node.getFailedUpdateScope() );
823             equal &= nullEquals( getVersionSelectedFromRange(), node.getVersionSelectedFromRange() );
824             equal &= nullEquals( getAvailableVersions(), node.getAvailableVersions() );
825         }
826         else
827         {
828             equal = false;
829         }
830 
831         return equal;
832     }
833 
834     /**
835      * Returns a string representation of this dependency node and its children.
836      * 
837      * @return the string representation
838      * @see #toNodeString()
839      * @see java.lang.Object#toString()
840      */
841     public String toString()
842     {
843         StringWriter writer = new StringWriter();
844         accept( new SerializingDependencyNodeVisitor( writer ) );
845         return writer.toString();
846     }
847 
848     // private methods --------------------------------------------------------
849 
850     /**
851      * Removes all of this dependency node's children.
852      */
853     private void removeAllChildren()
854     {
855         for ( DependencyNode child : children )
856         {
857             child.parent = null;
858         }
859 
860         children.clear();
861     }
862 
863     /**
864      * Computes a hash-code for the specified object.
865      * 
866      * @param a
867      *            the object to compute a hash-code for, possibly <code>null</code>
868      * @return the computed hash-code
869      */
870     private int nullHashCode( Object a )
871     {
872         return ( a == null ) ? 0 : a.hashCode();
873     }
874 
875     /**
876      * Gets whether the specified objects are equal.
877      * 
878      * @param a
879      *            the first object to compare, possibly <code>null</code>
880      * @param b
881      *            the second object to compare, possibly <code>null</code>
882      * @return <code>true</code> if the specified objects are equal
883      */
884     private boolean nullEquals( Object a, Object b )
885     {
886         return ( a == null ? b == null : a.equals( b ) );
887     }
888     
889     /**
890      * Gets the artifact for the specified node.
891      * 
892      * @param node
893      *            the dependency node, possibly <code>null</code>
894      * @return the node's artifact, or <code>null</code> if the specified node was <code>null</code>
895      */
896     private static Artifact nullGetArtifact( DependencyNode node )
897     {
898         return ( node != null ) ? node.getArtifact() : null;
899     }
900 }