View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.dependency;
19  
20  import com.github.packageurl.MalformedPackageURLException;
21  import com.github.packageurl.PackageURL;
22  import com.google.common.collect.ImmutableSortedSet;
23  import org.apache.commons.lang3.builder.EqualsBuilder;
24  import org.apache.commons.lang3.builder.HashCodeBuilder;
25  import org.jspecify.annotations.NonNull;
26  import org.jspecify.annotations.Nullable;
27  import org.owasp.dependencycheck.data.nexus.MavenArtifact;
28  import org.owasp.dependencycheck.utils.Checksum;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import javax.annotation.concurrent.ThreadSafe;
33  import java.io.File;
34  import java.io.IOException;
35  import java.io.Serializable;
36  import java.security.NoSuchAlgorithmException;
37  import java.util.ArrayList;
38  import java.util.Collections;
39  import java.util.Comparator;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Optional;
43  import java.util.Set;
44  import java.util.SortedSet;
45  import java.util.TreeSet;
46  import org.apache.commons.lang3.StringUtils;
47  
48  import org.owasp.dependencycheck.analyzer.exception.UnexpectedAnalysisException;
49  import org.owasp.dependencycheck.dependency.naming.CpeIdentifier;
50  import org.owasp.dependencycheck.dependency.naming.Identifier;
51  import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
52  
53  /**
54   * A program dependency. This object is one of the core components within
55   * DependencyCheck. It is used to collect information about the dependency in
56   * the form of evidence. The Evidence is then used to determine if there are any
57   * known, published, vulnerabilities associated with the program dependency.
58   *
59   * @author Jeremy Long
60   */
61  @ThreadSafe
62  public class Dependency extends EvidenceCollection implements Serializable {
63  
64      /**
65       * The serial version UID for serialization.
66       */
67      private static final long serialVersionUID = 7388854637023297752L;
68      /**
69       * The logger.
70       */
71      private static final Logger LOGGER = LoggerFactory.getLogger(Dependency.class);
72      /**
73       * The MD5 hashing function.
74       */
75      private static final HashingFunction MD5_HASHING_FUNCTION = Checksum::getMD5Checksum;
76      /**
77       * The SHA1 hashing function.
78       */
79      private static final HashingFunction SHA1_HASHING_FUNCTION = Checksum::getSHA1Checksum;
80      /**
81       * The SHA256 hashing function.
82       */
83      private static final HashingFunction SHA256_HASHING_FUNCTION = Checksum::getSHA256Checksum;
84      /**
85       * A list of Identifiers.
86       */
87      private final Set<Identifier> softwareIdentifiers = new TreeSet<>();
88      /**
89       * A list of Identifiers.
90       */
91      private final Set<Identifier> vulnerableSoftwareIdentifiers = new TreeSet<>();
92      /**
93       * A set of identifiers that have been suppressed.
94       */
95      private final Set<Identifier> suppressedIdentifiers = new TreeSet<>();
96      /**
97       * A set of vulnerabilities that have been suppressed.
98       */
99      private final Set<Vulnerability> suppressedVulnerabilities = new HashSet<>();
100     /**
101      * A list of vulnerabilities for this dependency.
102      */
103     private final Set<Vulnerability> vulnerabilities = new HashSet<>();
104     /**
105      * A collection of related dependencies.
106      */
107     private final SortedSet<Dependency> relatedDependencies = new TreeSet<>(Dependency.NAME_COMPARATOR);
108     /**
109      * The set of dependencies that included this dependency (i.e., this is a
110      * transitive dependency because it was included by X).
111      */
112     private final Set<IncludedByReference> includedBy = new HashSet<>();
113     /**
114      * A list of projects that reference this dependency.
115      */
116     private final Set<String> projectReferences = new HashSet<>();
117     /**
118      * A list of available versions.
119      */
120     private final List<String> availableVersions = new ArrayList<>();
121     /**
122      * The actual file path of the dependency on disk.
123      */
124     private String actualFilePath;
125     /**
126      * The file path to display.
127      */
128     private String filePath;
129     /**
130      * The file name of the dependency.
131      */
132     private String fileName;
133     /**
134      * The package path.
135      */
136     private String packagePath;
137     /**
138      * The md5 hash of the dependency.
139      */
140     private String md5sum;
141     /**
142      * The SHA1 hash of the dependency.
143      */
144     private String sha1sum;
145     /**
146      * The SHA256 hash of the dependency.
147      */
148     private String sha256sum;
149     /**
150      * The file name to display in reports.
151      */
152     private String displayName = null;
153     /**
154      * The description of the JAR file.
155      */
156     private String description;
157     /**
158      * The license that this dependency uses.
159      */
160     private String license;
161     /**
162      * Defines an actual or virtual dependency.
163      */
164     private boolean isVirtual = false;
165 
166     /**
167      * Defines the human-recognizable name for the dependency
168      */
169     private String name;
170 
171     /**
172      * Defines the human-recognizable version for the dependency
173      */
174     private String version;
175 
176     /**
177      * A descriptor for the type of dependency based on which analyzer added it
178      * or collected evidence about it
179      */
180     private String ecosystem;
181 
182     /**
183      * Constructs a new Dependency object.
184      */
185     public Dependency() {
186         //empty constructor
187     }
188 
189     /**
190      * Constructs a new Dependency object.
191      *
192      * @param file the File to create the dependency object from.
193      */
194     public Dependency(File file) {
195         this(file, false);
196     }
197 
198     /**
199      * Constructs a new Dependency object.
200      *
201      * @param file the File to create the dependency object from.
202      * @param isVirtual specifies if the dependency is virtual indicating the
203      * file doesn't actually exist.
204      */
205     public Dependency(File file, boolean isVirtual) {
206         this();
207         this.isVirtual = isVirtual;
208         this.actualFilePath = file.getAbsolutePath();
209         this.filePath = this.actualFilePath;
210         this.fileName = file.getName();
211         this.packagePath = filePath;
212         if (!isVirtual && file.isFile()) {
213             calculateChecksums(file);
214         }
215     }
216 
217     /**
218      * Calculates the checksums for the given file.
219      *
220      * @param file the file used to calculate the checksums
221      */
222     private void calculateChecksums(File file) {
223         try {
224             this.md5sum = Checksum.getMD5Checksum(file);
225             this.sha1sum = Checksum.getSHA1Checksum(file);
226             this.sha256sum = Checksum.getSHA256Checksum(file);
227         } catch (NoSuchAlgorithmException | IOException ex) {
228             LOGGER.debug(String.format("Unable to calculate checksums on %s", file), ex);
229         }
230     }
231 
232     /**
233      * Constructs a new Dependency object.
234      *
235      * @param isVirtual specifies if the dependency is virtual indicating the
236      * file doesn't actually exist.
237      */
238     public Dependency(boolean isVirtual) {
239         this();
240         this.isVirtual = isVirtual;
241     }
242 
243     /**
244      * Returns the package path.
245      *
246      * @return the package path
247      */
248     public String getPackagePath() {
249         return packagePath;
250     }
251 
252     /**
253      * Sets the package path.
254      *
255      * @param packagePath the package path
256      */
257     public void setPackagePath(String packagePath) {
258         this.packagePath = packagePath;
259     }
260 
261     /**
262      * Returns the file name of the dependency.
263      *
264      * @return the file name of the dependency
265      */
266     public String getFileName() {
267         return this.fileName;
268     }
269 
270     /**
271      * Sets the file name of the dependency.
272      *
273      * @param fileName the file name of the dependency
274      */
275     public void setFileName(String fileName) {
276         this.fileName = fileName;
277     }
278 
279     /**
280      * Gets the file path of the dependency.
281      *
282      * @return the file path of the dependency
283      */
284     public String getActualFilePath() {
285         return this.actualFilePath;
286     }
287 
288     /**
289      * Sets the actual file path of the dependency on disk.
290      *
291      * @param actualFilePath the file path of the dependency
292      */
293     public void setActualFilePath(String actualFilePath) {
294         this.actualFilePath = actualFilePath;
295         this.sha1sum = null;
296         this.sha256sum = null;
297         this.md5sum = null;
298         final File file = getActualFile();
299         if (file.isFile()) {
300             calculateChecksums(this.getActualFile());
301         }
302     }
303 
304     /**
305      * Gets a reference to the File object.
306      *
307      * @return the File object
308      */
309     public File getActualFile() {
310         return new File(this.actualFilePath);
311     }
312 
313     /**
314      * Returns the file name to display in reports; if no display file name has
315      * been set it will default to constructing a name based on the name and
316      * version fields, otherwise it will return the actual file name.
317      *
318      * @return the file name to display
319      */
320     public String getDisplayFileName() {
321         if (displayName != null) {
322             return displayName;
323         }
324         if (!isVirtual) {
325             return fileName;
326         }
327         if (name == null) {
328             return fileName;
329         }
330         if (version == null) {
331             return name;
332         }
333         return name + ":" + version;
334     }
335 
336     /**
337      * Sets the file name to display in reports.
338      *
339      * @param displayName the name to display
340      */
341     public void setDisplayFileName(String displayName) {
342         this.displayName = displayName;
343     }
344 
345     /**
346      * <p>
347      * Gets the file path of the dependency.</p>
348      * <p>
349      * <b>NOTE:</b> This may not be the actual path of the file on disk. The
350      * actual path of the file on disk can be obtained via the
351      * getActualFilePath().</p>
352      *
353      * @return the file path of the dependency
354      */
355     public String getFilePath() {
356         return this.filePath;
357     }
358 
359     /**
360      * Sets the file path of the dependency.
361      *
362      * @param filePath the file path of the dependency
363      */
364     public void setFilePath(String filePath) {
365         this.filePath = filePath;
366     }
367 
368     /**
369      * Returns the MD5 Checksum of the dependency file.
370      *
371      * @return the MD5 Checksum
372      */
373     public String getMd5sum() {
374         if (md5sum == null) {
375             this.md5sum = determineHashes(MD5_HASHING_FUNCTION);
376         }
377 
378         return this.md5sum;
379     }
380 
381     /**
382      * Sets the MD5 Checksum of the dependency.
383      *
384      * @param md5sum the MD5 Checksum
385      */
386     public void setMd5sum(String md5sum) {
387         this.md5sum = md5sum;
388     }
389 
390     /**
391      * Returns the SHA1 Checksum of the dependency.
392      *
393      * @return the SHA1 Checksum
394      */
395     public String getSha1sum() {
396         if (sha1sum == null) {
397             this.sha1sum = determineHashes(SHA1_HASHING_FUNCTION);
398         }
399         return this.sha1sum;
400     }
401 
402     /**
403      * Sets the SHA1 Checksum of the dependency.
404      *
405      * @param sha1sum the SHA1 Checksum
406      */
407     public void setSha1sum(String sha1sum) {
408         this.sha1sum = sha1sum;
409     }
410 
411     /**
412      * Returns the SHA256 Checksum of the dependency.
413      *
414      * @return the SHA256 Checksum of the dependency
415      */
416     public String getSha256sum() {
417         if (sha256sum == null) {
418             this.sha256sum = determineHashes(SHA256_HASHING_FUNCTION);
419         }
420         return sha256sum;
421     }
422 
423     public void setSha256sum(String sha256sum) {
424         this.sha256sum = sha256sum;
425     }
426 
427     /**
428      * Returns an unmodifiable set of software identifiers.
429      *
430      * @return an unmodifiable set of software identifiers
431      */
432     public synchronized Set<Identifier> getSoftwareIdentifiers() {
433         return Collections.unmodifiableSet(softwareIdentifiers);
434     }
435 
436     /**
437      * Returns an unmodifiable set of vulnerability identifiers.
438      *
439      * @return an unmodifiable set of vulnerability identifiers
440      */
441     public synchronized Set<Identifier> getVulnerableSoftwareIdentifiers() {
442         return Collections.unmodifiableSet(this.vulnerableSoftwareIdentifiers);
443     }
444 
445     /**
446      * Returns the count of vulnerability identifiers.
447      *
448      * @return the count of vulnerability identifiers
449      */
450     public synchronized int getVulnerableSoftwareIdentifiersCount() {
451         return this.vulnerableSoftwareIdentifiers.size();
452     }
453 
454     /**
455      * Returns true if the dependency has a known exploited vulnerability.
456      *
457      * @return true if the dependency has a known exploited vulnerability;
458      * otherwise false.
459      */
460     public synchronized boolean hasKnownExploitedVulnerability() {
461         for (Vulnerability v : vulnerabilities) {
462             if (v.getKnownExploitedVulnerability() != null) {
463                 return true;
464             }
465         }
466         return false;
467     }
468 
469     /**
470      * Adds a set of Identifiers to the current list of software identifiers.
471      * Only used for testing.
472      *
473      * @param identifiers A set of Identifiers
474      */
475     protected synchronized void addSoftwareIdentifiers(Set<Identifier> identifiers) {
476         this.softwareIdentifiers.addAll(identifiers);
477     }
478 
479     /**
480      * Adds a set of Identifiers to the current list of vulnerable software
481      * identifiers. Only used for testing.
482      *
483      * @param identifiers A set of Identifiers
484      */
485     protected synchronized void addVulnerableSoftwareIdentifiers(Set<Identifier> identifiers) {
486         this.vulnerableSoftwareIdentifiers.addAll(identifiers);
487     }
488 
489     /**
490      * Adds an entry to the list of detected Identifiers for the dependency
491      * file.
492      *
493      * @param identifier a reference to the identifier to add
494      */
495     public synchronized void addSoftwareIdentifier(Identifier identifier) {
496         //todo the following assertion should be removed after initial testing and implementation
497         assert !(identifier instanceof CpeIdentifier) : "vulnerability identifier cannot be added to software identifiers";
498 
499         final Optional<Identifier> found = softwareIdentifiers.stream().filter(id
500                 -> id.getValue().equals(identifier.getValue())).findFirst();
501         if (found.isPresent()) {
502             //TODO - should we check for type of identifier?  I.e. could we see a Purl and GenericIdentifier with the same value
503             final Identifier existing = found.get();
504             if (existing.getConfidence().compareTo(identifier.getConfidence()) < 0) {
505                 existing.setConfidence(identifier.getConfidence());
506             }
507             if (existing.getNotes() != null && identifier.getNotes() != null) {
508                 existing.setNotes(existing.getNotes() + " " + identifier.getNotes());
509             } else if (identifier.getNotes() != null) {
510                 existing.setNotes(identifier.getNotes());
511             }
512             if (existing.getUrl() == null && identifier.getUrl() != null) {
513                 existing.setUrl(identifier.getUrl());
514             }
515         } else {
516             this.softwareIdentifiers.add(identifier);
517         }
518     }
519 
520     /**
521      * Adds an entry to the list of detected vulnerable software identifiers for
522      * the dependency file.
523      *
524      * @param identifier a reference to the identifier to add
525      */
526     public synchronized void addVulnerableSoftwareIdentifier(Identifier identifier) {
527         this.vulnerableSoftwareIdentifiers.add(identifier);
528     }
529 
530     /**
531      * Removes a vulnerable software identifier from the set of identifiers.
532      *
533      * @param i the identifier to remove
534      */
535     public synchronized void removeVulnerableSoftwareIdentifier(Identifier i) {
536         this.vulnerableSoftwareIdentifiers.remove(i);
537     }
538 
539     /**
540      * Adds the Maven artifact as evidence.
541      *
542      * @param source The source of the evidence
543      * @param mavenArtifact The Maven artifact
544      * @param confidence The confidence level of this evidence
545      */
546     public void addAsEvidence(String source, MavenArtifact mavenArtifact, Confidence confidence) {
547         if (mavenArtifact.getGroupId() != null && !mavenArtifact.getGroupId().isEmpty()) {
548             this.addEvidence(EvidenceType.VENDOR, source, "groupid", mavenArtifact.getGroupId(), confidence);
549         }
550         if (mavenArtifact.getArtifactId() != null && !mavenArtifact.getArtifactId().isEmpty()) {
551             this.addEvidence(EvidenceType.PRODUCT, source, "artifactid", mavenArtifact.getArtifactId(), confidence);
552             this.addEvidence(EvidenceType.VENDOR, source, "artifactid", mavenArtifact.getArtifactId(), confidence);
553         }
554         if (mavenArtifact.getVersion() != null && !mavenArtifact.getVersion().isEmpty()) {
555             this.addEvidence(EvidenceType.VERSION, source, "version", mavenArtifact.getVersion(), confidence);
556         }
557         boolean found = false;
558         if (mavenArtifact.getArtifactUrl() != null && !mavenArtifact.getArtifactUrl().isEmpty()) {
559             synchronized (this) {
560                 for (Identifier i : this.softwareIdentifiers) {
561                     if (i instanceof PurlIdentifier) {
562                         final PurlIdentifier id = (PurlIdentifier) i;
563                         if (mavenArtifact.getArtifactId().equals(id.getName())
564                                 && mavenArtifact.getGroupId().equals(id.getNamespace())) {
565                             found = true;
566                             i.setConfidence(Confidence.HIGHEST);
567                             final String url = "https://search.maven.org/search?q=1:" + this.getSha1sum();
568                             i.setUrl(url);
569                             //i.setUrl(mavenArtifact.getArtifactUrl());
570                             LOGGER.debug("Already found identifier {}. Confidence set to highest", i.getValue());
571                             break;
572                         }
573                     }
574                 }
575             }
576         }
577         if (!found && !StringUtils.isAnyEmpty(mavenArtifact.getGroupId(),
578                 mavenArtifact.getArtifactId(), mavenArtifact.getVersion())) {
579             try {
580                 LOGGER.debug("Adding new maven identifier {}", mavenArtifact);
581                 final PackageURL p = new PackageURL("maven", mavenArtifact.getGroupId(),
582                         mavenArtifact.getArtifactId(), mavenArtifact.getVersion(), null, null);
583                 final PurlIdentifier id = new PurlIdentifier(p, Confidence.HIGHEST);
584                 this.addSoftwareIdentifier(id);
585             } catch (MalformedPackageURLException ex) {
586                 throw new UnexpectedAnalysisException(ex);
587             }
588         }
589     }
590 
591     /**
592      * Get the unmodifiable set of suppressedIdentifiers.
593      *
594      * @return the value of suppressedIdentifiers
595      */
596     public synchronized Set<Identifier> getSuppressedIdentifiers() {
597         return Collections.unmodifiableSet(this.suppressedIdentifiers);
598     }
599 
600     /**
601      * Adds an identifier to the list of suppressed identifiers.
602      *
603      * @param identifier an identifier that was suppressed.
604      */
605     public synchronized void addSuppressedIdentifier(Identifier identifier) {
606         this.suppressedIdentifiers.add(identifier);
607     }
608 
609     /**
610      * Get the unmodifiable sorted set of vulnerabilities.
611      *
612      * @return the unmodifiable sorted set of vulnerabilities
613      */
614     public synchronized Set<Vulnerability> getVulnerabilities() {
615         return getVulnerabilities(false);
616     }
617 
618     /**
619      * Get the unmodifiable list of vulnerabilities; optionally sorted.
620      *
621      * @param sorted if true the list will be sorted
622      * @return the unmodifiable list set of vulnerabilities
623      */
624     public synchronized Set<Vulnerability> getVulnerabilities(boolean sorted) {
625         final Set<Vulnerability> vulnerabilitySet;
626         if (sorted) {
627             vulnerabilitySet = new TreeSet<>(vulnerabilities);
628         } else {
629             vulnerabilitySet = new HashSet<>(vulnerabilities);
630         }
631         return Collections.unmodifiableSet(vulnerabilitySet);
632     }
633 
634     /**
635      * Get vulnerability count.
636      *
637      * @return the count of vulnerabilities
638      */
639     public synchronized int getVulnerabilitiesCount() {
640         return vulnerabilities.size();
641     }
642 
643     /**
644      * Get an unmodifiable set of suppressedVulnerabilities.
645      *
646      * @return the unmodifiable sorted set of suppressedVulnerabilities
647      */
648     public synchronized Set<Vulnerability> getSuppressedVulnerabilities() {
649         return getSuppressedVulnerabilities(false);
650     }
651 
652     /**
653      * Get an unmodifiable, optionally sorted. set of suppressedVulnerabilities.
654      *
655      * @param sorted whether or not the set is sorted
656      * @return the unmodifiable sorted set of suppressedVulnerabilities
657      */
658     public synchronized Set<Vulnerability> getSuppressedVulnerabilities(boolean sorted) {
659         final Set<Vulnerability> vulnerabilitySet;
660         if (sorted) {
661             vulnerabilitySet = new TreeSet<>(suppressedVulnerabilities);
662         } else {
663             vulnerabilitySet = suppressedVulnerabilities;
664         }
665         return Collections.unmodifiableSet(vulnerabilitySet);
666     }
667 
668     /**
669      * Adds a vulnerability to the set of suppressed vulnerabilities.
670      *
671      * @param vulnerability the vulnerability that was suppressed
672      */
673     public synchronized void addSuppressedVulnerability(Vulnerability vulnerability) {
674         this.suppressedVulnerabilities.add(vulnerability);
675     }
676 
677     /**
678      * Get the value of description.
679      *
680      * @return the value of description
681      */
682     public String getDescription() {
683         return description;
684     }
685 
686     /**
687      * Set the value of description.
688      *
689      * @param description new value of description
690      */
691     public void setDescription(String description) {
692         this.description = description;
693     }
694 
695     /**
696      * Get the value of license.
697      *
698      * @return the value of license
699      */
700     public String getLicense() {
701         return license;
702     }
703 
704     /**
705      * Set the value of license.
706      *
707      * @param license new value of license
708      */
709     public void setLicense(String license) {
710         this.license = license;
711     }
712 
713     /**
714      * @return the name
715      */
716     public String getName() {
717         return name;
718     }
719 
720     /**
721      * @param name the name to set
722      */
723     public void setName(String name) {
724         this.name = name;
725     }
726 
727     /**
728      * Determines the SHA1 and MD5 sum for the given file.
729      *
730      * @param hashFunction the hashing function
731      * @return the checksum
732      */
733     private String determineHashes(HashingFunction hashFunction) {
734         if (isVirtual) {
735             return null;
736         }
737         try {
738             final File file = getActualFile();
739             return hashFunction.hash(file);
740         } catch (IOException | RuntimeException ex) {
741             LOGGER.warn("Unable to read '{}' to determine hashes.", actualFilePath);
742             LOGGER.debug("", ex);
743         } catch (NoSuchAlgorithmException ex) {
744             LOGGER.warn("Unable to use MD5 or SHA1 checksums.");
745             LOGGER.debug("", ex);
746         }
747         return null;
748     }
749 
750     /**
751      * Adds a vulnerability to the dependency.
752      *
753      * @param vulnerability a vulnerability
754      */
755     public synchronized void addVulnerability(Vulnerability vulnerability) {
756         this.vulnerabilities.add(vulnerability);
757     }
758 
759     /**
760      * Adds a list of vulnerabilities to the dependency.
761      *
762      * @param vulnerabilities a list of vulnerabilities
763      */
764     public synchronized void addVulnerabilities(List<Vulnerability> vulnerabilities) {
765         this.vulnerabilities.addAll(vulnerabilities);
766     }
767 
768     /**
769      * Removes the given vulnerability from the list.
770      *
771      * @param v the vulnerability to remove
772      */
773     public synchronized void removeVulnerability(Vulnerability v) {
774         this.vulnerabilities.remove(v);
775     }
776 
777     /**
778      * Get the unmodifiable set of {@link #relatedDependencies}. This field is
779      * used to collect other dependencies which really represent the same
780      * dependency, and may be presented as one item in reports.
781      *
782      * @return the unmodifiable set of relatedDependencies
783      */
784     public synchronized Set<Dependency> getRelatedDependencies() {
785         return Collections.unmodifiableSet(relatedDependencies);
786     }
787 
788     /**
789      * Clears the {@link #relatedDependencies}.
790      */
791     public synchronized void clearRelatedDependencies() {
792         relatedDependencies.clear();
793     }
794 
795     /**
796      * Get the unmodifiable set of includedBy (the list of parents of this
797      * transitive dependency).
798      *
799      * @return the unmodifiable set of includedBy
800      */
801     public synchronized Set<IncludedByReference> getIncludedBy() {
802         return Set.copyOf(includedBy);
803     }
804 
805     /**
806      * Get the unmodifiable set of includedBy (the list of parents of this
807      * transitive dependency), sorted by natural comparator.
808      *
809      * @return the unmodifiable set of includedBy, sorted
810      */
811     public synchronized SortedSet<IncludedByReference> getIncludedBySorted() {
812         return ImmutableSortedSet.copyOf(includedBy);
813     }
814 
815     /**
816      * Adds the parent or root of the transitive dependency chain (i.e., this
817      * was included by the parent dependency X).
818      *
819      * @param includedBy a project reference
820      */
821     public synchronized void addIncludedBy(@NonNull String includedBy) {
822         this.includedBy.add(new IncludedByReference(includedBy, null));
823     }
824 
825     /**
826      * Adds the parent or root of the transitive dependency chain (i.e., this
827      * was included by the parent dependency X).
828      *
829      * @param includedBy a project reference
830      * @param type the type of project reference (i.e. 'plugins', 'buildEnv')
831      */
832     public synchronized void addIncludedBy(@NonNull String includedBy, @Nullable String type) {
833         this.includedBy.add(new IncludedByReference(includedBy, type));
834     }
835 
836     /**
837      * Adds a set of project references.
838      *
839      * @param includedBy a set of project references
840      */
841     public synchronized void addAllIncludedBy(@NonNull Set<IncludedByReference> includedBy) {
842         this.includedBy.addAll(includedBy);
843     }
844 
845     /**
846      * Get the unmodifiable set of projectReferences.
847      *
848      * @return the unmodifiable set of projectReferences
849      */
850     public synchronized Set<String> getProjectReferences() {
851         return Set.copyOf(projectReferences);
852     }
853 
854     /**
855      * Get the unmodifiable set of projectReferences, sorted by natural comparator.
856      *
857      * @return the unmodifiable set of projectReferences, sorted
858      */
859     public synchronized SortedSet<String> getProjectReferencesSorted() {
860         return ImmutableSortedSet.copyOf(projectReferences);
861     }
862 
863     /**
864      * Adds a project reference.
865      *
866      * @param projectReference a project reference
867      */
868     public synchronized void addProjectReference(@NonNull String projectReference) {
869         this.projectReferences.add(projectReference);
870     }
871 
872     /**
873      * Add a collection of project reference.
874      *
875      * @param projectReferences a set of project references
876      */
877     public synchronized void addAllProjectReferences(@NonNull Set<String> projectReferences) {
878         this.projectReferences.addAll(projectReferences);
879     }
880 
881     /**
882      * Adds a related dependency.
883      *
884      * @param dependency a reference to the related dependency
885      */
886     @SuppressWarnings("ReferenceEquality")
887     public synchronized void addRelatedDependency(Dependency dependency) {
888         if (this == dependency) {
889             LOGGER.warn("Attempted to add a circular reference - please post the log file to issue #172 here "
890                     + "https://github.com/dependency-check/DependencyCheck/issues/172");
891             LOGGER.debug("this: {}", this);
892             LOGGER.debug("dependency: {}", dependency);
893         } else if (NAME_COMPARATOR.compare(this, dependency) == 0) {
894             LOGGER.debug("Attempted to add the same dependency as this, likely due to merging identical dependencies "
895                     + "obtained from different modules");
896             LOGGER.debug("this: {}", this);
897             LOGGER.debug("dependency: {}", dependency);
898         } else if (!relatedDependencies.add(dependency)) {
899             LOGGER.debug("Failed to add dependency, likely due to referencing the same file as another dependency in the set.");
900             LOGGER.debug("this: {}", this);
901             LOGGER.debug("dependency: {}", dependency);
902         }
903     }
904 
905     /**
906      * Removes a related dependency.
907      *
908      * @param dependency the dependency to remove
909      */
910     public synchronized void removeRelatedDependencies(Dependency dependency) {
911         this.relatedDependencies.remove(dependency);
912     }
913 
914     /**
915      * Get the value of availableVersions.
916      *
917      * @return the value of availableVersions
918      */
919     public synchronized List<String> getAvailableVersions() {
920         return List.copyOf(availableVersions);
921     }
922 
923     /**
924      * Adds a version to the available version list.
925      *
926      * @param version the version to add to the list
927      */
928     public synchronized void addAvailableVersion(@NonNull String version) {
929         this.availableVersions.add(version);
930     }
931 
932     /**
933      * Returns whether or not this dependency is virtual or not. Virtual
934      * dependencies are specified during object constructor. No setter.
935      *
936      * @return true if Dependency is virtual, false if not
937      */
938     public boolean isVirtual() {
939         return isVirtual;
940     }
941 
942     /**
943      * Implementation of the equals method.
944      *
945      * @param obj the object to compare
946      * @return true if the objects are equal, otherwise false
947      */
948     @Override
949     public boolean equals(Object obj) {
950         if (!(obj instanceof Dependency)) {
951             return false;
952         }
953         if (this == obj) {
954             return true;
955         }
956         final Dependency other = (Dependency) obj;
957         return new EqualsBuilder()
958                 .appendSuper(super.equals(obj))
959                 .append(this.actualFilePath, other.actualFilePath)
960                 .append(this.filePath, other.filePath)
961                 .append(this.fileName, other.fileName)
962                 .append(this.packagePath, other.packagePath)
963                 .append(this.md5sum, other.md5sum)
964                 .append(this.sha1sum, other.sha1sum)
965                 .append(this.sha256sum, other.sha256sum)
966                 .append(this.softwareIdentifiers, other.softwareIdentifiers)
967                 .append(this.vulnerableSoftwareIdentifiers, other.vulnerableSoftwareIdentifiers)
968                 .append(this.suppressedIdentifiers, other.suppressedIdentifiers)
969                 .append(this.description, other.description)
970                 .append(this.license, other.license)
971                 .append(this.vulnerabilities, other.vulnerabilities)
972                 .append(this.projectReferences, other.projectReferences)
973                 .append(this.availableVersions, other.availableVersions)
974                 .append(this.version, other.version)
975                 .append(this.ecosystem, other.ecosystem)
976                 .isEquals();
977     }
978 
979     /**
980      * Generates the HashCode.
981      *
982      * @return the HashCode
983      */
984     @Override
985     public int hashCode() {
986         return new HashCodeBuilder(3, 47)
987                 .appendSuper(super.hashCode())
988                 .append(actualFilePath)
989                 .append(filePath)
990                 .append(fileName)
991                 .append(packagePath)
992                 .append(md5sum)
993                 .append(sha1sum)
994                 .append(sha256sum)
995                 .append(softwareIdentifiers)
996                 .append(vulnerableSoftwareIdentifiers)
997                 .append(suppressedIdentifiers)
998                 .append(description)
999                 .append(license)
1000                 .append(vulnerabilities)
1001                 .append(projectReferences)
1002                 .append(availableVersions)
1003                 .append(version)
1004                 .append(ecosystem)
1005                 .toHashCode();
1006     }
1007 
1008     /**
1009      * Standard toString() implementation showing the filename, actualFilePath,
1010      * and filePath.
1011      *
1012      * @return the string representation of the file
1013      */
1014     @Override
1015     public synchronized String toString() {
1016         return "Dependency{ fileName='" + fileName + "', actualFilePath='" + actualFilePath
1017                 + "', filePath='" + filePath + "', packagePath='" + packagePath + "'}";
1018     }
1019 
1020     /**
1021      * Add a list of suppressed vulnerabilities to the collection.
1022      *
1023      * @param vulns the list of suppressed vulnerabilities to add
1024      */
1025     public synchronized void addSuppressedVulnerabilities(List<Vulnerability> vulns) {
1026         this.suppressedVulnerabilities.addAll(vulns);
1027     }
1028 
1029     /**
1030      * @return the version
1031      */
1032     public String getVersion() {
1033         return version;
1034     }
1035 
1036     /**
1037      * @param version the version to set
1038      */
1039     public void setVersion(String version) {
1040         this.version = version;
1041     }
1042 
1043     /**
1044      * @return the ecosystem
1045      */
1046     public String getEcosystem() {
1047         return ecosystem;
1048     }
1049 
1050     /**
1051      * @param ecosystem the ecosystem to set
1052      */
1053     public void setEcosystem(String ecosystem) {
1054         this.ecosystem = ecosystem;
1055     }
1056 
1057     //CSOFF: OperatorWrap
1058     /**
1059      * Simple sorting by display file name and actual file path.
1060      */
1061     public static final Comparator<Dependency> NAME_COMPARATOR
1062             = Comparator.comparing((Dependency d) -> (d.getDisplayFileName() + d.getFilePath()));
1063 
1064     //CSON: OperatorWrap
1065     /**
1066      * A hashing function shortcut.
1067      */
1068     interface HashingFunction {
1069 
1070         /**
1071          * Calculates the checksum for the given file.
1072          *
1073          * @param file the source for the checksum
1074          * @return the string representation of the checksum
1075          * @throws IOException thrown if there is an I/O error
1076          * @throws NoSuchAlgorithmException thrown if the algorithm is not found
1077          */
1078         String hash(File file) throws IOException, NoSuchAlgorithmException;
1079     }
1080 }