diff --git a/core/src/main/java/io/cucumber/core/feature/Encoding.java b/core/src/main/java/io/cucumber/core/feature/EncodingParser.java similarity index 85% rename from core/src/main/java/io/cucumber/core/feature/Encoding.java rename to core/src/main/java/io/cucumber/core/feature/EncodingParser.java index 86252a9d7f..6c636f4feb 100644 --- a/core/src/main/java/io/cucumber/core/feature/Encoding.java +++ b/core/src/main/java/io/cucumber/core/feature/EncodingParser.java @@ -1,5 +1,6 @@ package io.cucumber.core.feature; +import io.cucumber.core.exception.CucumberException; import io.cucumber.core.resource.Resource; import java.io.BufferedReader; @@ -13,9 +14,10 @@ import static java.util.Locale.ROOT; /** - * Utilities for reading the encoding of a file. + * Parser for the {@code # encoding: } header. Will reload the file + * in the specified encoding if not UTF-8. */ -final class Encoding { +final class EncodingParser { private static final Pattern COMMENT_OR_EMPTY_LINE_PATTERN = Pattern.compile("^\\s*#|^\\s*$"); private static final Pattern ENCODING_PATTERN = Pattern.compile("^\\s*#\\s*encoding\\s*:\\s*([0-9a-zA-Z\\-]+)", @@ -23,7 +25,7 @@ final class Encoding { private static final String DEFAULT_ENCODING = UTF_8.name(); private static final String UTF_8_BOM = "\uFEFF"; - static String readFile(Resource resource) throws RuntimeException, IOException { + String parse(Resource resource) { String source = read(resource, DEFAULT_ENCODING); // Remove UTF8 BOM encoded in first bytes if (source.startsWith(UTF_8_BOM)) { @@ -36,7 +38,7 @@ static String readFile(Resource resource) throws RuntimeException, IOException { return source; } - private static String read(Resource resource, String encoding) throws IOException { + private static String read(Resource resource, String encoding) { char[] buffer = new char[2 * 1024]; final StringBuilder out = new StringBuilder(); try ( @@ -47,6 +49,8 @@ private static String read(Resource resource, String encoding) throws IOExceptio while ((read = reader.read(buffer, 0, buffer.length)) > 0) { out.append(buffer, 0, read); } + } catch (IOException e) { + throw new CucumberException("Failed to read resource:" + resource.getUri(), e); } return out.toString(); } diff --git a/core/src/main/java/io/cucumber/core/feature/FeatureParser.java b/core/src/main/java/io/cucumber/core/feature/FeatureParser.java index 5be70487e8..0cb96b4907 100644 --- a/core/src/main/java/io/cucumber/core/feature/FeatureParser.java +++ b/core/src/main/java/io/cucumber/core/feature/FeatureParser.java @@ -1,10 +1,8 @@ package io.cucumber.core.feature; -import io.cucumber.core.exception.CucumberException; import io.cucumber.core.gherkin.Feature; import io.cucumber.core.resource.Resource; -import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Collections; @@ -21,6 +19,9 @@ public final class FeatureParser { + private final EncodingParser encodingParser = new EncodingParser(); + private final LanguageParser languageParser = new LanguageParser(); + private final Supplier idGenerator; public FeatureParser(Supplier idGenerator) { @@ -30,7 +31,10 @@ public FeatureParser(Supplier idGenerator) { public Optional parseResource(Resource resource) { requireNonNull(resource); URI uri = resource.getUri(); - String source = read(resource); + + String encodingParsed = encodingParser.parse(resource); + String source = languageParser.parse(encodingParsed); + ServiceLoader services = ServiceLoader .load(io.cucumber.core.gherkin.FeatureParser.class); Iterator iterator = services.iterator(); @@ -43,12 +47,4 @@ public Optional parseResource(Resource resource) { return Collections.max(parser, version).parse(uri, source, idGenerator); } - private static String read(Resource resource) { - try { - return Encoding.readFile(resource); - } catch (IOException e) { - throw new CucumberException("Failed to read resource:" + resource.getUri(), e); - } - } - } diff --git a/core/src/main/java/io/cucumber/core/feature/LanguageParser.java b/core/src/main/java/io/cucumber/core/feature/LanguageParser.java new file mode 100644 index 0000000000..570daa2687 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/feature/LanguageParser.java @@ -0,0 +1,26 @@ +package io.cucumber.core.feature; + +import io.cucumber.core.logging.Logger; +import io.cucumber.core.logging.LoggerFactory; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parser for the {@code # language: } header. Replaces changed ISO + * codes for backwards compatibility. + */ +class LanguageParser { + private static final Logger log = LoggerFactory.getLogger(FeatureParser.class); + private static final Pattern LANGUAGE_PATTERN = Pattern.compile("^(\\s*#\\s*language\\s*:\\s*)tl"); + + String parse(String feature) { + Matcher matcher = LANGUAGE_PATTERN.matcher(feature); + if (matcher.find()) { + log.warn( + () -> "The ISO 639-1 code for Telugu was changed from tl to te. Please use '# language: te' in your feature files."); + return matcher.replaceFirst("$1te"); + } + return feature; + } +} diff --git a/core/src/test/java/io/cucumber/core/feature/EncodingParserTest.java b/core/src/test/java/io/cucumber/core/feature/EncodingParserTest.java new file mode 100644 index 0000000000..2a0e3cca4d --- /dev/null +++ b/core/src/test/java/io/cucumber/core/feature/EncodingParserTest.java @@ -0,0 +1,44 @@ +package io.cucumber.core.feature; + +import io.cucumber.core.resource.Resource; +import org.junit.jupiter.api.Test; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +class EncodingParserTest { + + private final EncodingParser parser = new EncodingParser(); + + @Test + void test_utf8_bom_encode() throws RuntimeException, IOException { + Resource resource = resource("src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature"); + assertFalse(parser.parse(resource).startsWith("\uFEFF"), "UTF-8 BOM encoding not removed."); + } + + @Test + void test_utf8_encode() throws RuntimeException, IOException { + Resource resource = resource("src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature"); + assertFalse(parser.parse(resource).startsWith("\uFEFF"), "UTF-8 BOM encoding should not be present."); + } + + private Resource resource(String name) { + Resource resource = new Resource() { + @Override + public URI getUri() { + return null; + } + + @Override + public InputStream getInputStream() throws IOException { + + return new FileInputStream(name); + } + }; + return resource; + } +} diff --git a/core/src/test/java/io/cucumber/core/feature/EncodingTest.java b/core/src/test/java/io/cucumber/core/feature/EncodingTest.java deleted file mode 100644 index 79b1deb14c..0000000000 --- a/core/src/test/java/io/cucumber/core/feature/EncodingTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.cucumber.core.feature; - -import io.cucumber.core.resource.Resource; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.io.FileInputStream; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class EncodingTest { - - @Mock - private Resource resource; - - @Test - void test_utf8_bom_encode() throws RuntimeException, IOException { - when(resource.getInputStream()).thenReturn( - new FileInputStream("src/test/resources/io/cucumber/core/feature/UTF_8_BOM_Encoded.feature")); - assertFalse(Encoding.readFile(resource).startsWith("\uFEFF"), "UTF-8 BOM encoding not removed."); - } - - @Test - void test_utf8_encode() throws RuntimeException, IOException { - when(resource.getInputStream()) - .thenReturn(new FileInputStream("src/test/resources/io/cucumber/core/feature/UTF_8_Encoded.feature")); - assertFalse(Encoding.readFile(resource).startsWith("\uFEFF"), "UTF-8 BOM encoding should not be present."); - } - -} diff --git a/core/src/test/java/io/cucumber/core/feature/FeatureParserTest.java b/core/src/test/java/io/cucumber/core/feature/FeatureParserTest.java new file mode 100644 index 0000000000..9d163ea2ba --- /dev/null +++ b/core/src/test/java/io/cucumber/core/feature/FeatureParserTest.java @@ -0,0 +1,52 @@ +package io.cucumber.core.feature; + +import io.cucumber.core.gherkin.Feature; +import io.cucumber.core.resource.Resource; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.UUID; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsEmptyIterable.emptyIterable; +import static org.junit.jupiter.api.Assertions.*; + +class FeatureParserTest { + + FeatureParser featureParser = new FeatureParser(UUID::randomUUID); + + @Test + void renamesDeprecatedTeToTl() { + Feature feature = featureParser.parseResource(resource("" + + "# language: tl\n" + + "గుణము: Test feature\n" + + " ఉదాహరణ: Test scenario\n" + + " చెప్పబడినది some text\n")).get(); + assertThat(feature.getPickles().get(0).getLanguage(), is("te")); + } + + private Resource resource(String feature) { + return new Resource() { + @Override + public URI getUri() { + return URI.create("file:test.feature"); + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(feature.getBytes(UTF_8)); + } + }; + } + +}