1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.basepom.mojo.duplicatefinder;
16
17 import static com.google.common.base.Preconditions.checkArgument;
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static com.google.common.base.Preconditions.checkState;
20 import static java.lang.String.format;
21 import static org.apache.maven.artifact.Artifact.SCOPE_COMPILE;
22 import static org.apache.maven.artifact.Artifact.SCOPE_PROVIDED;
23 import static org.apache.maven.artifact.Artifact.SCOPE_RUNTIME;
24 import static org.apache.maven.artifact.Artifact.SCOPE_SYSTEM;
25 import static org.basepom.mojo.duplicatefinder.ConflictState.CONFLICT_CONTENT_DIFFERENT;
26 import static org.basepom.mojo.duplicatefinder.ConflictState.CONFLICT_CONTENT_EQUAL;
27 import static org.basepom.mojo.duplicatefinder.ConflictType.CLASS;
28 import static org.basepom.mojo.duplicatefinder.ConflictType.RESOURCE;
29 import static org.basepom.mojo.duplicatefinder.artifact.ArtifactHelper.getOutputDirectory;
30 import static org.basepom.mojo.duplicatefinder.artifact.ArtifactHelper.getTestOutputDirectory;
31
32 import org.basepom.mojo.duplicatefinder.ResultCollector.ConflictResult;
33 import org.basepom.mojo.duplicatefinder.artifact.ArtifactFileResolver;
34 import org.basepom.mojo.duplicatefinder.artifact.MavenCoordinates;
35 import org.basepom.mojo.duplicatefinder.classpath.ClasspathDescriptor;
36
37 import java.io.BufferedInputStream;
38 import java.io.File;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.nio.file.Files;
42 import java.util.AbstractMap.SimpleImmutableEntry;
43 import java.util.Arrays;
44 import java.util.Collection;
45 import java.util.EnumSet;
46 import java.util.Map;
47 import java.util.Map.Entry;
48 import java.util.Set;
49 import java.util.SortedSet;
50 import java.util.zip.ZipEntry;
51 import java.util.zip.ZipFile;
52 import javax.xml.stream.XMLStreamException;
53
54 import com.google.common.base.Strings;
55 import com.google.common.collect.ImmutableMap;
56 import com.google.common.collect.ImmutableSet;
57 import com.google.common.collect.Maps;
58 import com.google.common.collect.Multimap;
59 import com.google.common.hash.HashFunction;
60 import com.google.common.hash.Hashing;
61 import com.google.common.io.ByteStreams;
62 import com.google.common.io.Closer;
63 import org.apache.maven.artifact.Artifact;
64 import org.apache.maven.artifact.DependencyResolutionRequiredException;
65 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
66 import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
67 import org.apache.maven.model.Dependency;
68 import org.apache.maven.plugin.AbstractMojo;
69 import org.apache.maven.plugin.MojoExecutionException;
70 import org.apache.maven.plugin.MojoFailureException;
71 import org.apache.maven.plugins.annotations.LifecyclePhase;
72 import org.apache.maven.plugins.annotations.Mojo;
73 import org.apache.maven.plugins.annotations.Parameter;
74 import org.apache.maven.plugins.annotations.ResolutionScope;
75 import org.apache.maven.project.MavenProject;
76 import org.codehaus.stax2.XMLOutputFactory2;
77 import org.codehaus.staxmate.SMOutputFactory;
78 import org.codehaus.staxmate.out.SMOutputDocument;
79 import org.codehaus.staxmate.out.SMOutputElement;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
82
83
84
85
86 @Mojo(name = "check", requiresProject = true, threadSafe = true, defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.TEST)
87 public final class DuplicateFinderMojo extends AbstractMojo {
88
89 private static final Logger LOG = LoggerFactory.getLogger(DuplicateFinderMojo.class);
90
91 private static final int SAVE_FILE_VERSION = 1;
92
93 private static final HashFunction SHA_256 = Hashing.sha256();
94
95 private static final Set<String> COMPILE_SCOPE = ImmutableSet.of(SCOPE_COMPILE, SCOPE_PROVIDED, SCOPE_SYSTEM);
96 private static final Set<String> RUNTIME_SCOPE = ImmutableSet.of(SCOPE_COMPILE, SCOPE_RUNTIME);
97 private static final Set<String> TEST_SCOPE = ImmutableSet.of();
98
99
100
101
102 @Parameter(defaultValue = "${project}", readonly = true)
103 public MavenProject project;
104
105
106
107
108
109
110 @Parameter(defaultValue = "false", property = "duplicate-finder.printEqualFiles")
111 public boolean printEqualFiles = false;
112
113
114
115
116
117
118 @Parameter(defaultValue = "false", property = "duplicate-finder.failBuildInCaseOfDifferentContentConflict")
119 public boolean failBuildInCaseOfDifferentContentConflict = false;
120
121
122
123
124
125
126 @Parameter(defaultValue = "false", property = "duplicate-finder.failBuildInCaseOfEqualContentConflict")
127 public boolean failBuildInCaseOfEqualContentConflict = false;
128
129
130
131
132 @Parameter(defaultValue = "false", property = "duplicate-finder.failBuildInCaseOfConflict")
133 public boolean failBuildInCaseOfConflict = false;
134
135
136
137
138 @Parameter(defaultValue = "true", property = "duplicate-finder.useDefaultResourceIgnoreList")
139 public boolean useDefaultResourceIgnoreList = true;
140
141
142
143
144
145
146 @Parameter(defaultValue = "true", property = "duplicate-finder.useDefaultClassIgnoreList")
147 public boolean useDefaultClassIgnoreList = true;
148
149
150
151
152 @Parameter
153 public String[] ignoredResourcePatterns = new String[0];
154
155
156
157
158
159
160 @Parameter
161 public String[] ignoredClassPatterns = new String[0];
162
163
164
165
166 @Parameter(alias = "exceptions")
167 public ConflictingDependency[] conflictingDependencies = new ConflictingDependency[0];
168
169
170
171
172 @Parameter(alias = "ignoredDependencies")
173 MavenCoordinates[] ignoredDependencies = new MavenCoordinates[0];
174
175
176
177
178 @Parameter(defaultValue = "true", property = "duplicate-finder.checkCompileClasspath")
179 public boolean checkCompileClasspath = true;
180
181
182
183
184 @Parameter(defaultValue = "true", property = "duplicate-finder.checkRuntimeClasspath")
185 public boolean checkRuntimeClasspath = true;
186
187
188
189
190 @Parameter(defaultValue = "true", property = "duplicate-finder.checkTestClasspath")
191 public boolean checkTestClasspath = true;
192
193
194
195
196 @Parameter(defaultValue = "false", property = "duplicate-finder.skip")
197 public boolean skip = false;
198
199
200
201
202
203
204
205 @Parameter(defaultValue = "false", property = "duplicate-finder.quiet")
206 @Deprecated
207 public boolean quiet = false;
208
209
210
211
212
213
214 @Parameter(defaultValue = "true", property = "duplicate-finder.preferLocal")
215 public boolean preferLocal = true;
216
217
218
219
220
221
222 @Parameter(defaultValue = "${project.build.directory}/duplicate-finder-result.xml", property = "duplicate-finder.resultFile")
223 public File resultFile;
224
225
226
227
228
229
230 @Parameter(defaultValue = "true", property = "duplicate-finder.useResultFile")
231 public boolean useResultFile = true;
232
233
234
235
236
237
238 @Parameter(defaultValue = "2", property = "duplicate-finder.resultFileMinClasspathCount")
239 public int resultFileMinClasspathCount = 2;
240
241
242
243
244
245
246
247 @Parameter(defaultValue = "false", property = "duplicate-finder.includeBootClasspath")
248 @Deprecated
249 public boolean includeBootClasspath = false;
250
251
252
253
254
255
256
257 @Parameter(property = "duplicate-finder.bootClasspathProperty")
258 @Deprecated
259 public String bootClasspathProperty = null;
260
261
262
263
264
265
266 @Parameter(defaultValue = "false", property = "duplicate-finder.includePomProjects")
267 public boolean includePomProjects = false;
268
269 private final EnumSet<ConflictState> printState = EnumSet.of(CONFLICT_CONTENT_DIFFERENT);
270 private final EnumSet<ConflictState> failState = EnumSet.noneOf(ConflictState.class);
271
272
273 public void setIgnoredDependencies(final Dependency... dependencies) throws InvalidVersionSpecificationException {
274 checkArgument(dependencies != null);
275
276 this.ignoredDependencies = new MavenCoordinates[dependencies.length];
277 for (int idx = 0; idx < dependencies.length; idx++) {
278 this.ignoredDependencies[idx] = new MavenCoordinates(dependencies[idx]);
279 }
280 }
281
282 @Override
283 public void execute() throws MojoExecutionException, MojoFailureException {
284 if (skip) {
285 LOG.info("Skipping duplicate-finder execution!");
286 } else if (!includePomProjects && "pom".equals(project.getArtifact().getType())) {
287 LOG.info("Ignoring POM project!");
288 } else {
289 if (printEqualFiles) {
290 printState.add(CONFLICT_CONTENT_EQUAL);
291 }
292
293 if (failBuildInCaseOfConflict || failBuildInCaseOfEqualContentConflict) {
294 printState.add(CONFLICT_CONTENT_EQUAL);
295
296 failState.add(CONFLICT_CONTENT_EQUAL);
297 failState.add(CONFLICT_CONTENT_DIFFERENT);
298 }
299
300 if (failBuildInCaseOfDifferentContentConflict) {
301 failState.add(CONFLICT_CONTENT_DIFFERENT);
302 }
303
304 if (includeBootClasspath) {
305 LOG.warn("<includeBootClasspath> is no longer supported and will be ignored!");
306 }
307 if (bootClasspathProperty != null) {
308 LOG.warn("<bootClasspathProperty> is no longer supported and will be ignored!");
309 }
310
311 if (quiet) {
312 LOG.warn("<quiet> is no longer supported and will be ignored!");
313 }
314
315 try {
316
317 MavenCoordinates projectCoordinates = new MavenCoordinates(project.getArtifact());
318
319 for (ConflictingDependency conflictingDependency : conflictingDependencies) {
320 conflictingDependency.addProjectMavenCoordinates(projectCoordinates);
321 }
322
323 final ArtifactFileResolver artifactFileResolver = new ArtifactFileResolver(project, preferLocal);
324 final ImmutableMap.Builder<String, Entry<ResultCollector, ClasspathDescriptor>> classpathResultBuilder = ImmutableMap.builder();
325
326 if (checkCompileClasspath) {
327 LOG.info("Checking compile classpath");
328 final ResultCollector resultCollector = new ResultCollector(printState, failState);
329 final ClasspathDescriptor classpathDescriptor = checkClasspath(resultCollector, artifactFileResolver, COMPILE_SCOPE,
330 getOutputDirectory(project));
331 classpathResultBuilder.put("compile", new SimpleImmutableEntry<>(resultCollector, classpathDescriptor));
332 }
333
334 if (checkRuntimeClasspath) {
335 LOG.info("Checking runtime classpath");
336 final ResultCollector resultCollector = new ResultCollector(printState, failState);
337 final ClasspathDescriptor classpathDescriptor = checkClasspath(resultCollector, artifactFileResolver, RUNTIME_SCOPE,
338 getOutputDirectory(project));
339 classpathResultBuilder.put("runtime", new SimpleImmutableEntry<>(resultCollector, classpathDescriptor));
340 }
341
342 if (checkTestClasspath) {
343 LOG.info("Checking test classpath");
344 final ResultCollector resultCollector = new ResultCollector(printState, failState);
345 final ClasspathDescriptor classpathDescriptor = checkClasspath(resultCollector,
346 artifactFileResolver,
347 TEST_SCOPE,
348 getOutputDirectory(project),
349 getTestOutputDirectory(project));
350 classpathResultBuilder.put("test", new SimpleImmutableEntry<>(resultCollector, classpathDescriptor));
351 }
352
353 final ImmutableMap<String, Entry<ResultCollector, ClasspathDescriptor>> classpathResults = classpathResultBuilder.build();
354
355 if (useResultFile) {
356 checkState(resultFile != null, "resultFile must be set if useResultFile is true");
357 writeResultFile(resultFile, classpathResults);
358 }
359
360 boolean failed = false;
361
362 for (Map.Entry<String, Entry<ResultCollector, ClasspathDescriptor>> classpathEntry : classpathResults.entrySet()) {
363 String classpathName = classpathEntry.getKey();
364 ResultCollector resultCollector = classpathEntry.getValue().getKey();
365
366 for (final ConflictState state : printState) {
367 for (final ConflictType type : ConflictType.values()) {
368 if (resultCollector.hasConflictsFor(type, state)) {
369 final Map<String, Collection<ConflictResult>> results = resultCollector.getResults(type, state);
370 for (final Map.Entry<String, Collection<ConflictResult>> entry : results.entrySet()) {
371 final String artifactNames = entry.getKey();
372 final Collection<ConflictResult> conflictResults = entry.getValue();
373
374 LOG.warn(format("Found duplicate %s %s in [%s]:", state.getHint(), type.getType(), artifactNames));
375 for (final ConflictResult conflictResult : conflictResults) {
376 LOG.warn(format(" %s", conflictResult.getName()));
377 }
378 }
379 }
380 }
381 }
382
383 failed |= resultCollector.isFailed();
384
385 if (resultCollector.isFailed()) {
386 LOG.warn(format("Found duplicate classes/resources in %s classpath.", classpathName));
387 }
388 }
389
390 if (failed) {
391 throw new MojoExecutionException("Found duplicate classes/resources!");
392 }
393 } catch (final DependencyResolutionRequiredException e) {
394 throw new MojoFailureException("Could not resolve dependencies", e);
395 } catch (final InvalidVersionSpecificationException e) {
396 throw new MojoFailureException("Invalid version specified", e);
397 } catch (final OverConstrainedVersionException e) {
398 throw new MojoFailureException("Version too constrained", e);
399 } catch (final IOException e) {
400 throw new MojoExecutionException("While loading artifacts", e);
401 }
402 }
403 }
404
405 private ImmutableSet<String> getIgnoredResourcePatterns() {
406 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
407 builder.add(ignoredResourcePatterns);
408
409 return builder.build();
410 }
411
412 private ImmutableSet<String> getIgnoredClassPatterns() {
413 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
414 builder.add(ignoredClassPatterns);
415
416 return builder.build();
417 }
418
419
420
421
422
423 private ClasspathDescriptor checkClasspath(final ResultCollector resultCollector,
424 final ArtifactFileResolver artifactFileResolver,
425 final Set<String> scopes,
426 final File... projectFolders)
427 throws MojoExecutionException, InvalidVersionSpecificationException, OverConstrainedVersionException, DependencyResolutionRequiredException {
428
429
430
431 final Multimap<File, Artifact> fileToArtifactMap = artifactFileResolver.resolveArtifactsForScopes(scopes);
432
433 final ClasspathDescriptor classpathDescriptor = ClasspathDescriptor.createClasspathDescriptor(project,
434 fileToArtifactMap,
435 getIgnoredResourcePatterns(),
436 getIgnoredClassPatterns(),
437 Arrays.asList(ignoredDependencies),
438 useDefaultResourceIgnoreList,
439 useDefaultClassIgnoreList,
440 projectFolders);
441
442
443
444 checkForDuplicates(CLASS, resultCollector, classpathDescriptor, artifactFileResolver);
445 checkForDuplicates(RESOURCE, resultCollector, classpathDescriptor, artifactFileResolver);
446 return classpathDescriptor;
447 }
448
449 private void checkForDuplicates(final ConflictType type, final ResultCollector resultCollector, final ClasspathDescriptor classpathDescriptor,
450 final ArtifactFileResolver artifactFileResolver)
451 throws OverConstrainedVersionException {
452
453 final Map<String, Collection<File>> filteredMap = ImmutableMap.copyOf(Maps.filterEntries(classpathDescriptor.getClasspathElementLocations(type),
454 entry -> {
455 checkNotNull(entry, "entry is null");
456 checkState(entry.getValue() != null, "Entry '%s' is invalid", entry);
457
458 return entry.getValue().size() > 1;
459 }));
460
461 for (final Map.Entry<String, Collection<File>> entry : filteredMap.entrySet()) {
462 final String name = entry.getKey();
463 final Collection<File> elements = entry.getValue();
464
465
466
467 final SortedSet<ClasspathElement> conflictingClasspathElements = artifactFileResolver.getClasspathElementsForElements(elements);
468
469 ImmutableSet.Builder<Artifact> artifactBuilder = ImmutableSet.builder();
470
471 for (ClasspathElement conflictingClasspathElement : conflictingClasspathElements) {
472 if (conflictingClasspathElement.hasArtifact()) {
473 artifactBuilder.add(conflictingClasspathElement.getArtifact());
474 } else if (conflictingClasspathElement.isLocalFolder()) {
475 artifactBuilder.add(project.getArtifact());
476 }
477 }
478
479 final boolean excepted = isExcepted(type, name, artifactBuilder.build());
480 final ConflictState conflictState = DuplicateFinderMojo.determineConflictState(type, name, elements);
481
482 resultCollector.addConflict(type, name, conflictingClasspathElements, excepted, conflictState);
483 }
484 }
485
486
487
488
489 private static ConflictState determineConflictState(final ConflictType type, final String name, final Iterable<File> elements) {
490 File firstFile = null;
491 String firstSHA256 = null;
492
493 final String resourcePath = type == ConflictType.CLASS ? name.replace('.', '/') + ".class" : name;
494
495 for (final File element : elements) {
496 try {
497 final String newSHA256 = getSHA256HexOfElement(element, resourcePath);
498
499 if (firstSHA256 == null) {
500
501 firstSHA256 = newSHA256;
502 firstFile = element;
503 } else if (!newSHA256.equals(firstSHA256)) {
504 LOG.debug(format("Found different SHA256 hashes for elements %s in file %s and %s", resourcePath, firstFile, element));
505 return ConflictState.CONFLICT_CONTENT_DIFFERENT;
506 }
507 } catch (final IOException ex) {
508 LOG.warn(format("Could not read content from file %s!", element), ex);
509 }
510 }
511
512 return ConflictState.CONFLICT_CONTENT_EQUAL;
513 }
514
515
516
517
518
519
520
521
522
523 private static String getSHA256HexOfElement(final File file, final String resourcePath) throws IOException {
524
525 try (Closer closer = Closer.create()) {
526 InputStream in;
527
528 if (file.isDirectory()) {
529 final File resourceFile = new File(file, resourcePath);
530 in = closer.register(new BufferedInputStream(Files.newInputStream(resourceFile.toPath())));
531 } else {
532 final ZipFile zip = new ZipFile(file);
533
534 closer.register(zip::close);
535
536 final ZipEntry zipEntry = zip.getEntry(resourcePath);
537
538 if (zipEntry == null) {
539 throw new IOException(format("Could not find %s in archive %s", resourcePath, file));
540 }
541
542 in = zip.getInputStream(zipEntry);
543 }
544
545 return SHA_256.newHasher().putBytes(ByteStreams.toByteArray(in)).hash().toString();
546 }
547 }
548
549 private boolean isExcepted(final ConflictType type, final String name, final Set<Artifact> artifacts)
550 throws OverConstrainedVersionException {
551 final ImmutableSet.Builder<ConflictingDependency> conflictBuilder = ImmutableSet.builder();
552 checkState(conflictingDependencies != null, "conflictingDependencies is null");
553
554
555 for (final ConflictingDependency conflictingDependency : conflictingDependencies) {
556 if (conflictingDependency.isForArtifacts(artifacts)) {
557 conflictBuilder.add(conflictingDependency);
558 }
559 }
560
561
562 for (final ConflictingDependency conflictingDependency : conflictBuilder.build()) {
563 if (type == ConflictType.CLASS && conflictingDependency.containsClass(name)) {
564 return true;
565 } else if (type == ConflictType.RESOURCE && conflictingDependency.containsResource(name)) {
566 return true;
567 }
568 }
569 return false;
570 }
571
572 private void writeResultFile(File resultFile, ImmutableMap<String, Entry<ResultCollector, ClasspathDescriptor>> results)
573 throws MojoExecutionException, InvalidVersionSpecificationException, OverConstrainedVersionException {
574 File parent = resultFile.getParentFile();
575 if (!parent.exists()) {
576 if (!parent.mkdirs()) {
577 throw new MojoExecutionException("Could not create parent folders for " + parent.getAbsolutePath());
578 }
579 }
580 if (!parent.isDirectory() || !parent.canWrite()) {
581 throw new MojoExecutionException("Can not create result file in " + parent.getAbsolutePath());
582 }
583
584 try {
585 SMOutputFactory factory = new SMOutputFactory(XMLOutputFactory2.newFactory());
586 SMOutputDocument resultDocument = factory.createOutputDocument(resultFile);
587 resultDocument.setIndentation("\n" + Strings.repeat(" ", 64), 1, 4);
588
589 SMOutputElement rootElement = resultDocument.addElement("duplicate-finder-result");
590 XMLWriterUtils.addAttribute(rootElement, "version", SAVE_FILE_VERSION);
591
592 XMLWriterUtils.addProjectInformation(rootElement, project);
593
594 addConfiguration(rootElement);
595
596 SMOutputElement resultsElement = rootElement.addElement("results");
597 for (Map.Entry<String, Entry<ResultCollector, ClasspathDescriptor>> entry : results.entrySet()) {
598 SMOutputElement resultElement = resultsElement.addElement("result");
599 XMLWriterUtils.addAttribute(resultElement, "name", entry.getKey());
600 XMLWriterUtils.addResultCollector(resultElement, entry.getValue().getKey());
601 XMLWriterUtils.addClasspathDescriptor(resultElement, resultFileMinClasspathCount, entry.getValue().getValue());
602 }
603
604 resultDocument.closeRootAndWriter();
605 } catch (XMLStreamException e) {
606 throw new MojoExecutionException("While writing result file", e);
607 }
608 }
609
610 private void addConfiguration(SMOutputElement rootElement)
611 throws XMLStreamException {
612 SMOutputElement prefs = XMLWriterUtils.addElement(rootElement, "configuration", null);
613
614 XMLWriterUtils.addAttribute(prefs, "skip", skip);
615 XMLWriterUtils.addAttribute(prefs, "checkCompileClasspath", checkCompileClasspath);
616 XMLWriterUtils.addAttribute(prefs, "checkRuntimeClasspath", checkRuntimeClasspath);
617 XMLWriterUtils.addAttribute(prefs, "checkTestClasspath", checkTestClasspath);
618 XMLWriterUtils.addAttribute(prefs, "failBuildInCaseOfDifferentContentConflict", failBuildInCaseOfDifferentContentConflict);
619 XMLWriterUtils.addAttribute(prefs, "failBuildInCaseOfEqualContentConflict", failBuildInCaseOfEqualContentConflict);
620 XMLWriterUtils.addAttribute(prefs, "failBuildInCaseOfConflict", failBuildInCaseOfConflict);
621 XMLWriterUtils.addAttribute(prefs, "printEqualFiles", printEqualFiles);
622 XMLWriterUtils.addAttribute(prefs, "preferLocal", preferLocal);
623 XMLWriterUtils.addAttribute(prefs, "includePomProjects", includePomProjects);
624
625 XMLWriterUtils.addAttribute(prefs, "useDefaultResourceIgnoreList", useDefaultResourceIgnoreList);
626 XMLWriterUtils.addAttribute(prefs, "useDefaultClassIgnoreList", useDefaultClassIgnoreList);
627
628 XMLWriterUtils.addAttribute(prefs, "useResultFile", useResultFile);
629 XMLWriterUtils.addAttribute(prefs, "resultFileMinClasspathCount", resultFileMinClasspathCount);
630 XMLWriterUtils.addAttribute(prefs, "resultFile", resultFile.getAbsolutePath());
631
632 SMOutputElement ignoredResourcesElement = prefs.addElement("ignoredResourcePatterns");
633 for (String ignoredResource : getIgnoredResourcePatterns()) {
634 XMLWriterUtils.addElement(ignoredResourcesElement, "ignoredResourcePattern", ignoredResource);
635 }
636
637 SMOutputElement ignoredClassElement = prefs.addElement("ignoredClassPatterns");
638 for (String ignoredClass : getIgnoredClassPatterns()) {
639 XMLWriterUtils.addElement(ignoredClassElement, "ignoredClassPattern", ignoredClass);
640 }
641
642 SMOutputElement conflictingDependenciesElement = prefs.addElement("conflictingDependencies");
643 for (ConflictingDependency conflictingDependency : conflictingDependencies) {
644 XMLWriterUtils.addConflictingDependency(conflictingDependenciesElement, "conflictingDependency", conflictingDependency);
645 }
646
647 SMOutputElement ignoredDependenciesElement = prefs.addElement("ignoredDependencies");
648 for (MavenCoordinates ignoredDependency : ignoredDependencies) {
649 XMLWriterUtils.addMavenCoordinate(ignoredDependenciesElement, "dependency", ignoredDependency);
650 }
651 }
652 }
653