Skip to content

Commit 2be735f

Browse files
authored
Command chain to insert only a dependency (#232)
Adding new method `insertDependency` to POMOperator to only insert a dependency if it is not present in the pom
1 parent 1ddddd2 commit 2be735f

File tree

8 files changed

+364
-26
lines changed

8 files changed

+364
-26
lines changed

plugins/codemodder-plugin-maven/src/main/java/io/codemodder/plugins/maven/operator/CommandChain.java

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,19 @@ class CommandChain {
1818
/** Internal ArrayList of the Commands */
1919
private List<Command> commandList;
2020

21-
private CommandChain(Command... commands) {
22-
this.commandList = new ArrayList<>(Arrays.asList(commands));
21+
private static List<Command> COMMON_COMMANDS =
22+
List.of(
23+
// Validation commands
24+
CheckDependencyPresent.getInstance(),
25+
CheckParentPackaging.getInstance(),
26+
// Format commands
27+
new FormatCommand(),
28+
new DiscardFormatCommand(),
29+
// Multipom command
30+
new CompositeDependencyManagement());
31+
32+
private CommandChain(List<Command> commands) {
33+
this.commandList = commands;
2334
}
2435

2536
/**
@@ -81,16 +92,26 @@ public boolean execute(ProjectModel c)
8192
*
8293
* @return A pre-configured Chain for modifying a POM.
8394
*/
84-
public static CommandChain createForModify() {
85-
return new CommandChain(
86-
CheckDependencyPresent.getInstance(),
87-
CheckParentPackaging.getInstance(),
88-
new FormatCommand(),
89-
new DiscardFormatCommand(),
90-
new CompositeDependencyManagement(),
91-
SimpleUpgrade.getInstance(),
92-
SimpleDependencyManagement.getInstance(),
93-
SimpleInsert.getInstance());
95+
public static CommandChain modifyDependency() {
96+
final List<Command> modifyCommands = new ArrayList<>(COMMON_COMMANDS);
97+
modifyCommands.addAll(
98+
List.of(
99+
SimpleUpgrade.getInstance(),
100+
SimpleDependencyManagement.getInstance(),
101+
new SimpleInsert(false)));
102+
return new CommandChain(modifyCommands);
103+
}
104+
105+
/**
106+
* Creates a pre-configured Chain with default commands for only inserting a dependency onto a
107+
* POM.
108+
*
109+
* @return A pre-configured Chain.
110+
*/
111+
public static CommandChain insertDependency() {
112+
final List<Command> insertCommands = new ArrayList<>(COMMON_COMMANDS);
113+
insertCommands.add(new SimpleInsert(true));
114+
return new CommandChain(insertCommands);
94115
}
95116

96117
private static CommandChain filterByQueryType(
@@ -122,7 +143,7 @@ private static CommandChain filterByQueryType(
122143
"Unable to load any available strategy for " + queryType.name());
123144
}
124145

125-
return new CommandChain(commands.toArray(new Command[0]));
146+
return new CommandChain(commands);
126147
}
127148

128149
/**

plugins/codemodder-plugin-maven/src/main/java/io/codemodder/plugins/maven/operator/POMOperator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ public Collection<DependencyGAV> getAllFoundDependencies()
8989
*/
9090
static boolean modify(ProjectModel projectModel)
9191
throws URISyntaxException, IOException, XMLStreamException {
92-
return CommandChain.createForModify().execute(projectModel);
92+
return CommandChain.modifyDependency().execute(projectModel);
93+
}
94+
95+
static boolean insert(ProjectModel projectModel)
96+
throws URISyntaxException, IOException, XMLStreamException {
97+
return CommandChain.insertDependency().execute(projectModel);
9398
}
9499

95100
/**

plugins/codemodder-plugin-maven/src/main/java/io/codemodder/plugins/maven/operator/SimpleInsert.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,14 @@
1010
*/
1111
class SimpleInsert implements Command {
1212

13-
private static final SimpleInsert INSTANCE = new SimpleInsert();
14-
15-
private SimpleInsert() {}
13+
private final boolean validateDependencyExistence;
1614

1715
/**
18-
* Gets the singleton instance of SimpleInsert.
19-
*
20-
* @return The singleton instance of SimpleInsert.
16+
* @param validateDependencyExistence is true when we only want to this command alone to insert
17+
* dependencies in a CommandChain for dependency existence validations
2118
*/
22-
public static SimpleInsert getInstance() {
23-
return INSTANCE;
19+
SimpleInsert(final boolean validateDependencyExistence) {
20+
this.validateDependencyExistence = validateDependencyExistence;
2421
}
2522

2623
/**
@@ -32,6 +29,11 @@ public static SimpleInsert getInstance() {
3229
*/
3330
@Override
3431
public boolean execute(ProjectModel pm) {
32+
33+
if (validateDependencyExistence && checkDependencyExists(pm)) {
34+
return true;
35+
}
36+
3537
List<Node> dependencyManagementNodeList =
3638
Util.selectXPathNodes(pm.getPomFile().getResultPom(), "/m:project/m:dependencyManagement");
3739

@@ -74,6 +76,16 @@ public boolean execute(ProjectModel pm) {
7476
return true;
7577
}
7678

79+
private boolean checkDependencyExists(final ProjectModel pm) {
80+
String lookupExpressionForDependency =
81+
Util.buildLookupExpressionForDependency(pm.getDependency());
82+
83+
List<Node> matchedDependencies =
84+
Util.selectXPathNodes(pm.getPomFile().getResultPom(), lookupExpressionForDependency);
85+
86+
return !matchedDependencies.isEmpty();
87+
}
88+
7789
/** Creates the XML Elements for a given dependency */
7890
private Element appendCoordinates(Element dependenciesNode, ProjectModel c) {
7991
Element dependencyNode =

plugins/codemodder-plugin-maven/src/test/java/io/codemodder/plugins/maven/operator/AbstractTestBase.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.nio.file.Path;
1313
import java.nio.file.Paths;
1414
import java.util.LinkedList;
15+
import javax.xml.stream.XMLStreamException;
1516
import org.dom4j.Document;
1617
import org.dom4j.io.SAXReader;
1718
import org.junit.Assert;
@@ -24,6 +25,11 @@
2425
class AbstractTestBase {
2526
protected static final Logger LOGGER = LoggerFactory.getLogger(POMOperatorTest.class);
2627

28+
static enum OperationType {
29+
MODIFY,
30+
INSERT
31+
}
32+
2733
protected File getResource(String name) throws URISyntaxException {
2834
return new File(this.getClass().getResource(name).toURI());
2935
}
@@ -34,17 +40,22 @@ File getResourceAsFile(String name) throws URISyntaxException {
3440
}
3541

3642
protected ProjectModel gwt(String name, ProjectModelFactory pmf) throws Exception {
37-
return gwt(name, pmf.build());
43+
return gwt(name, pmf.build(), OperationType.MODIFY);
44+
}
45+
46+
protected ProjectModel performInsert(String name, ProjectModelFactory pmf) throws Exception {
47+
return gwt(name, pmf.build(), OperationType.INSERT);
3848
}
3949

40-
protected ProjectModel gwt(String testName, ProjectModel context) throws Exception {
50+
protected ProjectModel gwt(
51+
String testName, ProjectModel context, final OperationType operationType) throws Exception {
4152

4253
String resultFile = "pom-" + testName + "-result.xml";
4354
URL resource = AbstractTestBase.class.getClass().getResource(resultFile);
4455

4556
if (resource != null) {
4657
Document outcome = new SAXReader().read(resource);
47-
POMOperator.modify(context);
58+
performPomOperation(operationType, context);
4859

4960
Assert.assertFalse(
5061
"Expected and outcome have differences",
@@ -56,7 +67,7 @@ protected ProjectModel gwt(String testName, ProjectModel context) throws Excepti
5667
AbstractTestBase.class.getPackage().getName().replace(".", "/"),
5768
resultFile);
5869

59-
POMOperator.modify(context);
70+
performPomOperation(operationType, context);
6071

6172
byte[] resultPomBytes = context.getPomFile().getResultPomBytes();
6273

@@ -69,6 +80,16 @@ protected ProjectModel gwt(String testName, ProjectModel context) throws Excepti
6980
return context;
7081
}
7182

83+
private void performPomOperation(
84+
final OperationType operationType, final ProjectModel projectModel)
85+
throws XMLStreamException, URISyntaxException, IOException {
86+
switch (operationType) {
87+
case INSERT -> POMOperator.insert(projectModel);
88+
case MODIFY -> POMOperator.modify(projectModel);
89+
default -> throw new IllegalArgumentException("Invalid operation type: " + operationType);
90+
}
91+
}
92+
7293
static Diff getXmlDifferences(Document original, Document modified) {
7394
String originalDoc = original.asXML();
7495
String modifiedDoc = modified.asXML();

plugins/codemodder-plugin-maven/src/test/java/io/codemodder/plugins/maven/operator/POMOperatorTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,4 +519,68 @@ void testCaseWithEmptyElementFromCustomer() throws Exception {
519519
resultPomAsString.contains(
520520
" <DependencyConvergence></DependencyConvergence>"));
521521
}
522+
523+
@Test
524+
void insert_should_fail_because_dependency_exists() throws Exception {
525+
Dependency dependencyToUpgrade =
526+
new Dependency("org.dom4j", "dom4j", "2.0.2", null, null, null);
527+
528+
ProjectModel context =
529+
performInsert(
530+
"pom-case-3-insert-fails",
531+
ProjectModelFactory.load(POMOperatorTest.class.getResource("pom-case-3.xml"))
532+
.withDependency(dependencyToUpgrade)
533+
.withSkipIfNewer(true));
534+
535+
Assert.assertFalse("Original POM File is Dirty", context.getPomFile().getDirty());
536+
537+
List<Dependency> resolvedDeps =
538+
POMOperator.queryDependency(
539+
ProjectModelFactory.load(
540+
POMOperatorTest.class.getResource("pom-pom-case-3-insert-fails-result.xml"))
541+
.withSafeQueryType()
542+
.build())
543+
.stream()
544+
.toList();
545+
546+
final List<Dependency> dom4jDependency =
547+
resolvedDeps.stream()
548+
.filter(dependency -> dependency.matchesWithoutConsideringVersion(dependencyToUpgrade))
549+
.toList();
550+
551+
Assert.assertTrue("only one dom4j dependency exists", dom4jDependency.size() == 1);
552+
Assert.assertTrue(
553+
"dependency doesn't match because of version",
554+
!dom4jDependency.get(0).equals(dependencyToUpgrade));
555+
}
556+
557+
@Test
558+
void insert_should_perform_gracefully() throws Exception {
559+
Dependency dependencyToUpgrade =
560+
new Dependency("org.dom4j", "dom4j", "1.0.0", null, null, null);
561+
562+
ProjectModel context =
563+
performInsert(
564+
"inserts-dependency",
565+
ProjectModelFactory.load(
566+
POMOperatorTest.class.getResource("pom-without-dependencies.xml"))
567+
.withDependency(dependencyToUpgrade)
568+
.withUseProperties(true)
569+
.withSkipIfNewer(true));
570+
571+
Assert.assertTrue("Original POM File is Dirty", context.getPomFile().getDirty());
572+
573+
List<Dependency> resolvedDeps =
574+
POMOperator.queryDependency(
575+
ProjectModelFactory.load(
576+
POMOperatorTest.class.getResource("pom-inserts-dependency-result.xml"))
577+
.withSafeQueryType()
578+
.build())
579+
.stream()
580+
.toList();
581+
582+
Assert.assertTrue("Must have one dependencies", 1 == resolvedDeps.size());
583+
Assert.assertTrue(
584+
"dom4j is the desired dependency", resolvedDeps.get(0).equals(dependencyToUpgrade));
585+
}
522586
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns="http://maven.apache.org/POM/4.0.0"
5+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
6+
<modelVersion>4.0.0</modelVersion>
7+
8+
<groupId>br.com.ingenieux</groupId>
9+
<artifactId>pom-operator</artifactId>
10+
<version>0.0.1-SNAPSHOT</version>
11+
12+
<properties>
13+
<sample.version>0.0.1-SNAPSHOT</sample.version>
14+
<versions.dom4j>1.0.0</versions.dom4j>
15+
</properties>
16+
17+
<profiles>
18+
<profile>
19+
<id>test-profile</id>
20+
<properties>
21+
<sample.version>1.0.1</sample.version>
22+
</properties>
23+
</profile>
24+
</profiles>
25+
<dependencyManagement>
26+
<dependencies>
27+
<dependency>
28+
<groupId>org.dom4j</groupId>
29+
<artifactId>dom4j</artifactId>
30+
<version>${versions.dom4j}</version>
31+
</dependency>
32+
</dependencies>
33+
</dependencyManagement>
34+
<dependencies>
35+
<dependency>
36+
<groupId>org.dom4j</groupId>
37+
<artifactId>dom4j</artifactId>
38+
</dependency>
39+
</dependencies>
40+
</project>

0 commit comments

Comments
 (0)