Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
56 changes: 23 additions & 33 deletions SpecsUtils/src/pt/up/fe/specs/util/SpecsStrings.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@

package pt.up.fe.specs.util;

import pt.up.fe.specs.util.collections.MultiMap;
import pt.up.fe.specs.util.exceptions.OverflowException;
import pt.up.fe.specs.util.parsing.LineParser;
import pt.up.fe.specs.util.utilities.LineStream;
import pt.up.fe.specs.util.utilities.StringLines;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.ParameterizedType;
Expand All @@ -22,17 +28,7 @@
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.function.Supplier;
Expand All @@ -41,12 +37,6 @@
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;

import pt.up.fe.specs.util.collections.MultiMap;
import pt.up.fe.specs.util.exceptions.OverflowException;
import pt.up.fe.specs.util.parsing.LineParser;
import pt.up.fe.specs.util.utilities.LineStream;
import pt.up.fe.specs.util.utilities.StringLines;

/**
* Utility methods for parsing of values which, instead of throwing an
* exception, return a default value if a parsing
Expand All @@ -67,6 +57,7 @@ public class SpecsStrings {
}

private static final Map<TimeUnit, String> TIME_UNIT_SYMBOL;

static {
TIME_UNIT_SYMBOL = new HashMap<>();
SpecsStrings.TIME_UNIT_SYMBOL.put(TimeUnit.DAYS, "days");
Expand Down Expand Up @@ -94,7 +85,7 @@ public static boolean isPrintableChar(char c) {
*
* @param integer a String representing an integer.
* @return the intenger represented by the string, or 0 if it couldn't be
* parsed.
* parsed.
*/
public static int parseInt(String integer) {
int intResult = 0;
Expand All @@ -113,7 +104,7 @@ public static int parseInt(String integer) {
*
* @param integer a String representing an integer.
* @return the integer represented by the string, or null if it couldn't be
* parsed.
* parsed.
*/
public static Integer parseInteger(String integer) {
try {
Expand All @@ -128,7 +119,7 @@ public static Integer parseInteger(String integer) {
*
* @param doublefloat a String representing a double.
* @return the double represented by the string, or null if it couldn't be
* parsed.
* parsed.
*/
public static Optional<Double> valueOfDouble(String doublefloat) {
try {
Expand All @@ -147,7 +138,7 @@ public static short parseShort(String s) {
*
* @param afloat a String representing a float.
* @return the float represented by the string, or null if it couldn't be
* parsed.
* parsed.
*/
public static Float parseFloat(String afloat) {
return parseFloat(afloat, true);
Expand Down Expand Up @@ -176,7 +167,7 @@ public static Float parseFloat(String afloat, boolean isStrict) {
*
* @param aDouble a String representing a double.
* @return the double represented by the string, or null if it couldn't be
* parsed.
* parsed.
*/
public static Double parseDouble(String aDouble) {
return parseDouble(aDouble, true);
Expand All @@ -188,7 +179,7 @@ public static Double parseDouble(String aDouble) {
*
* @param aDouble a String representing a double.
* @return the double represented by the string, or null if it couldn't be
* parsed.
* parsed.
*/
public static Double parseDouble(String aDouble, boolean isStrict) {
try {
Expand Down Expand Up @@ -244,7 +235,7 @@ public static BigInteger parseBigInteger(String intNumber) {
*
* @param booleanString a String representing a Boolean.
* @return the Boolean represented by the string, or null if it couldn't be
* parsed.
* parsed.
*/
public static Boolean parseBoolean(String booleanString) {
booleanString = booleanString.toLowerCase();
Expand Down Expand Up @@ -320,7 +311,7 @@ public static String toHexString(long decimalLong, int stringSize) {
/**
* @param string a string
* @return the index of the first whitespace found in the given String, or -1 if
* none is found.
* none is found.
*/
public static int indexOfFirstWhitespace(String string) {
return indexOf(string, Character::isWhitespace, false);
Expand Down Expand Up @@ -1587,13 +1578,12 @@ public static String normalizeFileContents(String fileContents, boolean ignoreEm
// Normalize new lines
String normalizedString = fileContents.replaceAll("\r\n", "\n");

// Remove empty lines
if (ignoreEmptyLines) {
normalizedString = StringLines.getLines(normalizedString).stream()
.map(String::trim)
.filter(line -> !line.isEmpty())
.collect(Collectors.joining("\n"));
}
// Trim strings
normalizedString = StringLines.getLines(normalizedString).stream()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This meaningful changes the behaviour of this function. Check if all uses in the specs-feup org use this with ignoreEmptyLines on. If that is not the case, there should be a path without filtering the empty lines.
It's a 2 line change by not applying the filter to the stream, conditionally.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nevermind. Reread the new filter function. Go ahead and merge

.map(String::trim)
// Remove empty lines
.filter(line -> !(ignoreEmptyLines && line.isEmpty()))
.collect(Collectors.joining("\n"));
Comment on lines +1582 to +1586

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Added a NormalizeFileContents nested test class in SpecsStringsTest.java with 8 targeted tests covering:

  • Trimming whitespace-only lines (both with and without ignoreEmptyLines)
  • Preserving empty lines when ignoreEmptyLines=false
  • Removing empty lines when ignoreEmptyLines=true
  • Trailing empty line behavior (using double newlines, since BufferedReader.readLine() absorbs a single trailing newline)
  • CRLF normalization
  • Single-argument overload delegation

Committed in the latest push.


return normalizedString;

Expand Down Expand Up @@ -1683,7 +1673,7 @@ public static String toDecimal(long number) {
* Splits the given String according to a separator, and removes blank String
* that can be created from the splitting.
*
* @param strip if true, strips each splitted String
* @param strip if true, strips each splitted String
*/
public static List<String> splitNonEmpty(String string, String separator, boolean strip) {
return Arrays.stream(string.split(separator))
Expand Down
65 changes: 65 additions & 0 deletions SpecsUtils/test/pt/up/fe/specs/util/SpecsStringsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,71 @@ void testParseDoubleStrict() {
}
}

@Nested
@DisplayName("normalizeFileContents")
class NormalizeFileContents {

@Test
@DisplayName("trims whitespace-only lines to empty strings")
void testWhitespaceOnlyLinesAreTrimmed() {
String input = "hello\n \nworld";
// ignoreEmptyLines=false: whitespace-only lines become empty lines, preserved
assertThat(SpecsStrings.normalizeFileContents(input, false)).isEqualTo("hello\n\nworld");
}

@Test
@DisplayName("preserves empty lines when ignoreEmptyLines is false")
void testPreservesEmptyLinesWhenNotIgnoring() {
String input = "line1\n\nline2\n\nline3";
assertThat(SpecsStrings.normalizeFileContents(input, false)).isEqualTo("line1\n\nline2\n\nline3");
}

@Test
@DisplayName("removes empty lines when ignoreEmptyLines is true")
void testRemovesEmptyLinesWhenIgnoring() {
String input = "line1\n\nline2\n\nline3";
assertThat(SpecsStrings.normalizeFileContents(input, true)).isEqualTo("line1\nline2\nline3");
}

@Test
@DisplayName("removes whitespace-only lines when ignoreEmptyLines is true")
void testRemovesWhitespaceOnlyLinesWhenIgnoring() {
String input = "line1\n \nline2";
assertThat(SpecsStrings.normalizeFileContents(input, true)).isEqualTo("line1\nline2");
}

@Test
@DisplayName("preserves trailing empty line (from double newline) when ignoreEmptyLines is false")
void testTrailingEmptyLinePreservedWhenNotIgnoring() {
// A double trailing newline produces an empty last line that should be preserved
String input = "line1\nline2\n\n";
assertThat(SpecsStrings.normalizeFileContents(input, false)).isEqualTo("line1\nline2\n");
}

@Test
@DisplayName("removes trailing empty line (from double newline) when ignoreEmptyLines is true")
void testTrailingEmptyLineRemovedWhenIgnoring() {
// A double trailing newline produces an empty last line that should be removed
String input = "line1\nline2\n\n";
assertThat(SpecsStrings.normalizeFileContents(input, true)).isEqualTo("line1\nline2");
}

@Test
@DisplayName("normalizes CRLF line endings")
void testNormalizesCrLf() {
String input = "line1\r\nline2\r\nline3";
assertThat(SpecsStrings.normalizeFileContents(input, false)).isEqualTo("line1\nline2\nline3");
}

@Test
@DisplayName("single-argument overload delegates to ignoreEmptyLines=false")
void testSingleArgOverload() {
String input = "line1\n\nline2";
assertThat(SpecsStrings.normalizeFileContents(input))
.isEqualTo(SpecsStrings.normalizeFileContents(input, false));
}
}

// Static data providers for parameterized tests
static List<Arguments> validIntegerInputs() {
return Arrays.asList(
Expand Down