Skip to content

Commit

Permalink
Add configurable EOL specifier (#27)
Browse files Browse the repository at this point in the history
* Add configurable EOL specifier
* Document where LineEnding.java and LineEndingTest.java come from

This closes #2
  • Loading branch information
dwalluck authored Apr 25, 2020
1 parent 6a760b3 commit 5ab57f8
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 42 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
/.classpath
/.project
/.settings/
/.idea/
/*.iml
8 changes: 4 additions & 4 deletions src/main/java/net/revelc/code/impsort/Grouper.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public boolean getJoinStaticWithNonStatic() {
return joinStaticWithNonStatic;
}

public String groupedImports(Collection<Import> allImports) {
public String groupedImports(Collection<Import> allImports, String eol) {
StringBuilder sb = new StringBuilder();
Map<Integer, ArrayList<Import>> staticImports = groupStatic(allImports);
Map<Integer, ArrayList<Import>> nonStaticImports = groupNonStatic(allImports);
Expand All @@ -135,13 +135,13 @@ public String groupedImports(Collection<Import> allImports) {
AtomicBoolean firstGroup = new AtomicBoolean(true);
Consumer<ArrayList<Import>> 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);
Expand Down
44 changes: 28 additions & 16 deletions src/main/java/net/revelc/code/impsort/ImpSort.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,33 +56,42 @@

public class ImpSort {

private static final Comparator<Node> BY_POSITION =
(a, b) -> a.getBegin().get().compareTo(b.getBegin().get());
private static final Comparator<Node> 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<String> fileLines = Files.readAllLines(path, sourceEncoding);
ParseResult<CompilationUnit> 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<String> fileLines = Arrays.asList(file.split(fileLineEnding.getChars()));
ParseResult<CompilationUnit> parseResult = new JavaParser().parse(file);
CompilationUnit unit =
parseResult.getResult().orElseThrow(() -> new IOException("Unable to parse " + path));
Position packagePosition =
unit.getPackageDeclaration().map(p -> p.getEnd().get()).orElse(unit.getBegin().get());
NodeList<ImportDeclaration> 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
Expand All @@ -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<Import> allImports = convertImportSection(importSectionNodes);
Set<Import> allImports = convertImportSection(importSectionNodes, impLineEnding.getChars());

if (removeUnused) {
removeUnusedImports(allImports, tokensInUse(unit));
Expand All @@ -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<Import> convertImportSection(List<Node> importSectionNodes) {
private static Set<Import> convertImportSection(List<Node> importSectionNodes, String eol) {
List<Comment> recentComments = new ArrayList<>();
LinkedHashSet<Import> allImports = new LinkedHashSet<>(importSectionNodes.size() / 2);
for (Node node : importSectionNodes) {
Expand All @@ -159,7 +170,7 @@ private static Set<Import> convertImportSection(List<Node> importSectionNodes) {
}

recentComments.clear();
convertAndAddImport(allImports, thisImport);
convertAndAddImport(allImports, thisImport, eol);
} else {
throw new IllegalStateException("Unknown node: " + node);
}
Expand All @@ -171,7 +182,8 @@ private static Set<Import> convertImportSection(List<Node> importSectionNodes) {
return allImports;
}

private static void convertAndAddImport(LinkedHashSet<Import> allImports, List<Node> thisImport) {
private static void convertAndAddImport(LinkedHashSet<Import> allImports, List<Node> thisImport,
String eol) {
boolean isStatic = false;
String importItem = null;
String prefix = "";
Expand All @@ -194,7 +206,7 @@ private static void convertAndAddImport(LinkedHashSet<Import> allImports, List<N
if (!suffix.isEmpty()) {
suffix = " " + suffix;
}
Import imp = new Import(isStatic, importItem, prefix.trim(), suffix);
Import imp = new Import(isStatic, importItem, prefix.trim(), suffix, eol);
Iterator<Import> iter = allImports.iterator();
// this de-duplication can probably be made more efficient by doing it all at the end
while (iter.hasNext()) {
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/net/revelc/code/impsort/Import.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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;
}

Expand Down Expand Up @@ -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();
Expand All @@ -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);
}

}
66 changes: 66 additions & 0 deletions src/main/java/net/revelc/code/impsort/LineEnding.java
Original file line number Diff line number Diff line change
@@ -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 <a href="https://github.com/revelc/formatter-maven-plugin/blob/46b15ca42db50cb91e2edad79dd40882ccb2af4c/src/main/java/net/revelc/code/formatter/LineEnding.java">formatter-maven-plugin</a>.
*/
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;
}

}
29 changes: 25 additions & 4 deletions src/main/java/net/revelc/code/impsort/Result.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,9 +41,11 @@ public class Result {
private final List<String> fileLines;
private final int start;
private final int stop;
private final LineEnding lineEnding;

Result(Path path, Charset sourceEncoding, List<String> fileLines, int start, int stop,
String originalSection, String newSection, Collection<Import> allImports) {
String originalSection, String newSection, Collection<Import> allImports,
LineEnding lineEnding) {
this.path = path;
this.sourceEncoding = sourceEncoding;
this.originalSection = originalSection;
Expand All @@ -47,6 +54,7 @@ public class Result {
this.fileLines = fileLines;
this.start = start;
this.stop = stop;
this.lineEnding = lineEnding;
}

public boolean isSorted() {
Expand All @@ -72,17 +80,30 @@ public void saveSorted(Path destination) throws IOException {
return;
}
List<String> beforeImports = fileLines.subList(0, start);
List<String> importLines = Arrays.asList(newSection.split("\\n"));
List<String> importLines = Arrays.asList(newSection.split(lineEnding.getChars()));
List<String> afterImports = fileLines.subList(stop, fileLines.size());
List<String> 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<String> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
* <ul>
* <li><b>"AUTO"</b> - Use line endings of current system</li>
* <li><b>"KEEP"</b> - Preserve line endings of files, default to AUTO if ambiguous</li>
* <li><b>"LF"</b> - Use Unix and Mac style line endings</li>
* <li><b>"CRLF"</b> - Use DOS and Windows style line endings</li>
* <li><b>"CR"</b> - Use early Mac style line endings</li>
* </ul>
*
* @since 1.4.0
*/
@Parameter(defaultValue = "AUTO", property = "lineending", required = true)
private LineEnding lineEnding;

abstract void processResult(Path path, Result results) throws MojoFailureException;

@Override
Expand All @@ -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);

Expand Down
Loading

0 comments on commit 5ab57f8

Please sign in to comment.