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