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