Skip to content

Commit

Permalink
Merge pull request #19 from TechnologyBrewery/#18
Browse files Browse the repository at this point in the history
 #18: Add utilities for assisting with common migration logic
  • Loading branch information
cpointe-ibllanos authored Jun 28, 2024
2 parents 308a724 + fdc61d1 commit 0e4536b
Show file tree
Hide file tree
Showing 13 changed files with 498 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public class FooToBarMigration extends AbstractMigration {
}
```

#### Leveraging utilities classes
The following Java classes in `org.technologybrewery.baton.util` can be leveraged to easily implement common migration logic into your extension:
* `CommonUtils`
* `FileUtils`

### Configure Baton to use the migration
With a migration to apply, we both configure and tailor that use through a simple json file. This file can live anywhere
in Baton's classpath and is named `migrations.json` by default.
Expand Down
5 changes: 5 additions & 0 deletions baton-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<artifactId>slf4j-api</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>file-management</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.technologybrewery.baton.util;
import com.vdurmont.semver4j.Semver;

/**
* Common logic used when performing a migration.
*/
public final class CommonUtils {
private CommonUtils(){
}

/**
* Checks if version1 is less than version2 using the Semver4j library.
*
* @param version1
* @param version2
* @return isLessThanVersion - if true, version1 is less than version2.
*/
public static boolean isLessThanVersion(String version1, String version2) {
Semver sem = new Semver(version1);
return sem.isLowerThan(version2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package org.technologybrewery.baton.util;

import org.apache.commons.io.IOUtils;
import org.codehaus.plexus.util.StringUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.commons.lang3.StringUtils.SPACE;

/**
* Common file-related logic used when performing a migration.
*/
public final class FileUtils {
private FileUtils(){
}

/**
* Evaluates a file against a literal string and replaces each match with a specified replacement.
*
* @param file the File
* @param toReplace a string representing the text to replace
* @param replacement the replacement text to substitute the toReplace string
* @return a boolean set to true if at least one replacement was performed in the file
*/
public static boolean replaceLiteralInFile(File file, String toReplace, String replacement) throws IOException {
boolean replacedInFile = false;

if (file != null && file.exists()) {
String content = new String(Files.readAllBytes((file.toPath())));
content = content.replace(toReplace, replacement);
Files.write(file.toPath(), content.getBytes());
replacedInFile = true;
}
return replacedInFile;
}

/**
* Evaluates a file against a regex pattern and replaces the captured string(s) of each regex match
* with a specified replacement
*
* @param file the File
* @param regex a regex representing the text to replace, as a String
* @param replacement the replacement text to substitute the regex
* @return a boolean set to true if at least one replacement was performed in the file
*/
public static boolean replaceInFile(File file, String regex, String replacement) throws IOException {
boolean replacedInFile = false;
if (file != null && file.exists()) {
Charset charset = StandardCharsets.UTF_8;
String fileContent = new String(Files.readAllBytes(file.toPath()), charset);

Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(fileContent);
String newFileContent = matcher.replaceAll(replacement);
IOUtils.write(newFileContent, new FileOutputStream(file), charset);
replacedInFile = true;
}
return replacedInFile;
}

/**
* Evaluates a file against a regex pattern and replaces a substring of each regex match
* with a specified replacement.
*
* @param file the File
* @param regex a regex representing the text to replace a substring of
* @param substring the substring of the regex match that will be replaced
* @param replacement the replacement of the match substring
* @return a boolean set to true if at least one modification was performed in the file
*/
public static boolean modifyRegexMatchInFile(File file, String regex, String substring, String replacement) {
if (file == null || !file.exists()) {
return false;
}

boolean modified = false;

try {
Path path = file.toPath();
Charset charset = StandardCharsets.UTF_8;
List<String> resultLines = new ArrayList<>();

Pattern pattern = Pattern.compile(regex);
for (String line : Files.readAllLines(path, charset)) {
if (pattern.matcher(line).find()) {
line = line.replace(substring, replacement);
modified = true;
}
resultLines.add(line);
}
if (modified) {
Files.write(path, resultLines, charset);
}
} catch (IOException e) {
return false;
}
return modified;
}

/**
* Reads in the {@link File} object and returns a {@link List} of the contents.
*
* @param file {@link File} to read
* @return {@link List} of the contents
* @throws IOException
*/
public static List<String> readAllFileLines(File file) throws IOException {
return Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
}

/**
* Writes a {@link List} of the contents to the {@link File} object.
*
* @param file {@link File} to write
* @param contents {@link List} of the contents
* @throws IOException
*/
public static void writeFile(File file, List<String> contents) throws IOException {
Files.write(file.toPath(), contents, StandardCharsets.UTF_8);
}

/**
* Retrieves capture groups from a regular expression match inside a file.
*
* @see FileUtils#getRegExCaptureGroups(String, String)
* @param regex a regex containing capture groups, as a String
* @param file the file to search for matching capture groups
* @return An ArrayList of Strings representing each capture group in the regex that was matched
*/
public static List<String> getRegExCaptureGroups(String regex, File file) throws IOException {
String fileContent = "";
if (file != null && file.exists()) {
Charset charset = StandardCharsets.UTF_8;
fileContent = new String(Files.readAllBytes(file.toPath()), charset);
}
return StringUtils.isNotEmpty(fileContent) ? getRegExCaptureGroups(regex, fileContent) : new ArrayList<>();
}

/**
* Retrieves capture groups from a regular expression match inside a String.
*
* @param regex a regex containing capture groups, as a String
* @param input the string to search for matching capture groups
* @return An ArrayList of Strings representing each capture group in the regex that was matched
*/
public static List<String> getRegExCaptureGroups(String regex, String input) {
Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(input);

ArrayList<String> captured = new ArrayList<>();
if (matcher.find()) {
// Skip the 0 index -- the first match is always all capture groups put together
for (int i = 1; i <= matcher.groupCount(); ++i) {
captured.add(matcher.group(i));
}
}

return captured;
}

/**
* Evaluates a regex pattern against a file to determine if at least one regex match exists
*
* @param regex a regex pattern, as a String
* @param file the file to search for matching substrings
* @return true if there is at least one regex match, otherwise false
*/
public static boolean hasRegExMatch(String regex, File file) throws IOException {
String fileContent;
if (file != null && file.exists()) {
Charset charset = StandardCharsets.UTF_8;
fileContent = Files.readString(file.toPath(), charset);
return Pattern.compile(regex, Pattern.MULTILINE).matcher(fileContent).find();
} else {
return false;
}
}

/**
* Infers the indentation style from the given line.
*
* @param line the line to infer the indentation style from
* @param level the level of indentation of the line
* @return a single indent in the inferred style
*/
public static String getIndent(String line, int level) {
if( level < 1 ) {
return "";
}
int i = 0;
while (i < line.length() && Character.isWhitespace(line.charAt(i))) {
i++;
}
return line.substring(0, i/level);
}

/**
* Indent the values the desired number of tabs with a variable tab size.
*
* @param values List of {@link String} values to indent
* @param numSpaces number of spaces to indent
*/
public static void indentValues(List<String> values, int numSpaces) {
for (int i = 0; i < values.size(); i++) {
if (!values.get(i).isBlank()) {
values.set(i, SPACE.repeat(numSpaces) + values.get(i));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.technologybrewery.baton;

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.technologybrewery.baton.util.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;


public class UtilsTestSteps {

private static final File DEFAULT_TEST_DIRECTORY = new File("target/utils-test/");
private static final String DEFAULT_TEST_FILE_NAME = "text_file.txt";
File testFile;
Boolean match;
String inputAsString;
List<String> captureGroups;

@Given("I have a string containing {string}")
public void i_have_a_string_containing(String str) throws Throwable {
inputAsString = str;
}

@When("I use the regex {string} to retrieve capture groups")
public void i_use_the_regex_to_retrieve_capture_groups(String regex) throws IOException {
if (testFile != null) {
captureGroups = FileUtils.getRegExCaptureGroups(regex, testFile);
} else {
captureGroups = FileUtils.getRegExCaptureGroups(regex, inputAsString);
}
}

@Then("the size of the retrieved capture groups should be \"{int}\"")
public void the_size_of_the_retrieved_capture_groups_should_be(int groups) {
assertEquals(groups, captureGroups.size(),"Unexpected size for capture groups");
}

@Then("the capture groups should include")
public void the_capture_groups_should_include(List<String> expectedGroups) {
assertEquals(expectedGroups, captureGroups);
}

@Given("I have a file containing the string {string}")
public void i_have_a_file_containing_the_string(String string) throws IOException {
testFile = new File(DEFAULT_TEST_DIRECTORY, DEFAULT_TEST_FILE_NAME);
if (DEFAULT_TEST_DIRECTORY.mkdirs()) {
FileUtils.writeFile(testFile, Collections.singletonList(string));
}
}

@When("I use the regex {string} to substitute {string}")
public void i_use_the_regex_to_substitute(String regex, String substitute) throws IOException {
FileUtils.replaceInFile(testFile, regex, substitute);
}

@When("I use the literal {string} to substitute {string}")
public void iUseTheLiteralToSubstitute(String literal, String substitute) throws IOException {
FileUtils.replaceLiteralInFile(testFile, literal, substitute);
}

@Then("the file should now contain the string {string}")
public void the_file_should_now_contain_the_string(String string) throws IOException {
assertTrue(FileUtils.hasRegExMatch(string, testFile), String.format("File does not contain expected string %s",string));
}

@When("I use the regex {string} to substitute {string} with {string}")
public void i_use_the_regex_to_substitute_with(String regex, String target, String substitute) {
FileUtils.modifyRegexMatchInFile(testFile, regex, target, substitute);
}

@When("I use the regex {string} to search for matches in a file")
public void i_use_the_regex_to_search_for_matches_in_a_file(String regex) throws IOException {
match = FileUtils.hasRegExMatch(regex, testFile);

}

@Then("the match result should be \"{booleanValue}\"")
public void the_match_result_should_be(Boolean expected) {
assertEquals(expected, match, "RegEx file matcher did not return expected result");

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

package org.technologybrewery.baton.util;

import io.cucumber.java.ParameterType;

public class TestUtils {
@ParameterType( value = "true|True|TRUE|false|False|FALSE")
public Boolean booleanValue(String value){
return Boolean.valueOf(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Feature: Baton utilities can be used to assist with commonly used logic in a migration
Scenario: I can easily compare the semantic versioning of two strings
Loading

0 comments on commit 0e4536b

Please sign in to comment.