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} < {@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 }