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;
19  
20  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
21  import org.apache.commons.jcs3.JCS;
22  import org.jetbrains.annotations.NotNull;
23  import org.jetbrains.annotations.Nullable;
24  import org.owasp.dependencycheck.analyzer.AnalysisPhase;
25  import org.owasp.dependencycheck.analyzer.Analyzer;
26  import org.owasp.dependencycheck.analyzer.AnalyzerService;
27  import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer;
28  import org.owasp.dependencycheck.data.nvdcve.DatabaseManager;
29  import org.owasp.dependencycheck.data.nvdcve.CveDB;
30  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
31  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
32  import org.owasp.dependencycheck.data.update.CachedWebDataSource;
33  import org.owasp.dependencycheck.data.update.UpdateService;
34  import org.owasp.dependencycheck.data.update.exception.UpdateException;
35  import org.owasp.dependencycheck.dependency.Dependency;
36  import org.owasp.dependencycheck.exception.ExceptionCollection;
37  import org.owasp.dependencycheck.exception.InitializationException;
38  import org.owasp.dependencycheck.exception.NoDataException;
39  import org.owasp.dependencycheck.exception.ReportException;
40  import org.owasp.dependencycheck.exception.WriteLockException;
41  import org.owasp.dependencycheck.reporting.ReportGenerator;
42  import org.owasp.dependencycheck.utils.FileUtils;
43  import org.owasp.dependencycheck.utils.Settings;
44  import org.owasp.dependencycheck.utils.WriteLock;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import javax.annotation.concurrent.NotThreadSafe;
49  import java.io.File;
50  import java.io.FileFilter;
51  import java.io.IOException;
52  import java.nio.file.Files;
53  import java.util.ArrayList;
54  import java.util.Arrays;
55  import java.util.Collection;
56  import java.util.Collections;
57  import java.util.EnumMap;
58  import java.util.HashMap;
59  import java.util.HashSet;
60  import java.util.Iterator;
61  import java.util.List;
62  import java.util.Map;
63  import java.util.Objects;
64  import java.util.Set;
65  import java.util.concurrent.CancellationException;
66  import java.util.concurrent.ExecutionException;
67  import java.util.concurrent.ExecutorService;
68  import java.util.concurrent.Executors;
69  import java.util.concurrent.Future;
70  import java.util.concurrent.TimeUnit;
71  
72  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINAL;
73  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINDING_ANALYSIS;
74  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINDING_ANALYSIS_PHASE2;
75  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.IDENTIFIER_ANALYSIS;
76  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INFORMATION_COLLECTION;
77  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INFORMATION_COLLECTION2;
78  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INITIAL;
79  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_FINDING_ANALYSIS;
80  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_IDENTIFIER_ANALYSIS;
81  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION1;
82  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION2;
83  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION3;
84  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_FINDING_ANALYSIS;
85  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_IDENTIFIER_ANALYSIS;
86  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_INFORMATION_COLLECTION;
87  import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer;
88  import org.owasp.dependencycheck.dependency.naming.Identifier;
89  
90  /**
91   * Scans files, directories, etc. for Dependencies. Analyzers are loaded and
92   * used to process the files found by the scan, if a file is encountered and an
93   * Analyzer is associated with the file type then the file is turned into a
94   * dependency.
95   *
96   * @author Jeremy Long
97   */
98  @NotThreadSafe
99  public class Engine implements FileFilter, AutoCloseable {
100 
101     /**
102      * The Logger for use throughout the class.
103      */
104     private static final Logger LOGGER = LoggerFactory.getLogger(Engine.class);
105     /**
106      * The list of dependencies.
107      */
108     private final List<Dependency> dependencies = Collections.synchronizedList(new ArrayList<>());
109     /**
110      * A Map of analyzers grouped by Analysis phase.
111      */
112     private final Map<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<>(AnalysisPhase.class);
113     /**
114      * A Map of analyzers grouped by Analysis phase.
115      */
116     private final Set<FileTypeAnalyzer> fileTypeAnalyzers = new HashSet<>();
117     /**
118      * The engine execution mode indicating it will either collect evidence or
119      * process evidence or both.
120      */
121     private final Mode mode;
122     /**
123      * The ClassLoader to use when dynamically loading Analyzer and Update
124      * services.
125      */
126     private final ClassLoader serviceClassLoader;
127     /**
128      * The configured settings.
129      */
130     private final Settings settings;
131     /**
132      * A storage location to persist objects throughout the execution of ODC.
133      */
134     private final Map<String, Object> objects = new HashMap<>();
135     /**
136      * The external view of the dependency list.
137      */
138     private Dependency[] dependenciesExternalView = null;
139     /**
140      * A reference to the database.
141      */
142     private CveDB database = null;
143     /**
144      * Used to store the value of
145      * System.getProperty("javax.xml.accessExternalSchema") - ODC may change the
146      * value of this system property at runtime. We store the value to reset the
147      * property to its original value.
148      */
149     private final String accessExternalSchema;
150 
151     /**
152      * Creates a new {@link Mode#STANDALONE} Engine.
153      *
154      * @param settings reference to the configured settings
155      */
156     public Engine(@NotNull final Settings settings) {
157         this(Mode.STANDALONE, settings);
158     }
159 
160     /**
161      * Creates a new Engine.
162      *
163      * @param mode the mode of operation
164      * @param settings reference to the configured settings
165      */
166     public Engine(@NotNull final Mode mode, @NotNull final Settings settings) {
167         this(Thread.currentThread().getContextClassLoader(), mode, settings);
168     }
169 
170     /**
171      * Creates a new {@link Mode#STANDALONE} Engine.
172      *
173      * @param serviceClassLoader a reference the class loader being used
174      * @param settings reference to the configured settings
175      */
176     public Engine(@NotNull final ClassLoader serviceClassLoader, @NotNull final Settings settings) {
177         this(serviceClassLoader, Mode.STANDALONE, settings);
178     }
179 
180     /**
181      * Creates a new Engine.
182      *
183      * @param serviceClassLoader a reference the class loader being used
184      * @param mode the mode of the engine
185      * @param settings reference to the configured settings
186      */
187     public Engine(@NotNull final ClassLoader serviceClassLoader, @NotNull final Mode mode, @NotNull final Settings settings) {
188         this.settings = settings;
189         this.serviceClassLoader = serviceClassLoader;
190         this.mode = mode;
191         this.accessExternalSchema = System.getProperty("javax.xml.accessExternalSchema");
192 
193         initializeEngine();
194     }
195 
196     /**
197      * Creates a new Engine using the specified classloader to dynamically load
198      * Analyzer and Update services.
199      *
200      * @throws DatabaseException thrown if there is an error connecting to the
201      * database
202      */
203     protected final void initializeEngine() {
204         loadAnalyzers();
205     }
206 
207     /**
208      * Properly cleans up resources allocated during analysis.
209      */
210     @Override
211     public void close() {
212         if (mode.isDatabaseRequired()) {
213             if (database != null) {
214                 database.close();
215                 database = null;
216             }
217         }
218         if (accessExternalSchema != null) {
219             System.setProperty("javax.xml.accessExternalSchema", accessExternalSchema);
220         } else {
221             System.clearProperty("javax.xml.accessExternalSchema");
222         }
223         JCS.shutdown();
224     }
225 
226     /**
227      * Loads the analyzers specified in the configuration file (or system
228      * properties).
229      */
230     private void loadAnalyzers() {
231         if (!analyzers.isEmpty()) {
232             return;
233         }
234         mode.getPhases().forEach((phase) -> analyzers.put(phase, new ArrayList<>()));
235         final AnalyzerService service = new AnalyzerService(serviceClassLoader, settings);
236         final List<Analyzer> iterator = service.getAnalyzers(mode.getPhases());
237         iterator.forEach((a) -> {
238             a.initialize(this.settings);
239             analyzers.get(a.getAnalysisPhase()).add(a);
240             if (a instanceof FileTypeAnalyzer) {
241                 this.fileTypeAnalyzers.add((FileTypeAnalyzer) a);
242             }
243         });
244     }
245 
246     /**
247      * Get the List of the analyzers for a specific phase of analysis.
248      *
249      * @param phase the phase to get the configured analyzers.
250      * @return the analyzers loaded
251      */
252     public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
253         return analyzers.get(phase);
254     }
255 
256     /**
257      * Adds a dependency. In some cases, when adding a virtual dependency, the
258      * method will identify if the virtual dependency was previously added and
259      * update the existing dependency rather then adding a duplicate.
260      *
261      * @param dependency the dependency to add
262      */
263     public synchronized void addDependency(Dependency dependency) {
264         if (dependency.isVirtual()) {
265             for (Dependency existing : dependencies) {
266                 if (existing.isVirtual()
267                         && existing.getSha256sum() != null
268                         && existing.getSha256sum().equals(dependency.getSha256sum())
269                         && existing.getDisplayFileName() != null
270                         && existing.getDisplayFileName().equals(dependency.getDisplayFileName())
271                         && identifiersMatch(existing.getSoftwareIdentifiers(), dependency.getSoftwareIdentifiers())) {
272                     DependencyBundlingAnalyzer.mergeDependencies(existing, dependency, null);
273                     return;
274                 }
275             }
276         }
277         dependencies.add(dependency);
278         dependenciesExternalView = null;
279     }
280 
281     /**
282      * Sorts the dependency list.
283      */
284     public synchronized void sortDependencies() {
285         //TODO - is this actually necassary????
286 //        Collections.sort(dependencies);
287 //        dependenciesExternalView = null;
288     }
289 
290     /**
291      * Removes the dependency.
292      *
293      * @param dependency the dependency to remove.
294      */
295     public synchronized void removeDependency(@NotNull final Dependency dependency) {
296         dependencies.remove(dependency);
297         dependenciesExternalView = null;
298     }
299 
300     /**
301      * Returns a copy of the dependencies as an array.
302      *
303      * @return the dependencies identified
304      */
305     @SuppressFBWarnings(justification = "This is the intended external view of the dependencies", value = {"EI_EXPOSE_REP"})
306     public synchronized Dependency[] getDependencies() {
307         if (dependenciesExternalView == null) {
308             dependenciesExternalView = dependencies.toArray(new Dependency[0]);
309         }
310         return dependenciesExternalView;
311     }
312 
313     /**
314      * Sets the dependencies.
315      *
316      * @param dependencies the dependencies
317      */
318     public synchronized void setDependencies(@NotNull final List<Dependency> dependencies) {
319         this.dependencies.clear();
320         this.dependencies.addAll(dependencies);
321         dependenciesExternalView = null;
322     }
323 
324     /**
325      * Scans an array of files or directories. If a directory is specified, it
326      * will be scanned recursively. Any dependencies identified are added to the
327      * dependency collection.
328      *
329      * @param paths an array of paths to files or directories to be analyzed
330      * @return the list of dependencies scanned
331      * @since v0.3.2.5
332      */
333     public List<Dependency> scan(@NotNull final String[] paths) {
334         return scan(paths, null);
335     }
336 
337     /**
338      * Scans an array of files or directories. If a directory is specified, it
339      * will be scanned recursively. Any dependencies identified are added to the
340      * dependency collection.
341      *
342      * @param paths an array of paths to files or directories to be analyzed
343      * @param projectReference the name of the project or scope in which the
344      * dependency was identified
345      * @return the list of dependencies scanned
346      * @since v1.4.4
347      */
348     public List<Dependency> scan(@NotNull final String[] paths, @Nullable final String projectReference) {
349         final List<Dependency> deps = new ArrayList<>();
350         for (String path : paths) {
351             final List<Dependency> d = scan(path, projectReference);
352             if (d != null) {
353                 deps.addAll(d);
354             }
355         }
356         return deps;
357     }
358 
359     /**
360      * Scans a given file or directory. If a directory is specified, it will be
361      * scanned recursively. Any dependencies identified are added to the
362      * dependency collection.
363      *
364      * @param path the path to a file or directory to be analyzed
365      * @return the list of dependencies scanned
366      */
367     public List<Dependency> scan(@NotNull final String path) {
368         return scan(path, null);
369     }
370 
371     /**
372      * Scans a given file or directory. If a directory is specified, it will be
373      * scanned recursively. Any dependencies identified are added to the
374      * dependency collection.
375      *
376      * @param path the path to a file or directory to be analyzed
377      * @param projectReference the name of the project or scope in which the
378      * dependency was identified
379      * @return the list of dependencies scanned
380      * @since v1.4.4
381      */
382     public List<Dependency> scan(@NotNull final String path, String projectReference) {
383         final File file = new File(path);
384         return scan(file, projectReference);
385     }
386 
387     /**
388      * Scans an array of files or directories. If a directory is specified, it
389      * will be scanned recursively. Any dependencies identified are added to the
390      * dependency collection.
391      *
392      * @param files an array of paths to files or directories to be analyzed.
393      * @return the list of dependencies
394      * @since v0.3.2.5
395      */
396     public List<Dependency> scan(File[] files) {
397         return scan(files, null);
398     }
399 
400     /**
401      * Scans an array of files or directories. If a directory is specified, it
402      * will be scanned recursively. Any dependencies identified are added to the
403      * dependency collection.
404      *
405      * @param files an array of paths to files or directories to be analyzed.
406      * @param projectReference the name of the project or scope in which the
407      * dependency was identified
408      * @return the list of dependencies
409      * @since v1.4.4
410      */
411     public List<Dependency> scan(File[] files, String projectReference) {
412         final List<Dependency> deps = new ArrayList<>();
413         for (File file : files) {
414             final List<Dependency> d = scan(file, projectReference);
415             if (d != null) {
416                 deps.addAll(d);
417             }
418         }
419         return deps;
420     }
421 
422     /**
423      * Scans a collection of files or directories. If a directory is specified,
424      * it will be scanned recursively. Any dependencies identified are added to
425      * the dependency collection.
426      *
427      * @param files a set of paths to files or directories to be analyzed
428      * @return the list of dependencies scanned
429      * @since v0.3.2.5
430      */
431     public List<Dependency> scan(Collection<File> files) {
432         return scan(files, null);
433     }
434 
435     /**
436      * Scans a collection of files or directories. If a directory is specified,
437      * it will be scanned recursively. Any dependencies identified are added to
438      * the dependency collection.
439      *
440      * @param files a set of paths to files or directories to be analyzed
441      * @param projectReference the name of the project or scope in which the
442      * dependency was identified
443      * @return the list of dependencies scanned
444      * @since v1.4.4
445      */
446     public List<Dependency> scan(Collection<File> files, String projectReference) {
447         final List<Dependency> deps = new ArrayList<>();
448         files.stream().map((file) -> scan(file, projectReference))
449                 .filter(Objects::nonNull)
450                 .forEach(deps::addAll);
451         return deps;
452     }
453 
454     /**
455      * Scans a given file or directory. If a directory is specified, it will be
456      * scanned recursively. Any dependencies identified are added to the
457      * dependency collection.
458      *
459      * @param file the path to a file or directory to be analyzed
460      * @return the list of dependencies scanned
461      * @since v0.3.2.4
462      */
463     public List<Dependency> scan(File file) {
464         return scan(file, null);
465     }
466 
467     /**
468      * Scans a given file or directory. If a directory is specified, it will be
469      * scanned recursively. Any dependencies identified are added to the
470      * dependency collection.
471      *
472      * @param file the path to a file or directory to be analyzed
473      * @param projectReference the name of the project or scope in which the
474      * dependency was identified
475      * @return the list of dependencies scanned
476      * @since v1.4.4
477      */
478     @Nullable
479     public List<Dependency> scan(@NotNull final File file, String projectReference) {
480         if (file.exists()) {
481             if (file.isDirectory()) {
482                 return scanDirectory(file, projectReference);
483             } else {
484                 final Dependency d = scanFile(file, projectReference);
485                 if (d != null) {
486                     final List<Dependency> deps = new ArrayList<>();
487                     deps.add(d);
488                     return deps;
489                 }
490             }
491         }
492         return null;
493     }
494 
495     /**
496      * Recursively scans files and directories. Any dependencies identified are
497      * added to the dependency collection.
498      *
499      * @param dir the directory to scan
500      * @return the list of Dependency objects scanned
501      */
502     protected List<Dependency> scanDirectory(File dir) {
503         return scanDirectory(dir, null);
504     }
505 
506     /**
507      * Recursively scans files and directories. Any dependencies identified are
508      * added to the dependency collection.
509      *
510      * @param dir the directory to scan
511      * @param projectReference the name of the project or scope in which the
512      * dependency was identified
513      * @return the list of Dependency objects scanned
514      * @since v1.4.4
515      */
516     protected List<Dependency> scanDirectory(@NotNull final File dir, @Nullable final String projectReference) {
517         final File[] files = dir.listFiles();
518         final List<Dependency> deps = new ArrayList<>();
519         if (files != null) {
520             for (File f : files) {
521                 if (f.isDirectory()) {
522                     final List<Dependency> d = scanDirectory(f, projectReference);
523                     if (d != null) {
524                         deps.addAll(d);
525                     }
526                 } else {
527                     final Dependency d = scanFile(f, projectReference);
528                     if (d != null) {
529                         deps.add(d);
530                     }
531                 }
532             }
533         }
534         return deps;
535     }
536 
537     /**
538      * Scans a specified file. If a dependency is identified it is added to the
539      * dependency collection.
540      *
541      * @param file The file to scan
542      * @return the scanned dependency
543      */
544     protected Dependency scanFile(@NotNull final File file) {
545         return scanFile(file, null);
546     }
547 
548     //CSOFF: NestedIfDepth
549     /**
550      * Scans a specified file. If a dependency is identified it is added to the
551      * dependency collection.
552      *
553      * @param file The file to scan
554      * @param projectReference the name of the project or scope in which the
555      * dependency was identified
556      * @return the scanned dependency
557      * @since v1.4.4
558      */
559     protected synchronized Dependency scanFile(@NotNull final File file, @Nullable final String projectReference) {
560         Dependency dependency = null;
561         if (file.isFile()) {
562             if (accept(file)) {
563                 dependency = new Dependency(file);
564                 if (projectReference != null) {
565                     dependency.addProjectReference(projectReference);
566                 }
567                 final String sha1 = dependency.getSha1sum();
568                 boolean found = false;
569 
570                 if (sha1 != null) {
571                     for (Dependency existing : dependencies) {
572                         if (sha1.equals(existing.getSha1sum())) {
573                             if (existing.getDisplayFileName().contains(": ")
574                                     || dependency.getDisplayFileName().contains(": ")
575                                     || dependency.getActualFilePath().contains("dctemp")) {
576                                 continue;
577                             }
578                             found = true;
579                             if (projectReference != null) {
580                                 existing.addProjectReference(projectReference);
581                             }
582                             if (existing.getActualFilePath() != null && dependency.getActualFilePath() != null
583                                     && !existing.getActualFilePath().equals(dependency.getActualFilePath())) {
584 
585                                 if (DependencyBundlingAnalyzer.firstPathIsShortest(existing.getFilePath(), dependency.getFilePath())) {
586                                     DependencyBundlingAnalyzer.mergeDependencies(existing, dependency, null);
587 
588                                     //return null;
589                                     return existing;
590                                 } else {
591                                     //Merging dependency<-existing could be complicated. Instead analyze them seperately
592                                     //and possibly merge them at the end.
593                                     found = false;
594                                 }
595 
596                             } else { //somehow we scanned the same file twice?
597                                 //return null;
598                                 return existing;
599                             }
600                             break;
601                         }
602                     }
603                 }
604                 if (!found) {
605                     dependencies.add(dependency);
606                     dependenciesExternalView = null;
607                 }
608             }
609         } else {
610             LOGGER.debug("Path passed to scanFile(File) is not a file that can be scanned by dependency-check: {}. Skipping the file.", file);
611         }
612         return dependency;
613     }
614     //CSON: NestedIfDepth
615 
616     /**
617      * Runs the analyzers against all of the dependencies. Since the mutable
618      * dependencies list is exposed via {@link #getDependencies()}, this method
619      * iterates over a copy of the dependencies list. Thus, the potential for
620      * {@link java.util.ConcurrentModificationException}s is avoided, and
621      * analyzers may safely add or remove entries from the dependencies list.
622      * <p>
623      * Every effort is made to complete analysis on the dependencies. In some
624      * cases an exception will occur with part of the analysis being performed
625      * which may not affect the entire analysis. If an exception occurs it will
626      * be included in the thrown exception collection.
627      *
628      * @throws ExceptionCollection a collections of any exceptions that occurred
629      * during analysis
630      */
631     public void analyzeDependencies() throws ExceptionCollection {
632         final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>());
633 
634         initializeAndUpdateDatabase(exceptions);
635 
636         //need to ensure that data exists
637         try {
638             ensureDataExists();
639         } catch (NoDataException ex) {
640             throwFatalExceptionCollection("Unable to continue dependency-check analysis.", ex, exceptions);
641         }
642         LOGGER.info("\n\nDependency-Check is an open source tool performing a best effort analysis of 3rd party dependencies; false positives and "
643                 + "false negatives may exist in the analysis performed by the tool. Use of the tool and the reporting provided constitutes "
644                 + "acceptance for use in an AS IS condition, and there are NO warranties, implied or otherwise, with regard to the analysis "
645                 + "or its use. Any use of the tool and the reporting provided is at the user's risk. In no event shall the copyright holder "
646                 + "or OWASP be held liable for any damages whatsoever arising out of or in connection with the use of this tool, the analysis "
647                 + "performed, or the resulting report.\n\n\n"
648                 + "   About ODC: https://jeremylong.github.io/DependencyCheck/general/internals.html\n"
649                 + "   False Positives: https://jeremylong.github.io/DependencyCheck/general/suppression.html\n"
650                 + "\n"
651                 + "💖 Sponsor: https://github.com/sponsors/jeremylong\n\n");
652         LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------");
653         LOGGER.info("Analysis Started");
654         final long analysisStart = System.currentTimeMillis();
655 
656         // analysis phases
657         for (AnalysisPhase phase : mode.getPhases()) {
658             final List<Analyzer> analyzerList = analyzers.get(phase);
659 
660             for (final Analyzer analyzer : analyzerList) {
661                 final long analyzerStart = System.currentTimeMillis();
662                 try {
663                     initializeAnalyzer(analyzer);
664                 } catch (InitializationException ex) {
665                     exceptions.add(ex);
666                     if (ex.isFatal()) {
667                         continue;
668                     }
669                 }
670 
671                 if (analyzer.isEnabled()) {
672                     executeAnalysisTasks(analyzer, exceptions);
673 
674                     final long analyzerDurationMillis = System.currentTimeMillis() - analyzerStart;
675                     final long analyzerDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(analyzerDurationMillis);
676                     LOGGER.info("Finished {} ({} seconds)", analyzer.getName(), analyzerDurationSeconds);
677                 } else {
678                     LOGGER.debug("Skipping {} (not enabled)", analyzer.getName());
679                 }
680             }
681         }
682         mode.getPhases().stream()
683                 .map(analyzers::get)
684                 .forEach((analyzerList) -> analyzerList.forEach(this::closeAnalyzer));
685 
686         LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------");
687         final long analysisDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - analysisStart);
688         LOGGER.info("Analysis Complete ({} seconds)", analysisDurationSeconds);
689         if (exceptions.size() > 0) {
690             throw new ExceptionCollection(exceptions);
691         }
692     }
693 
694     /**
695      * Performs any necessary updates and initializes the database.
696      *
697      * @param exceptions a collection to store non-fatal exceptions
698      * @throws ExceptionCollection thrown if fatal exceptions occur
699      */
700     private void initializeAndUpdateDatabase(@NotNull final List<Throwable> exceptions) throws ExceptionCollection {
701         if (!mode.isDatabaseRequired()) {
702             return;
703         }
704         final boolean autoUpdate;
705         autoUpdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
706         if (autoUpdate) {
707             try {
708                 doUpdates(true);
709             } catch (UpdateException ex) {
710                 exceptions.add(ex);
711                 LOGGER.warn("Unable to update 1 or more Cached Web DataSource, using local "
712                         + "data instead. Results may not include recent vulnerabilities.");
713                 LOGGER.debug("Update Error", ex);
714             } catch (DatabaseException ex) {
715                 throwFatalDatabaseException(ex, exceptions);
716             }
717         } else {
718             try {
719                 if (DatabaseManager.isH2Connection(settings) && !DatabaseManager.h2DataFileExists(settings)) {
720                     throw new ExceptionCollection(new NoDataException("Autoupdate is disabled and the database does not exist"), true);
721                 } else {
722                     openDatabase(true, true);
723                 }
724             } catch (IOException ex) {
725                 throw new ExceptionCollection(new DatabaseException("Autoupdate is disabled and unable to connect to the database"), true);
726             } catch (DatabaseException ex) {
727                 throwFatalDatabaseException(ex, exceptions);
728             }
729         }
730     }
731 
732     /**
733      * Utility method to throw a fatal database exception.
734      *
735      * @param ex the exception that was caught
736      * @param exceptions the exception collection
737      * @throws ExceptionCollection the collection of exceptions is always thrown
738      * as a fatal exception
739      */
740     private void throwFatalDatabaseException(DatabaseException ex, final List<Throwable> exceptions) throws ExceptionCollection {
741         final String msg;
742         if (ex.getMessage().contains("Unable to connect") && DatabaseManager.isH2Connection(settings)) {
743             msg = "Unable to connect to the database - if this error persists it may be "
744                     + "due to a corrupt database. Consider running `purge` to delete the existing database";
745         } else {
746             msg = "Unable to connect to the dependency-check database";
747         }
748         exceptions.add(new DatabaseException(msg, ex));
749         throw new ExceptionCollection(exceptions, true);
750     }
751 
752     /**
753      * Executes executes the analyzer using multiple threads.
754      *
755      * @param exceptions a collection of exceptions that occurred during
756      * analysis
757      * @param analyzer the analyzer to execute
758      * @throws ExceptionCollection thrown if exceptions occurred during analysis
759      */
760     protected void executeAnalysisTasks(@NotNull final Analyzer analyzer, List<Throwable> exceptions) throws ExceptionCollection {
761         LOGGER.debug("Starting {}", analyzer.getName());
762         final List<AnalysisTask> analysisTasks = getAnalysisTasks(analyzer, exceptions);
763         final ExecutorService executorService = getExecutorService(analyzer);
764 
765         try {
766             final int timeout = settings.getInt(Settings.KEYS.ANALYSIS_TIMEOUT, 180);
767             final List<Future<Void>> results = executorService.invokeAll(analysisTasks, timeout, TimeUnit.MINUTES);
768 
769             // ensure there was no exception during execution
770             for (Future<Void> result : results) {
771                 try {
772                     result.get();
773                 } catch (ExecutionException e) {
774                     throwFatalExceptionCollection("Analysis task failed with a fatal exception.", e, exceptions);
775                 } catch (CancellationException e) {
776                     throwFatalExceptionCollection("Analysis task was cancelled.", e, exceptions);
777                 }
778             }
779         } catch (InterruptedException e) {
780             Thread.currentThread().interrupt();
781             throwFatalExceptionCollection("Analysis has been interrupted.", e, exceptions);
782         } finally {
783             executorService.shutdown();
784         }
785     }
786 
787     /**
788      * Returns the analysis tasks for the dependencies.
789      *
790      * @param analyzer the analyzer to create tasks for
791      * @param exceptions the collection of exceptions to collect
792      * @return a collection of analysis tasks
793      */
794     protected synchronized List<AnalysisTask> getAnalysisTasks(Analyzer analyzer, List<Throwable> exceptions) {
795         final List<AnalysisTask> result = new ArrayList<>();
796         dependencies.stream().map((dependency) -> new AnalysisTask(analyzer, dependency, this, exceptions)).forEach(result::add);
797         return result;
798     }
799 
800     /**
801      * Returns the executor service for a given analyzer.
802      *
803      * @param analyzer the analyzer to obtain an executor
804      * @return the executor service
805      */
806     protected ExecutorService getExecutorService(Analyzer analyzer) {
807         if (analyzer.supportsParallelProcessing()) {
808             final int maximumNumberOfThreads = Runtime.getRuntime().availableProcessors();
809             LOGGER.debug("Parallel processing with up to {} threads: {}.", maximumNumberOfThreads, analyzer.getName());
810             return Executors.newFixedThreadPool(maximumNumberOfThreads);
811         } else {
812             LOGGER.debug("Parallel processing is not supported: {}.", analyzer.getName());
813             return Executors.newSingleThreadExecutor();
814         }
815     }
816 
817     /**
818      * Initializes the given analyzer.
819      *
820      * @param analyzer the analyzer to prepare
821      * @throws InitializationException thrown when there is a problem
822      * initializing the analyzer
823      */
824     protected void initializeAnalyzer(@NotNull final Analyzer analyzer) throws InitializationException {
825         try {
826             LOGGER.debug("Initializing {}", analyzer.getName());
827             analyzer.prepare(this);
828         } catch (InitializationException ex) {
829             LOGGER.error("Exception occurred initializing {}.", analyzer.getName());
830             LOGGER.debug("", ex);
831             if (ex.isFatal()) {
832                 try {
833                     analyzer.close();
834                 } catch (Throwable ex1) {
835                     LOGGER.trace("", ex1);
836                 }
837             }
838             throw ex;
839         } catch (Throwable ex) {
840             LOGGER.error("Unexpected exception occurred initializing {}.", analyzer.getName());
841             LOGGER.debug("", ex);
842             try {
843                 analyzer.close();
844             } catch (Throwable ex1) {
845                 LOGGER.trace("", ex1);
846             }
847             throw new InitializationException("Unexpected Exception", ex);
848         }
849     }
850 
851     /**
852      * Closes the given analyzer.
853      *
854      * @param analyzer the analyzer to close
855      */
856     protected void closeAnalyzer(@NotNull final Analyzer analyzer) {
857         LOGGER.debug("Closing Analyzer '{}'", analyzer.getName());
858         try {
859             analyzer.close();
860         } catch (Throwable ex) {
861             LOGGER.trace("", ex);
862         }
863     }
864 
865     /**
866      * Cycles through the cached web data sources and calls update on all of
867      * them.
868      *
869      * @throws UpdateException thrown if the operation fails
870      * @throws DatabaseException if the operation fails due to a local database
871      * failure
872      * @return Whether any updates actually happened
873      */
874     public boolean doUpdates() throws UpdateException, DatabaseException {
875         return doUpdates(false);
876     }
877 
878     /**
879      * Cycles through the cached web data sources and calls update on all of
880      * them.
881      *
882      * @param remainOpen whether or not the database connection should remain
883      * open
884      * @throws UpdateException thrown if the operation fails
885      * @throws DatabaseException if the operation fails due to a local database
886      * failure
887      * @return Whether any updates actually happened
888      */
889     public boolean doUpdates(boolean remainOpen) throws UpdateException, DatabaseException {
890         if (mode.isDatabaseRequired()) {
891             try (WriteLock dblock = new WriteLock(getSettings(), DatabaseManager.isH2Connection(getSettings()))) {
892                 //lock is not needed as we already have the lock held
893                 openDatabase(false, false);
894                 LOGGER.info("Checking for updates");
895                 final long updateStart = System.currentTimeMillis();
896                 final UpdateService service = new UpdateService(serviceClassLoader);
897                 final Iterator<CachedWebDataSource> iterator = service.getDataSources();
898                 boolean dbUpdatesMade = false;
899                 UpdateException updateException = null;
900                 while (iterator.hasNext()) {
901                     try {
902                         final CachedWebDataSource source = iterator.next();
903                         dbUpdatesMade |= source.update(this);
904                     } catch (UpdateException ex) {
905                         updateException = ex;
906                         LOGGER.error(ex.getMessage(), ex);
907                     }
908                 }
909                 if (dbUpdatesMade) {
910                     database.defrag();
911                 }
912                 database.close();
913                 database = null;
914                 if (updateException != null) {
915                     throw updateException;
916                 }
917                 LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart);
918                 if (remainOpen) {
919                     //lock is not needed as we already have the lock held
920                     openDatabase(true, false);
921                 }
922 
923                 return dbUpdatesMade;
924             } catch (WriteLockException ex) {
925                 throw new UpdateException("Unable to obtain an exclusive lock on the H2 database to perform updates", ex);
926             }
927         } else {
928             LOGGER.info("Skipping update check in evidence collection mode.");
929             return false;
930         }
931     }
932 
933     /**
934      * Purges the cached web data sources.
935      *
936      * @return <code>true</code> if the purge was successful; otherwise
937      * <code>false</code>
938      */
939     public boolean purge() {
940         boolean result = true;
941         final UpdateService service = new UpdateService(serviceClassLoader);
942         final Iterator<CachedWebDataSource> iterator = service.getDataSources();
943         while (iterator.hasNext()) {
944             result &= iterator.next().purge(this);
945         }
946         try {
947             final File cache = new File(settings.getDataDirectory(), "cache");
948             if (cache.exists()) {
949                 if (FileUtils.delete(cache)) {
950                     LOGGER.info("Cache directory purged");
951                 }
952             }
953         } catch (IOException ex) {
954             throw new RuntimeException(ex);
955         }
956         try {
957             final File cache = new File(settings.getDataDirectory(), "oss_cache");
958             if (cache.exists()) {
959                 if (FileUtils.delete(cache)) {
960                     LOGGER.info("OSS Cache directory purged");
961                 }
962             }
963         } catch (IOException ex) {
964             throw new RuntimeException(ex);
965         }
966 
967         return result;
968     }
969 
970     /**
971      * <p>
972      * This method is only public for unit/integration testing. This method
973      * should not be called by any integration that uses
974      * dependency-check-core.</p>
975      * <p>
976      * Opens the database connection.</p>
977      *
978      * @throws DatabaseException if the database connection could not be created
979      */
980     public void openDatabase() throws DatabaseException {
981         openDatabase(false, true);
982     }
983 
984     /**
985      * <p>
986      * This method is only public for unit/integration testing. This method
987      * should not be called by any integration that uses
988      * dependency-check-core.</p>
989      * <p>
990      * Opens the database connection; if readOnly is true a copy of the database
991      * will be made.</p>
992      *
993      * @param readOnly whether or not the database connection should be readonly
994      * @param lockRequired whether or not a lock needs to be acquired when
995      * opening the database
996      * @throws DatabaseException if the database connection could not be created
997      */
998     @SuppressWarnings("try")
999     public void openDatabase(boolean readOnly, boolean lockRequired) throws DatabaseException {
1000         if (mode.isDatabaseRequired() && database == null) {
1001             try (WriteLock dblock = new WriteLock(getSettings(), lockRequired && DatabaseManager.isH2Connection(settings))) {
1002                 if (readOnly
1003                         && DatabaseManager.isH2Connection(settings)
1004                         && settings.getString(Settings.KEYS.DB_CONNECTION_STRING).contains("file:%s")) {
1005                     final File db = DatabaseManager.getH2DataFile(settings);
1006                     if (db.isFile()) {
1007                         final File temp = settings.getTempDirectory();
1008                         final File tempDB = new File(temp, db.getName());
1009                         LOGGER.debug("copying database {} to {}", db.toPath(), temp.toPath());
1010                         Files.copy(db.toPath(), tempDB.toPath());
1011                         settings.setString(Settings.KEYS.H2_DATA_DIRECTORY, temp.getPath());
1012                         final String connStr = settings.getString(Settings.KEYS.DB_CONNECTION_STRING);
1013                         if (!connStr.contains("ACCESS_MODE_DATA")) {
1014                             settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connStr + "ACCESS_MODE_DATA=r");
1015                         }
1016                         settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false);
1017                         database = new CveDB(settings);
1018                     } else {
1019                         throw new DatabaseException("Unable to open database - configured database file does not exist: " + db);
1020                     }
1021                 } else {
1022                     database = new CveDB(settings);
1023                 }
1024             } catch (IOException ex) {
1025                 throw new DatabaseException("Unable to open database in read only mode", ex);
1026             } catch (WriteLockException ex) {
1027                 throw new DatabaseException("Failed to obtain lock - unable to open database", ex);
1028             }
1029             database.open();
1030         }
1031     }
1032 
1033     /**
1034      * Returns a reference to the database.
1035      *
1036      * @return a reference to the database
1037      */
1038     public CveDB getDatabase() {
1039         return this.database;
1040     }
1041 
1042     /**
1043      * Returns a full list of all of the analyzers. This is useful for reporting
1044      * which analyzers where used.
1045      *
1046      * @return a list of Analyzers
1047      */
1048     @NotNull
1049     public List<Analyzer> getAnalyzers() {
1050         final List<Analyzer> analyzerList = new ArrayList<>();
1051         //insteae of forEach - we can just do a collect
1052         mode.getPhases().stream()
1053                 .map(analyzers::get)
1054                 .forEachOrdered(analyzerList::addAll);
1055         return analyzerList;
1056     }
1057 
1058     /**
1059      * Checks all analyzers to see if an extension is supported.
1060      *
1061      * @param file a file extension
1062      * @return true or false depending on whether or not the file extension is
1063      * supported
1064      */
1065     @Override
1066     public boolean accept(@Nullable final File file) {
1067         if (file == null) {
1068             return false;
1069         }
1070         /* note, we can't break early on this loop as the analyzers need to know if
1071         they have files to work on prior to initialization */
1072         return this.fileTypeAnalyzers.stream().map((a) -> a.accept(file)).reduce(false, (accumulator, result) -> accumulator || result);
1073     }
1074 
1075     /**
1076      * Returns the set of file type analyzers.
1077      *
1078      * @return the set of file type analyzers
1079      */
1080     public Set<FileTypeAnalyzer> getFileTypeAnalyzers() {
1081         return this.fileTypeAnalyzers;
1082     }
1083 
1084     /**
1085      * Returns the configured settings.
1086      *
1087      * @return the configured settings
1088      */
1089     public Settings getSettings() {
1090         return settings;
1091     }
1092 
1093     /**
1094      * Retrieve an object from the objects collection.
1095      *
1096      * @param key the key to retrieve the object
1097      * @return the object
1098      */
1099     public Object getObject(String key) {
1100         return objects.get(key);
1101     }
1102 
1103     /**
1104      * Put an object in the object collection.
1105      *
1106      * @param key the key to store the object
1107      * @param object the object to store
1108      */
1109     public void putObject(String key, Object object) {
1110         objects.put(key, object);
1111     }
1112 
1113     /**
1114      * Verifies if the object exists in the object store.
1115      *
1116      * @param key the key to retrieve the object
1117      * @return <code>true</code> if the object exists; otherwise
1118      * <code>false</code>
1119      */
1120     public boolean hasObject(String key) {
1121         return objects.containsKey(key);
1122     }
1123 
1124     /**
1125      * Removes an object from the object store.
1126      *
1127      * @param key the key to the object
1128      */
1129     public void removeObject(String key) {
1130         objects.remove(key);
1131     }
1132 
1133     /**
1134      * Returns the mode of the engine.
1135      *
1136      * @return the mode of the engine
1137      */
1138     public Mode getMode() {
1139         return mode;
1140     }
1141 
1142     /**
1143      * Adds a file type analyzer. This has been added solely to assist in unit
1144      * testing the Engine.
1145      *
1146      * @param fta the file type analyzer to add
1147      */
1148     protected void addFileTypeAnalyzer(@NotNull final FileTypeAnalyzer fta) {
1149         this.fileTypeAnalyzers.add(fta);
1150     }
1151 
1152     /**
1153      * Checks the CPE Index to ensure documents exists. If none exist a
1154      * NoDataException is thrown.
1155      *
1156      * @throws NoDataException thrown if no data exists in the CPE Index
1157      */
1158     private void ensureDataExists() throws NoDataException {
1159         if (mode.isDatabaseRequired() && (database == null || !database.dataExists())) {
1160             throw new NoDataException("No documents exist");
1161         }
1162     }
1163 
1164     /**
1165      * Constructs and throws a fatal exception collection.
1166      *
1167      * @param message the exception message
1168      * @param throwable the cause
1169      * @param exceptions a collection of exception to include
1170      * @throws ExceptionCollection a collection of exceptions that occurred
1171      * during analysis
1172      */
1173     private void throwFatalExceptionCollection(String message, @NotNull final Throwable throwable,
1174             @NotNull final List<Throwable> exceptions) throws ExceptionCollection {
1175         LOGGER.error(message);
1176         LOGGER.debug("", throwable);
1177         exceptions.add(throwable);
1178         throw new ExceptionCollection(exceptions, true);
1179     }
1180 
1181     /**
1182      * Writes the report to the given output directory.
1183      *
1184      * @param applicationName the name of the application/project
1185      * @param outputDir the path to the output directory (can include the full
1186      * file name if the format is not ALL)
1187      * @param format the report format (see {@link ReportGenerator.Format})
1188      * @throws ReportException thrown if there is an error generating the report
1189      * @deprecated use
1190      * {@link #writeReports(java.lang.String, java.io.File, java.lang.String, org.owasp.dependencycheck.exception.ExceptionCollection)}
1191      */
1192     @Deprecated
1193     public void writeReports(String applicationName, File outputDir, String format) throws ReportException {
1194         writeReports(applicationName, null, null, null, outputDir, format, null);
1195     }
1196 
1197     //CSOFF: LineLength
1198     /**
1199      * Writes the report to the given output directory.
1200      *
1201      * @param applicationName the name of the application/project
1202      * @param outputDir the path to the output directory (can include the full
1203      * file name if the format is not ALL)
1204      * @param format the report format (see {@link ReportGenerator.Format})
1205      * @param exceptions a collection of exceptions that may have occurred
1206      * during the analysis
1207      * @throws ReportException thrown if there is an error generating the report
1208      */
1209     public void writeReports(String applicationName, File outputDir, String format, ExceptionCollection exceptions) throws ReportException {
1210         writeReports(applicationName, null, null, null, outputDir, format, exceptions);
1211     }
1212     //CSON: LineLength
1213 
1214     /**
1215      * Writes the report to the given output directory.
1216      *
1217      * @param applicationName the name of the application/project
1218      * @param groupId the Maven groupId
1219      * @param artifactId the Maven artifactId
1220      * @param version the Maven version
1221      * @param outputDir the path to the output directory (can include the full
1222      * file name if the format is not ALL)
1223      * @param format the report format (see {@link ReportGenerator.Format})
1224      * @throws ReportException thrown if there is an error generating the report
1225      * @deprecated use
1226      * {@link #writeReports(String, String, String, String, File, String, ExceptionCollection)}
1227      */
1228     @Deprecated
1229     public synchronized void writeReports(String applicationName, @Nullable final String groupId,
1230             @Nullable final String artifactId, @Nullable final String version,
1231             @NotNull final File outputDir, String format) throws ReportException {
1232         writeReports(applicationName, groupId, artifactId, version, outputDir, format, null);
1233     }
1234 
1235     //CSOFF: LineLength
1236     /**
1237      * Writes the report to the given output directory.
1238      *
1239      * @param applicationName the name of the application/project
1240      * @param groupId the Maven groupId
1241      * @param artifactId the Maven artifactId
1242      * @param version the Maven version
1243      * @param outputDir the path to the output directory (can include the full
1244      * file name if the format is not ALL)
1245      * @param format the report format  (see {@link ReportGenerator.Format})
1246      * @param exceptions a collection of exceptions that may have occurred
1247      * during the analysis
1248      * @throws ReportException thrown if there is an error generating the report
1249      */
1250     public synchronized void writeReports(String applicationName, @Nullable final String groupId,
1251             @Nullable final String artifactId, @Nullable final String version,
1252             @NotNull final File outputDir, String format, ExceptionCollection exceptions) throws ReportException {
1253         if (mode == Mode.EVIDENCE_COLLECTION) {
1254             throw new UnsupportedOperationException("Cannot generate report in evidence collection mode.");
1255         }
1256         final DatabaseProperties prop = database.getDatabaseProperties();
1257 
1258         final ReportGenerator r = new ReportGenerator(applicationName, groupId, artifactId, version,
1259                 dependencies, getAnalyzers(), prop, settings, exceptions);
1260         try {
1261             r.write(outputDir.getAbsolutePath(), format);
1262         } catch (ReportException ex) {
1263             final String msg = String.format("Error generating the report for %s", applicationName);
1264             LOGGER.debug(msg, ex);
1265             throw new ReportException(msg, ex);
1266         }
1267     }
1268     //CSON: LineLength
1269 
1270     private boolean identifiersMatch(Set<Identifier> left, Set<Identifier> right) {
1271         if (left != null && right != null && left.size() > 0 && left.size() == right.size()) {
1272             int count = 0;
1273             for (Identifier l : left) {
1274                 for (Identifier r : right) {
1275                     if (l.getValue().equals(r.getValue())) {
1276                         count += 1;
1277                         break;
1278                     }
1279                 }
1280             }
1281             return count == left.size();
1282         }
1283         return false;
1284     }
1285 
1286     /**
1287      * {@link Engine} execution modes.
1288      */
1289     public enum Mode {
1290         /**
1291          * In evidence collection mode the {@link Engine} only collects evidence
1292          * from the scan targets, and doesn't require a database.
1293          */
1294         EVIDENCE_COLLECTION(
1295                 false,
1296                 INITIAL,
1297                 PRE_INFORMATION_COLLECTION,
1298                 INFORMATION_COLLECTION,
1299                 INFORMATION_COLLECTION2,
1300                 POST_INFORMATION_COLLECTION1,
1301                 POST_INFORMATION_COLLECTION2,
1302                 POST_INFORMATION_COLLECTION3
1303         ),
1304         /**
1305          * In evidence processing mode the {@link Engine} processes the evidence
1306          * collected using the {@link #EVIDENCE_COLLECTION} mode. Dependencies
1307          * should be injected into the {@link Engine} using
1308          * {@link Engine#setDependencies(List)}.
1309          */
1310         EVIDENCE_PROCESSING(
1311                 true,
1312                 PRE_IDENTIFIER_ANALYSIS,
1313                 IDENTIFIER_ANALYSIS,
1314                 POST_IDENTIFIER_ANALYSIS,
1315                 PRE_FINDING_ANALYSIS,
1316                 FINDING_ANALYSIS,
1317                 POST_FINDING_ANALYSIS,
1318                 FINDING_ANALYSIS_PHASE2,
1319                 FINAL
1320         ),
1321         /**
1322          * In standalone mode the {@link Engine} will collect and process
1323          * evidence in a single execution.
1324          */
1325         STANDALONE(true, AnalysisPhase.values());
1326 
1327         /**
1328          * Whether the database is required in this mode.
1329          */
1330         private final boolean databaseRequired;
1331         /**
1332          * The analysis phases included in the mode.
1333          */
1334         private final List<AnalysisPhase> phases;
1335 
1336         /**
1337          * Constructs a new mode.
1338          *
1339          * @param databaseRequired if the database is required for the mode
1340          * @param phases the analysis phases to include in the mode
1341          */
1342         Mode(boolean databaseRequired, AnalysisPhase... phases) {
1343             this.databaseRequired = databaseRequired;
1344             this.phases = Collections.unmodifiableList(Arrays.asList(phases));
1345         }
1346 
1347         /**
1348          * Returns true if the database is required; otherwise false.
1349          *
1350          * @return whether or not the database is required
1351          */
1352         private boolean isDatabaseRequired() {
1353             return databaseRequired;
1354         }
1355 
1356         /**
1357          * Returns the phases for this mode.
1358          *
1359          * @return the phases for this mode
1360          */
1361         public List<AnalysisPhase> getPhases() {
1362             return phases;
1363         }
1364     }
1365 }