Skip to content

Commit 1ddddd2

Browse files
authored
Refactor MavenProvider (#230)
Two new classes created: * CodeTFGenerator * POMDependencyUpdater
1 parent a666a2a commit 1ddddd2

File tree

4 files changed

+316
-184
lines changed

4 files changed

+316
-184
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package io.codemodder.plugins.maven;
2+
3+
import com.github.difflib.DiffUtils;
4+
import com.github.difflib.UnifiedDiffUtils;
5+
import com.github.difflib.patch.AbstractDelta;
6+
import com.github.difflib.patch.Patch;
7+
import io.codemodder.DependencyDescriptor;
8+
import io.codemodder.DependencyGAV;
9+
import io.codemodder.codetf.CodeTFChange;
10+
import io.codemodder.codetf.CodeTFChangesetEntry;
11+
import io.codemodder.plugins.maven.operator.POMDocument;
12+
import java.io.File;
13+
import java.net.URISyntaxException;
14+
import java.nio.file.Path;
15+
import java.util.*;
16+
import java.util.regex.Pattern;
17+
18+
/**
19+
* CodeTFGenerator is responsible for generating {@link CodeTFChangesetEntry} for Maven POM updates.
20+
*/
21+
final class CodeTFGenerator {
22+
23+
private final ArtifactInjectionPositionFinder positionFinder;
24+
25+
private final DependencyDescriptor dependencyDescriptor;
26+
27+
/**
28+
* Constructs a CodeTFGenerator with the specified artifact injection position finder and
29+
* dependency descriptor.
30+
*
31+
* @param positionFinder The ArtifactInjectionPositionFinder for finding artifact positions.
32+
* @param dependencyDescriptor The DependencyDescriptor for generating dependency descriptions.
33+
*/
34+
CodeTFGenerator(
35+
final ArtifactInjectionPositionFinder positionFinder,
36+
final DependencyDescriptor dependencyDescriptor) {
37+
this.dependencyDescriptor = Objects.requireNonNull(dependencyDescriptor);
38+
this.positionFinder = Objects.requireNonNull(positionFinder);
39+
}
40+
41+
/**
42+
* Get CodeTFChangesetEntry for Maven POM updates.
43+
*
44+
* @param projectDir The project directory where the POM is located.
45+
* @param pomDocument The POMDocument representing the POM to be updated.
46+
* @param newDependency The new dependency to be added to the POM.
47+
* @return CodeTFChangesetEntry representing the POM update.
48+
*/
49+
CodeTFChangesetEntry getChanges(
50+
final Path projectDir, final POMDocument pomDocument, final DependencyGAV newDependency) {
51+
final List<String> originalPomContents =
52+
getLinesFrom(pomDocument, pomDocument.getOriginalPom());
53+
final List<String> finalPomContents =
54+
getLinesFrom(pomDocument, pomDocument.getResultPomBytes());
55+
56+
final Patch<String> patch = DiffUtils.diff(originalPomContents, finalPomContents);
57+
58+
final List<AbstractDelta<String>> deltas = patch.getDeltas();
59+
final int position = positionFinder.find(deltas, newDependency.artifact());
60+
61+
final Path pomDocumentPath = getPomDocumentPath(pomDocument);
62+
63+
final String relativePomPath = projectDir.relativize(pomDocumentPath).toString();
64+
65+
final String description = dependencyDescriptor.create(newDependency);
66+
final Map<String, String> properties = buildPropertiesMap(description);
67+
final CodeTFChange change =
68+
new CodeTFChange(position, properties, description, List.of(), List.of());
69+
70+
final List<String> patchDiff =
71+
UnifiedDiffUtils.generateUnifiedDiff(
72+
relativePomPath, relativePomPath, originalPomContents, patch, 3);
73+
74+
final String diff = String.join(pomDocument.getEndl(), patchDiff);
75+
76+
return new CodeTFChangesetEntry(relativePomPath, diff, List.of(change));
77+
}
78+
79+
private Path getPomDocumentPath(final POMDocument pomDocument) {
80+
try {
81+
return new File(pomDocument.getPomPath().toURI()).toPath();
82+
} catch (URISyntaxException e) {
83+
throw new MavenProvider.DependencyUpdateException(
84+
"Failure on URI for " + pomDocument.getPomPath(), e);
85+
}
86+
}
87+
88+
private Map<String, String> buildPropertiesMap(final String description) {
89+
return description != null && !description.isBlank()
90+
? Map.of("contextual_description", "true")
91+
: Collections.emptyMap();
92+
}
93+
94+
private List<String> getLinesFrom(final POMDocument doc, final byte[] byteArray) {
95+
return Arrays.asList(
96+
new String(byteArray, doc.getCharset()).split(Pattern.quote(doc.getEndl())));
97+
}
98+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package io.codemodder.plugins.maven;
2+
3+
import io.codemodder.DependencyGAV;
4+
import io.codemodder.DependencyUpdateResult;
5+
import io.codemodder.codetf.CodeTFChangesetEntry;
6+
import io.codemodder.plugins.maven.operator.POMDocument;
7+
import io.codemodder.plugins.maven.operator.POMOperator;
8+
import io.codemodder.plugins.maven.operator.ProjectModel;
9+
import java.io.IOException;
10+
import java.io.UncheckedIOException;
11+
import java.net.URI;
12+
import java.net.URISyntaxException;
13+
import java.nio.file.Path;
14+
import java.util.*;
15+
import java.util.concurrent.atomic.AtomicReference;
16+
import javax.xml.stream.XMLStreamException;
17+
import org.dom4j.DocumentException;
18+
import org.jetbrains.annotations.NotNull;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
/** POMDependencyUpdater is responsible for updating Maven POM files with new dependencies. */
23+
class DefaultPOMDependencyUpdater implements POMDependencyUpdater {
24+
private final PomFileFinder pomFileFinder;
25+
26+
private Optional<Path> maybePomFile;
27+
28+
private final MavenProvider.PomModifier pomModifier;
29+
30+
private final CodeTFGenerator codeTFGenerator;
31+
32+
private List<CodeTFChangesetEntry> changesets;
33+
34+
private List<DependencyGAV> skippedDependencies;
35+
private List<DependencyGAV> injectedDependencies;
36+
private Set<Path> erroredFiles;
37+
38+
private AtomicReference<Collection<DependencyGAV>> foundDependenciesMapped;
39+
40+
private static final Logger LOG = LoggerFactory.getLogger(DefaultPOMDependencyUpdater.class);
41+
42+
/**
43+
* Constructs a POMDependencyUpdater with the specified CodeTFGenerator, PomFileFinder, and
44+
* PomModifier.
45+
*
46+
* @param codeTFGenerator The CodeTFGenerator for generating CodeTFChangesetEntries.
47+
* @param pomFileFinder The PomFileFinder for locating POM files.
48+
* @param pomModifier The MavenProvider.PomModifier for modifying POM files.
49+
*/
50+
DefaultPOMDependencyUpdater(
51+
final CodeTFGenerator codeTFGenerator,
52+
final PomFileFinder pomFileFinder,
53+
final MavenProvider.PomModifier pomModifier) {
54+
this.pomFileFinder = Objects.requireNonNull(pomFileFinder);
55+
this.pomModifier = Objects.requireNonNull(pomModifier);
56+
this.codeTFGenerator = Objects.requireNonNull(codeTFGenerator);
57+
}
58+
59+
/**
60+
* Execute the dependency update process for a specific project directory and set of dependencies.
61+
*
62+
* @param projectDir The project directory where the POM files are located.
63+
* @param file The specific POM file to update.
64+
* @param dependencies The list of new dependencies to be added.
65+
* @return A DependencyUpdateResult containing information about the update process.
66+
* @throws IOException If an I/O error occurs.
67+
* @throws XMLStreamException If an error occurs during XML stream processing.
68+
* @throws DocumentException If an error occurs while parsing the document.
69+
* @throws URISyntaxException If there is an issue with the URI syntax.
70+
*/
71+
@NotNull
72+
public DependencyUpdateResult execute(
73+
final Path projectDir, final Path file, final List<DependencyGAV> dependencies)
74+
throws IOException, XMLStreamException, DocumentException, URISyntaxException {
75+
if (isEmptyPomFile(projectDir, file)) {
76+
LOG.trace("Pom file was empty for {}", file);
77+
return DependencyUpdateResult.EMPTY_UPDATE;
78+
}
79+
80+
final Path pomFile = maybePomFile.get();
81+
final POMOperator pomOperator = new POMOperator(pomFile, projectDir);
82+
83+
changesets = new ArrayList<>();
84+
skippedDependencies = new ArrayList<>();
85+
injectedDependencies = new ArrayList<>();
86+
erroredFiles = new LinkedHashSet<>();
87+
foundDependenciesMapped = new AtomicReference<>(pomOperator.getAllFoundDependencies());
88+
LOG.trace("Beginning dependency set size: {}", foundDependenciesMapped.get().size());
89+
90+
dependencies.forEach(
91+
newDependencyGAV -> {
92+
try {
93+
94+
if (updateSkipDependencies(newDependencyGAV)) {
95+
LOG.trace("Found it -- skipping");
96+
return;
97+
}
98+
99+
final ProjectModel modifiedProjectModel = pomOperator.addDependency(newDependencyGAV);
100+
101+
if (modifiedProjectModel == null) {
102+
LOG.trace("POM file didn't need modification or it failed?");
103+
return;
104+
}
105+
106+
LOG.trace("Modified the pom -- writing it back");
107+
108+
modifyPomFiles(projectDir, modifiedProjectModel, newDependencyGAV);
109+
110+
final Collection<DependencyGAV> newDependencySet =
111+
pomOperator.getAllFoundDependencies();
112+
113+
LOG.trace("New dependency set size: {}", newDependencySet.size());
114+
115+
foundDependenciesMapped.set(newDependencySet);
116+
} catch (DocumentException | IOException | URISyntaxException | XMLStreamException e) {
117+
LOG.error("Unexpected problem getting on pom operator", e);
118+
throw new MavenProvider.DependencyUpdateException(
119+
"Failure while executing pom operator: ", e);
120+
}
121+
});
122+
123+
return DependencyUpdateResult.create(
124+
injectedDependencies, skippedDependencies, changesets, erroredFiles);
125+
}
126+
127+
private boolean isEmptyPomFile(final Path projectDir, final Path file) throws IOException {
128+
maybePomFile = pomFileFinder.findForFile(projectDir, file);
129+
return maybePomFile.isEmpty();
130+
}
131+
132+
private void modifyPomFiles(
133+
final Path projectDir,
134+
final ProjectModel modifiedProjectModel,
135+
final DependencyGAV newDependencyGAV) {
136+
Collection<POMDocument> allPomFiles = modifiedProjectModel.allPomFiles();
137+
LOG.trace("Found {} pom files -- {}", allPomFiles.size(), allPomFiles);
138+
139+
for (POMDocument aPomFile : allPomFiles) {
140+
final URI uri = getPomFileURI(aPomFile);
141+
142+
final Path path = Path.of(uri);
143+
144+
modifyDirtyPomFile(projectDir, path, aPomFile, newDependencyGAV);
145+
}
146+
}
147+
148+
private void modifyDirtyPomFile(
149+
final Path projectDir,
150+
final Path path,
151+
final POMDocument aPomFile,
152+
final DependencyGAV newDependencyGAV) {
153+
154+
if (!aPomFile.getDirty()) {
155+
LOG.trace("POM file {} wasn't dirty", path);
156+
return;
157+
}
158+
159+
LOG.trace("POM file {} was dirty", path);
160+
161+
try {
162+
final CodeTFChangesetEntry entry =
163+
codeTFGenerator.getChanges(projectDir, aPomFile, newDependencyGAV);
164+
pomModifier.modify(path, aPomFile.getResultPomBytes());
165+
LOG.trace("POM written!");
166+
injectedDependencies.add(newDependencyGAV);
167+
changesets.add(entry);
168+
} catch (IOException | UncheckedIOException exc) {
169+
LOG.error("Failed to write pom", exc);
170+
erroredFiles.add(path);
171+
}
172+
}
173+
174+
private URI getPomFileURI(final POMDocument aPomFile) {
175+
try {
176+
return aPomFile.getPomPath().toURI();
177+
} catch (URISyntaxException ex) {
178+
LOG.error("Unexpected problem getting pom URI", ex);
179+
throw new MavenProvider.DependencyUpdateException("Failure parsing URL: " + aPomFile, ex);
180+
}
181+
}
182+
183+
private boolean updateSkipDependencies(final DependencyGAV newDependencyGAV) {
184+
LOG.trace("Looking at injecting new dependency: {}", newDependencyGAV);
185+
final boolean foundIt =
186+
foundDependenciesMapped.get().stream().anyMatch(newDependencyGAV::equals);
187+
188+
if (foundIt) {
189+
skippedDependencies.add(newDependencyGAV);
190+
return true;
191+
}
192+
193+
return false;
194+
}
195+
}

0 commit comments

Comments
 (0)