From 5ab57f817fcacdf9b373e8528c000eb7fae9ec20 Mon Sep 17 00:00:00 2001 From: David Walluck Date: Sat, 25 Apr 2020 03:49:00 -0400 Subject: [PATCH] Add configurable EOL specifier (#27) * Add configurable EOL specifier * Document where LineEnding.java and LineEndingTest.java come from This closes #2 --- .gitignore | 2 + .../java/net/revelc/code/impsort/Grouper.java | 8 +- .../java/net/revelc/code/impsort/ImpSort.java | 44 ++++++---- .../java/net/revelc/code/impsort/Import.java | 11 ++- .../net/revelc/code/impsort/LineEnding.java | 66 ++++++++++++++ .../java/net/revelc/code/impsort/Result.java | 29 ++++++- .../maven/plugin/AbstractImpSortMojo.java | 19 +++- .../net/revelc/code/impsort/ImpSortTest.java | 33 ++++--- .../revelc/code/impsort/LineEndingTest.java | 86 +++++++++++++++++++ 9 files changed, 256 insertions(+), 42 deletions(-) create mode 100644 src/main/java/net/revelc/code/impsort/LineEnding.java create mode 100644 src/test/java/net/revelc/code/impsort/LineEndingTest.java diff --git a/.gitignore b/.gitignore index dedbb87..9ea8167 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ /.classpath /.project /.settings/ +/.idea/ +/*.iml diff --git a/src/main/java/net/revelc/code/impsort/Grouper.java b/src/main/java/net/revelc/code/impsort/Grouper.java index 36ea4bd..a6b9823 100644 --- a/src/main/java/net/revelc/code/impsort/Grouper.java +++ b/src/main/java/net/revelc/code/impsort/Grouper.java @@ -124,7 +124,7 @@ public boolean getJoinStaticWithNonStatic() { return joinStaticWithNonStatic; } - public String groupedImports(Collection allImports) { + public String groupedImports(Collection allImports, String eol) { StringBuilder sb = new StringBuilder(); Map> staticImports = groupStatic(allImports); Map> nonStaticImports = groupNonStatic(allImports); @@ -135,13 +135,13 @@ public String groupedImports(Collection allImports) { AtomicBoolean firstGroup = new AtomicBoolean(true); Consumer> consumer = grouping -> { if (!firstGroup.getAndSet(false)) { - sb.append("\n"); + sb.append(eol); } - grouping.forEach(imp -> sb.append(imp).append("\n")); + grouping.forEach(imp -> sb.append(imp).append(eol)); }; first.values().forEach(consumer); if (!getJoinStaticWithNonStatic() && !first.isEmpty() && !second.isEmpty()) { - sb.append("\n"); + sb.append(eol); } firstGroup.set(true); second.values().forEach(consumer); diff --git a/src/main/java/net/revelc/code/impsort/ImpSort.java b/src/main/java/net/revelc/code/impsort/ImpSort.java index d39f045..b6ae241 100644 --- a/src/main/java/net/revelc/code/impsort/ImpSort.java +++ b/src/main/java/net/revelc/code/impsort/ImpSort.java @@ -22,6 +22,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; @@ -55,25 +56,34 @@ public class ImpSort { - private static final Comparator BY_POSITION = - (a, b) -> a.getBegin().get().compareTo(b.getBegin().get()); + private static final Comparator BY_POSITION = Comparator.comparing(a -> a.getBegin().get()); private final Charset sourceEncoding; private final Grouper grouper; private final boolean removeUnused; private final boolean treatSamePackageAsUnused; + private final LineEnding lineEnding; public ImpSort(final Charset sourceEncoding, final Grouper grouper, final boolean removeUnused, - final boolean treatSamePackageAsUnused) { + final boolean treatSamePackageAsUnused, final LineEnding lineEnding) { this.sourceEncoding = sourceEncoding; this.grouper = grouper; this.removeUnused = removeUnused; this.treatSamePackageAsUnused = treatSamePackageAsUnused; + this.lineEnding = lineEnding; } public Result parseFile(final Path path) throws IOException { - List fileLines = Files.readAllLines(path, sourceEncoding); - ParseResult parseResult = new JavaParser().parse(String.join("\n", fileLines)); + String file = new String(Files.readAllBytes(path), sourceEncoding); + LineEnding fileLineEnding = LineEnding.determineLineEnding(file); + LineEnding impLineEnding; + if (lineEnding == LineEnding.KEEP) { + impLineEnding = fileLineEnding; + } else { + impLineEnding = lineEnding; + } + List fileLines = Arrays.asList(file.split(fileLineEnding.getChars())); + ParseResult parseResult = new JavaParser().parse(file); CompilationUnit unit = parseResult.getResult().orElseThrow(() -> new IOException("Unable to parse " + path)); Position packagePosition = @@ -81,7 +91,7 @@ public Result parseFile(final Path path) throws IOException { NodeList importDeclarations = unit.getImports(); if (importDeclarations.isEmpty()) { return new Result(path, sourceEncoding, fileLines, 0, fileLines.size(), "", "", - Collections.emptyList()); + Collections.emptyList(), impLineEnding); } // find orphaned comments before between package and last import @@ -107,9 +117,10 @@ public Result parseFile(final Path path) throws IOException { while (stop < fileLines.size() && fileLines.get(stop).trim().isEmpty()) { ++stop; } - String originalSection = String.join("\n", fileLines.subList(start, stop)) + "\n"; + String originalSection = String.join(impLineEnding.getChars(), fileLines.subList(start, stop)) + + impLineEnding.getChars(); - Set allImports = convertImportSection(importSectionNodes); + Set allImports = convertImportSection(importSectionNodes, impLineEnding.getChars()); if (removeUnused) { removeUnusedImports(allImports, tokensInUse(unit)); @@ -118,22 +129,22 @@ public Result parseFile(final Path path) throws IOException { } } - String newSection = grouper.groupedImports(allImports); + String newSection = grouper.groupedImports(allImports, impLineEnding.getChars()); if (start > 0) { // add newline before imports, as long as imports not at start of file - newSection = "\n" + newSection; + newSection = impLineEnding.getChars() + newSection; } if (stop < fileLines.size()) { // add newline after imports, as long as there's more in file - newSection += "\n"; + newSection += impLineEnding.getChars(); } return new Result(path, sourceEncoding, fileLines, start, stop, originalSection, newSection, - allImports); + allImports, impLineEnding); } // return imports, with associated comments, in order found in the file - private static Set convertImportSection(List importSectionNodes) { + private static Set convertImportSection(List importSectionNodes, String eol) { List recentComments = new ArrayList<>(); LinkedHashSet allImports = new LinkedHashSet<>(importSectionNodes.size() / 2); for (Node node : importSectionNodes) { @@ -159,7 +170,7 @@ private static Set convertImportSection(List importSectionNodes) { } recentComments.clear(); - convertAndAddImport(allImports, thisImport); + convertAndAddImport(allImports, thisImport, eol); } else { throw new IllegalStateException("Unknown node: " + node); } @@ -171,7 +182,8 @@ private static Set convertImportSection(List importSectionNodes) { return allImports; } - private static void convertAndAddImport(LinkedHashSet allImports, List thisImport) { + private static void convertAndAddImport(LinkedHashSet allImports, List thisImport, + String eol) { boolean isStatic = false; String importItem = null; String prefix = ""; @@ -194,7 +206,7 @@ private static void convertAndAddImport(LinkedHashSet allImports, List iter = allImports.iterator(); // this de-duplication can probably be made more efficient by doing it all at the end while (iter.hasNext()) { diff --git a/src/main/java/net/revelc/code/impsort/Import.java b/src/main/java/net/revelc/code/impsort/Import.java index 72908d1..3f04e8d 100644 --- a/src/main/java/net/revelc/code/impsort/Import.java +++ b/src/main/java/net/revelc/code/impsort/Import.java @@ -22,12 +22,15 @@ public class Import { private final String imp; private final String prefix; private final String suffix; + private final String eol; - Import(final boolean isStatic, final String imp, final String prefix, final String suffix) { + Import(final boolean isStatic, final String imp, final String prefix, final String suffix, + final String eol) { this.isStatic = isStatic; this.imp = Objects.requireNonNull(imp); this.prefix = Objects.requireNonNull(prefix); this.suffix = Objects.requireNonNull(suffix); + this.eol = eol; } public boolean isStatic() { @@ -48,7 +51,7 @@ public String getSuffix() { @Override public String toString() { - return prefix + (prefix.isEmpty() ? "" : "\n") + "import" + (isStatic() ? " static" : "") + " " + return prefix + (prefix.isEmpty() ? "" : eol) + "import" + (isStatic() ? " static" : "") + " " + getImport() + ";" + suffix; } @@ -83,7 +86,7 @@ public Import combineWith(Import duplicate) { } else if (duplicate.getPrefix().isEmpty()) { newPrefix = getPrefix(); } else { - newPrefix = getPrefix() + "\n" + duplicate.getPrefix(); + newPrefix = getPrefix() + eol + duplicate.getPrefix(); } if (getSuffix().isEmpty()) { newSuffix = duplicate.getSuffix(); @@ -92,7 +95,7 @@ public Import combineWith(Import duplicate) { } else { newSuffix = getSuffix() + duplicate.getSuffix(); } - return new Import(isStatic(), getImport(), newPrefix, newSuffix); + return new Import(isStatic(), getImport(), newPrefix, newSuffix, eol); } } diff --git a/src/main/java/net/revelc/code/impsort/LineEnding.java b/src/main/java/net/revelc/code/impsort/LineEnding.java new file mode 100644 index 0000000..ed7ffcb --- /dev/null +++ b/src/main/java/net/revelc/code/impsort/LineEnding.java @@ -0,0 +1,66 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.revelc.code.impsort; + +/** + * This file is taken from formatter-maven-plugin. + */ +public enum LineEnding { + + AUTO(System.lineSeparator()), KEEP(null), LF("\n"), CRLF("\r\n"), CR("\r"), UNKNOWN(null); + + private final String chars; + + LineEnding(String value) { + this.chars = value; + } + + public String getChars() { + return this.chars; + } + + /** + * Returns the most occurring line-ending characters in the file text or null if no line-ending + * occurs the most. + */ + public static LineEnding determineLineEnding(String fileDataString) { + int lfCount = 0; + int crCount = 0; + int crlfCount = 0; + + for (int i = 0; i < fileDataString.length(); i++) { + char c = fileDataString.charAt(i); + if (c == '\r') { + if ((i + 1) < fileDataString.length() && fileDataString.charAt(i + 1) == '\n') { + crlfCount++; + i++; + } else { + crCount++; + } + } else if (c == '\n') { + lfCount++; + } + } + + if (lfCount > crCount && lfCount > crlfCount) { + return LF; + } else if (crlfCount > lfCount && crlfCount > crCount) { + return CRLF; + } else if (crCount > lfCount && crCount > crlfCount) { + return CR; + } + return UNKNOWN; + } + +} diff --git a/src/main/java/net/revelc/code/impsort/Result.java b/src/main/java/net/revelc/code/impsort/Result.java index eaf7632..274d232 100644 --- a/src/main/java/net/revelc/code/impsort/Result.java +++ b/src/main/java/net/revelc/code/impsort/Result.java @@ -14,7 +14,12 @@ package net.revelc.code.impsort; +import static java.nio.file.Files.newOutputStream; + +import java.io.BufferedWriter; import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; @@ -36,9 +41,11 @@ public class Result { private final List fileLines; private final int start; private final int stop; + private final LineEnding lineEnding; Result(Path path, Charset sourceEncoding, List fileLines, int start, int stop, - String originalSection, String newSection, Collection allImports) { + String originalSection, String newSection, Collection allImports, + LineEnding lineEnding) { this.path = path; this.sourceEncoding = sourceEncoding; this.originalSection = originalSection; @@ -47,6 +54,7 @@ public class Result { this.fileLines = fileLines; this.start = start; this.stop = stop; + this.lineEnding = lineEnding; } public boolean isSorted() { @@ -72,17 +80,30 @@ public void saveSorted(Path destination) throws IOException { return; } List beforeImports = fileLines.subList(0, start); - List importLines = Arrays.asList(newSection.split("\\n")); + List importLines = Arrays.asList(newSection.split(lineEnding.getChars())); List afterImports = fileLines.subList(stop, fileLines.size()); List allLines = new ArrayList<>(beforeImports.size() + importLines.size() + afterImports.size() + 1); allLines.addAll(beforeImports); allLines.addAll(importLines); if (afterImports.size() > 0) { - allLines.add(""); // restore blank line lost by split("\\n") + allLines.add(""); // restore blank line lost by split } allLines.addAll(afterImports); - Files.write(destination, allLines, sourceEncoding); + writeLines(destination, allLines, sourceEncoding); + } + + private Path writeLines(Path destination, List lines, Charset sourceEncoding) + throws IOException { + try (OutputStream out = newOutputStream(destination); + BufferedWriter writer = + new BufferedWriter(new OutputStreamWriter(out, sourceEncoding.newEncoder()))) { + for (String line : lines) { + writer.write(line); + writer.write(lineEnding.getChars()); + } + } + return destination; } } diff --git a/src/main/java/net/revelc/code/impsort/maven/plugin/AbstractImpSortMojo.java b/src/main/java/net/revelc/code/impsort/maven/plugin/AbstractImpSortMojo.java index db4901c..75d86a6 100644 --- a/src/main/java/net/revelc/code/impsort/maven/plugin/AbstractImpSortMojo.java +++ b/src/main/java/net/revelc/code/impsort/maven/plugin/AbstractImpSortMojo.java @@ -36,6 +36,7 @@ import net.revelc.code.impsort.Grouper; import net.revelc.code.impsort.ImpSort; +import net.revelc.code.impsort.LineEnding; import net.revelc.code.impsort.Result; abstract class AbstractImpSortMojo extends AbstractMojo { @@ -174,6 +175,21 @@ abstract class AbstractImpSortMojo extends AbstractMojo { defaultValue = "true") private boolean breadthFirstComparator; + /** + * Sets the line-ending of files after formatting. Valid values are: + *
    + *
  • "AUTO" - Use line endings of current system
  • + *
  • "KEEP" - Preserve line endings of files, default to AUTO if ambiguous
  • + *
  • "LF" - Use Unix and Mac style line endings
  • + *
  • "CRLF" - Use DOS and Windows style line endings
  • + *
  • "CR" - Use early Mac style line endings
  • + *
+ * + * @since 1.4.0 + */ + @Parameter(defaultValue = "AUTO", property = "lineending", required = true) + private LineEnding lineEnding; + abstract void processResult(Path path, Result results) throws MojoFailureException; @Override @@ -199,7 +215,8 @@ public final void execute() throws MojoExecutionException, MojoFailureException Grouper grouper = new Grouper(groups, staticGroups, staticAfter, joinStaticWithNonStatic, breadthFirstComparator); Charset encoding = Charset.forName(sourceEncoding); - ImpSort impSort = new ImpSort(encoding, grouper, removeUnused, treatSamePackageAsUnused); + ImpSort impSort = + new ImpSort(encoding, grouper, removeUnused, treatSamePackageAsUnused, lineEnding); AtomicLong numAlreadySorted = new AtomicLong(0); AtomicLong numProcessed = new AtomicLong(0); diff --git a/src/test/java/net/revelc/code/impsort/ImpSortTest.java b/src/test/java/net/revelc/code/impsort/ImpSortTest.java index ec0386c..49e58a5 100644 --- a/src/test/java/net/revelc/code/impsort/ImpSortTest.java +++ b/src/test/java/net/revelc/code/impsort/ImpSortTest.java @@ -47,13 +47,13 @@ public class ImpSortTest { private TreeSet addTestImportsForSort(Comparator comparator) { TreeSet set = new TreeSet<>(comparator); - set.add(new Import(true, "p.MyClass.A", "", "")); - set.add(new Import(true, "p.MyClass.B.A", "", "")); - set.add(new Import(true, "p.MyClass.B.B", "", "")); - set.add(new Import(true, "p.MyClass.C.A.A", "", "")); - set.add(new Import(true, "p.MyClass.C.A.B", "", "")); - set.add(new Import(true, "p.MyClass.C.B", "", "")); - set.add(new Import(true, "p.MyClass.D", "", "")); + set.add(new Import(true, "p.MyClass.A", "", "", LineEnding.AUTO.getChars())); + set.add(new Import(true, "p.MyClass.B.A", "", "", LineEnding.AUTO.getChars())); + set.add(new Import(true, "p.MyClass.B.B", "", "", LineEnding.AUTO.getChars())); + set.add(new Import(true, "p.MyClass.C.A.A", "", "", LineEnding.AUTO.getChars())); + set.add(new Import(true, "p.MyClass.C.A.B", "", "", LineEnding.AUTO.getChars())); + set.add(new Import(true, "p.MyClass.C.B", "", "", LineEnding.AUTO.getChars())); + set.add(new Import(true, "p.MyClass.D", "", "", LineEnding.AUTO.getChars())); return set; } @@ -79,14 +79,16 @@ public void testBreadthFirstComparator() { public void testSort() throws IOException { Path p = Paths.get(System.getProperty("user.dir"), "src", "test", "resources", "BasicPluginTests.java"); - new ImpSort(StandardCharsets.UTF_8, eclipseDefaults, false, true).parseFile(p); + new ImpSort(StandardCharsets.UTF_8, eclipseDefaults, false, true, LineEnding.AUTO).parseFile(p); } @Test public void testUnused() throws IOException { Path p = Paths.get(System.getProperty("user.dir"), "src", "test", "resources", "UnusedImports.java"); - Result result = new ImpSort(StandardCharsets.UTF_8, eclipseDefaults, true, true).parseFile(p); + Result result = + new ImpSort(StandardCharsets.UTF_8, eclipseDefaults, true, true, LineEnding.AUTO) + .parseFile(p); Set imports = result.getImports().stream().map(Import::getImport).collect(Collectors.toSet()); assertEquals(20, imports.size()); @@ -124,7 +126,9 @@ public void testUnused() throws IOException { public void testEmptyJavadoc() throws IOException { Path p = Paths.get(System.getProperty("user.dir"), "src", "test", "resources", "EmptyJavadoc.java"); - Result result = new ImpSort(StandardCharsets.UTF_8, eclipseDefaults, true, true).parseFile(p); + Result result = + new ImpSort(StandardCharsets.UTF_8, eclipseDefaults, true, true, LineEnding.AUTO) + .parseFile(p); Set imports = new HashSet<>(); for (Import i : result.getImports()) { imports.add(i.getImport()); @@ -150,7 +154,8 @@ public void testIso8859ForIssue3() throws IOException { Path p = Paths.get(System.getProperty("user.dir"), "src", "test", "resources", "Iso8859File.java"); Result result = - new ImpSort(StandardCharsets.ISO_8859_1, eclipseDefaults, true, true).parseFile(p); + new ImpSort(StandardCharsets.ISO_8859_1, eclipseDefaults, true, true, LineEnding.AUTO) + .parseFile(p); assertTrue(result.getImports().isEmpty()); Path output = File.createTempFile("impSort", null).toPath(); result.saveSorted(output); @@ -164,8 +169,10 @@ public void testIso8859ForIssue3() throws IOException { @Test public void testRemoveSamePackageImports() { Set imports = Stream - .of(new Import(false, "abc.Blah", "", ""), new Import(false, "abcd.ef.Blah.Blah", "", ""), - new Import(false, "abcd.ef.Blah2", "", ""), new Import(false, "abcd.efg.Blah2", "", "")) + .of(new Import(false, "abc.Blah", "", "", LineEnding.AUTO.getChars()), + new Import(false, "abcd.ef.Blah.Blah", "", "", LineEnding.AUTO.getChars()), + new Import(false, "abcd.ef.Blah2", "", "", LineEnding.AUTO.getChars()), + new Import(false, "abcd.efg.Blah2", "", "", LineEnding.AUTO.getChars())) .collect(Collectors.toSet()); assertEquals(4, imports.size()); assertTrue(imports.stream().anyMatch(imp -> "abc.Blah".equals(imp.getImport()))); diff --git a/src/test/java/net/revelc/code/impsort/LineEndingTest.java b/src/test/java/net/revelc/code/impsort/LineEndingTest.java new file mode 100644 index 0000000..43b5d7e --- /dev/null +++ b/src/test/java/net/revelc/code/impsort/LineEndingTest.java @@ -0,0 +1,86 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.revelc.code.impsort; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +/** + * Test class for {@link LineEnding}. + * + * This file is taken from formatter-maven-plugin. + */ +public class LineEndingTest { + + /** + * Test successfully determining CRLF line ending. + */ + @Test + public void test_success_read_line_endings_crlf() throws Exception { + String fileData = "Test\r\nTest\r\nTest\r\n"; + LineEnding lineEnd = LineEnding.determineLineEnding(fileData); + assertEquals(LineEnding.CRLF, lineEnd); + } + + /** + * Test successfully determining LF line ending. + */ + @Test + public void test_success_read_line_endings_lf() throws Exception { + String fileData = "Test\nTest\nTest\n"; + LineEnding lineEnd = LineEnding.determineLineEnding(fileData); + assertEquals(LineEnding.LF, lineEnd); + } + + /** + * Test successfully determining CR line ending. + */ + @Test + public void test_success_read_line_endings_cr() throws Exception { + String fileData = "Test\rTest\rTest\r"; + LineEnding lineEnd = LineEnding.determineLineEnding(fileData); + assertEquals(LineEnding.CR, lineEnd); + } + + /** + * Test successfully determining LF line ending with mixed endings. + */ + @Test + public void test_success_read_line_endings_mixed_lf() throws Exception { + String fileData = "Test\r\nTest\rTest\nTest\nTest\r\nTest\n"; + LineEnding lineEnd = LineEnding.determineLineEnding(fileData); + assertEquals(LineEnding.LF, lineEnd); + } + + /** + * Test successfully determining AUTO line ending with mixed endings and no clear majority. + */ + @Test + public void test_success_read_line_endings_mixed_auto() throws Exception { + String fileData = "Test\r\nTest\r\nTest\nTest\nTest\r\nTest\nTest\r"; + LineEnding lineEnd = LineEnding.determineLineEnding(fileData); + assertEquals(LineEnding.UNKNOWN, lineEnd); + } + + /** + * Test successfully determining AUTO line ending with no endings. + */ + @Test + public void test_success_read_line_endings_none_auto() throws Exception { + String fileData = "TestTestTestTest"; + LineEnding lineEnd = LineEnding.determineLineEnding(fileData); + assertEquals(LineEnding.UNKNOWN, lineEnd); + } + +}