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