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