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