Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ares: Build Tool Configurator #26

Merged
merged 9 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 204 additions & 0 deletions src/main/java/de/tum/cit/ase/ares/api/util/DependencyManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package de.tum.cit.ase.ares.api.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class DependencyManager {

private static final Logger log = LoggerFactory.getLogger(DependencyManager.class);
private final String filePath;

public DependencyManager(String filePath) {
if (filePath == null || filePath.isEmpty()) {
throw new IllegalArgumentException("File path cannot be null or empty.");
}
this.filePath = filePath;
}

/**
* Adds a dependency to the file
*/
public void addDependency(String groupId, String artifactId, String version) throws IOException, ParserConfigurationException, TransformerException, SAXException {
if (filePath.trim().endsWith("pom.xml")) {
addDependencyToPom(groupId, artifactId, version);
} else if (filePath.trim().endsWith("build.gradle")) {
addDependencyToGradle(groupId, artifactId, version);
Comment on lines +58 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure consistent trimming of filePath across methods

In addDependency, you trim filePath before checking its ending, which is good practice to avoid issues with leading or trailing whitespace. However, in removeDependency, the trimming is missing. For consistency and to prevent potential bugs, consider trimming filePath in both methods.

Apply this diff to removeDependency:

- if (filePath.endsWith("pom.xml")) {
+ if (filePath.trim().endsWith("pom.xml")) {
    removeDependencyFromPom(groupId, artifactId);
} else if (filePath.endsWith("build.gradle")) {
+ if (filePath.trim().endsWith("build.gradle")) {
    removeDependencyFromGradle(groupId, artifactId);
}

Committable suggestion was skipped due to low confidence.

}
}

/**
* Removes a dependency from the file
*/
public void removeDependency(String groupId, String artifactId) throws Exception {
if (filePath.endsWith("pom.xml")) {
removeDependencyFromPom(groupId, artifactId);
} else if (filePath.endsWith("build.gradle")) {
removeDependencyFromGradle(groupId, artifactId);
}
}

/**
* Adds a dependency to the pom.xml file
*/
private void addDependencyToPom(String groupId, String artifactId, String version) throws ParserConfigurationException, IOException, SAXException, TransformerException {
File pomFile = new File(filePath);
var docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(pomFile);

Node dependenciesNode = doc.getElementsByTagName("dependencies").item(0);

if (dependenciesNode == null) {
throw new IllegalStateException("No dependencies tag found in pom.xml");
}

// Check if the dependency already exists
NodeList dependencies = dependenciesNode.getChildNodes();
for (int i = 0; i < dependencies.getLength(); i++) {
Node node = dependencies.item(i);

if (node.getNodeType() == Node.ELEMENT_NODE) {
Element dependency = (Element) node;
String existingGroupId = dependency.getElementsByTagName("groupId").item(0).getTextContent();
String existingArtifactId = dependency.getElementsByTagName("artifactId").item(0).getTextContent();

if (existingGroupId.equals(groupId) && existingArtifactId.equals(artifactId)) {
DependencyManager.log.info("Dependency {}:{} already exists in pom.xml", groupId, artifactId);
return; // Exit without adding duplicate
}
}
}

// Add the new dependency if not present
Element dependency = doc.createElement("dependency");

Element groupIdElement = doc.createElement("groupId");
groupIdElement.appendChild(doc.createTextNode(groupId));
dependency.appendChild(groupIdElement);

Element artifactIdElement = doc.createElement("artifactId");
artifactIdElement.appendChild(doc.createTextNode(artifactId));
dependency.appendChild(artifactIdElement);

Element versionElement = doc.createElement("version");
versionElement.appendChild(doc.createTextNode(version));
dependency.appendChild(versionElement);

dependenciesNode.appendChild(dependency);

saveXmlChanges(doc, pomFile);
}

/**
* Removes a dependency from the pom.xml file
*/
private void removeDependencyFromPom(String groupId, String artifactId) throws Exception {
var pomFile = new File(filePath);
var docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(pomFile);

NodeList dependencies = doc.getElementsByTagName("dependency");

for (int i = 0; i < dependencies.getLength(); i++) {
Node node = dependencies.item(i);

if (node.getNodeType() == Node.ELEMENT_NODE) {
Element dependency = (Element) node;
String depGroupId = dependency.getElementsByTagName("groupId").item(0).getTextContent();
String depArtifactId = dependency.getElementsByTagName("artifactId").item(0).getTextContent();

if (groupId.equals(depGroupId) && artifactId.equals(depArtifactId)) {
dependency.getParentNode().removeChild(dependency);
}
}
}

saveXmlChanges(doc, pomFile);
}

/**
* Adds a dependency to the build.gradle file
*/
private void addDependencyToGradle(String groupId, String artifactId, String version) throws IOException {
String dependencyLine = " implementation '" + groupId + ":" + artifactId + ":" + version + "'";
Path path = Paths.get(filePath);
List<String> lines = Files.readAllLines(path);

// Check if the dependency already exists
for (String line : lines) {
if (line.trim().contains("implementation '" + groupId + ":" + artifactId)) {
log.info("Dependency {}:{} already exists in build.gradle", groupId, artifactId);
return; // Exit without adding duplicate
}
}

// Find the dependencies block and add the new dependency
int dependenciesIndex = -1;
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).trim().equalsIgnoreCase("dependencies {")) {
dependenciesIndex = i;
break;
}
}
Comment on lines +196 to +200
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve robustness when detecting the dependencies block in Gradle files

The current check for the dependencies block using equalsIgnoreCase("dependencies {") may fail if there are variations in formatting, such as additional spaces, comments, or different cases. Consider using a more flexible approach to identify the dependencies block.

Apply this diff to enhance detection:

- if (lines.get(i).trim().equalsIgnoreCase("dependencies {")) {
+ if (lines.get(i).trim().startsWith("dependencies")) {
+     if (lines.get(i).contains("{")) {
+         dependenciesIndex = i;
+         break;
+     } else {
+         // Handle cases where '{' is on the next line
+         dependenciesIndex = i;
+     }
+ }

This modification checks if the line starts with dependencies and accounts for cases where the opening brace { is on the same line or the following line.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (lines.get(i).trim().equalsIgnoreCase("dependencies {")) {
dependenciesIndex = i;
break;
}
}
if (lines.get(i).trim().startsWith("dependencies")) {
if (lines.get(i).contains("{")) {
dependenciesIndex = i;
break;
} else {
// Handle cases where '{' is on the next line
dependenciesIndex = i;
}
}
}


if (dependenciesIndex != -1) {
lines.add(dependenciesIndex + 1, dependencyLine);
Files.write(path, lines);
} else {
throw new IOException("No dependencies block found in build.gradle");
}
}
Comment on lines +202 to +208
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle the absence of the dependencies block gracefully

If a build.gradle file lacks a dependencies block, the current implementation throws an IOException. To enhance usability, consider adding a new dependencies block to the file instead of throwing an exception.

Modify the code as follows:

if (dependenciesIndex != -1) {
    lines.add(dependenciesIndex + 1, dependencyLine);
} else {
    log.info("No dependencies block found. Adding a new dependencies block to build.gradle");
    lines.add("");
    lines.add("dependencies {");
    lines.add(dependencyLine);
    lines.add("}");
}
Files.write(path, lines);

This approach ensures that the dependency is added even if the dependencies block is initially absent.


/**
* Removes a dependency from the build.gradle file
*/
private void removeDependencyFromGradle(String groupId, String artifactId) throws IOException {
String dependencyPrefix = "implementation '" + groupId + ":" + artifactId + ":";
Path path = Paths.get(filePath);
List<String> lines = Files.readAllLines(path);
lines.removeIf(line -> line.trim().startsWith(dependencyPrefix));
Files.write(path, lines);
}

/**
* Saves the changes made to the XML document back to the file
*/
private void saveXmlChanges(Document doc, File file) throws TransformerException {
var transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(file);
transformer.transform(source, result);
}

/**
* Example usage!!!
*/
public static void main(String[] args) {
try {
DependencyManager manager = new DependencyManager("/home/sarps/IdeaProjects/Ares2/src/test/java/de/tum/cit/ase/ares/integration/testuser/subject/example/build/tools/pom.xml");
manager.addDependency("org.example", "example-artifact", "1.0.0");
// manager.removeDependency("org.example", "example-artifact");
} catch (Exception e) {
log.error(e.getLocalizedMessage());
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.tum.cit.ase.ares.integration.testuser;

import de.tum.cit.ase.ares.api.StrictTimeout;
import de.tum.cit.ase.ares.api.jupiter.Public;
import de.tum.cit.ase.ares.api.jupiter.PublicTest;
import de.tum.cit.ase.ares.api.localization.UseLocale;
import de.tum.cit.ase.ares.api.util.ProjectSourcesFinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Public
@UseLocale("en")
@StrictTimeout(5)
public class MavenConfigurationUser {

private static final Logger log = LoggerFactory.getLogger(MavenConfigurationUser.class);

@PublicTest
void testGetPomXmlPath() {
log.error(ProjectSourcesFinder.getPomXmlPath());
}
sarpsahinalp marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
plugins {
}

apply plugin: 'java'
sourceCompatibility = 17
version = '1.0.0'
compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'

repositories {
mavenCentral()
mavenLocal()
}

dependencies {
testImplementation 'de.tum.in.ase:artemis-java-test-sandbox:1.13.0'
implementation 'org.apache.commons:commons-lang3:3.14.0'

// testImplementation(':gradlexercise')
// testImplementation(':gradlexercise-Solution')
}

def assignmentSrcDir = "assignment/src"
def studentOutputDir = sourceSets.main.java.destinationDirectory.get()

sourceSets {
test {
java {
srcDir 'test'
}
resources {
srcDir 'test'
}
}


main {
java {
srcDirs = [assignmentSrcDir]
}
resources {
srcDirs = []
}
}
}

def forbiddenPackageFolders = [ //(2)
"$studentOutputDir/ch/qos/logback/",
"$studentOutputDir/com/github/javaparser/",
"$studentOutputDir/com/intellij/",
"$studentOutputDir/com/sun/",
"$studentOutputDir/de/tum/in/test/api/",
"$studentOutputDir/java/",
"$studentOutputDir/javax/",
"$studentOutputDir/jdk/",
"$studentOutputDir/net/jqwik/",
"$studentOutputDir/org/assertj/",
"$studentOutputDir/org/apache/",
"$studentOutputDir/org/eclipse/",
"$studentOutputDir/org/gradle/",
"$studentOutputDir/org/jacoco/",
"$studentOutputDir/org/json/",
"$studentOutputDir/org/junit/",
"$studentOutputDir/org/opentest4j/",
"$studentOutputDir/sun/",
"$studentOutputDir/worker/org/gradle/"
]
test {
doFirst { //(1)
for (String packageFolder in forbiddenPackageFolders) {
assert !file(packageFolder).exists(): "$packageFolder must not exist within the submission."
}
}
defaultCharacterEncoding = 'UTF-8'
testLogging.showStandardStreams = true
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.tum.cit.ase</groupId>
<artifactId>mavenexercise-Tests</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<name>mavenexercise Tests</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<argLine>-Dfile.encoding=UTF-8</argLine>
</properties>
<dependencies>
<dependency>
<!-- Comes with JUnit 5, AssertJ and Hamcrest. JUnit 4 (JUnit 5 Vintage) or jqwik need to be added explicitly -->
<groupId>de.tum.in.ase</groupId>
<artifactId>artemis-java-test-sandbox</artifactId>
<version>1.13.0</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/assignment/src</sourceDirectory>
<testSourceDirectory>${project.basedir}/test</testSourceDirectory>
<testResources>
<testResource>
<directory>${project.basedir}/test</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<id>enforce-no-student-code-in-trusted-packages</id>
<phase>process-classes</phase>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
<configuration>
<rules>
<requireFilesDontExist>
<files>
<file>${project.build.outputDirectory}/ch/qos/logback/</file>
<file>${project.build.outputDirectory}/com/github/javaparser/</file>
<file>${project.build.outputDirectory}/com/intellij/</file>
<file>${project.build.outputDirectory}/com/sun/</file>
<file>${project.build.outputDirectory}/de/tum/in/test/api/</file>
<file>${project.build.outputDirectory}/java/</file>
<file>${project.build.outputDirectory}/javax/</file>
<file>${project.build.outputDirectory}/jdk/</file>
<file>${project.build.outputDirectory}/net/jqwik/</file>
<file>${project.build.outputDirectory}/org/apache/</file>
<file>${project.build.outputDirectory}/org/assertj/</file>
<file>${project.build.outputDirectory}/org/eclipse/</file>
<file>${project.build.outputDirectory}/org/jacoco/</file>
<file>${project.build.outputDirectory}/org/json/</file>
<file>${project.build.outputDirectory}/org/junit/</file>
<file>${project.build.outputDirectory}/org/opentest4j/</file>
<file>${project.build.outputDirectory}/sun/</file>
<file>${project.build.outputDirectory}/org/gradle/</file>
<file>${project.build.outputDirectory}/worker/org/gradle/</file>
</files>
</requireFilesDontExist>
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>
Loading