/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.source.formatter.checks;
import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
import com.liferay.portal.kernel.util.CharPool;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.tools.ToolsUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Hugo Huijser
*/
public class JavaLineBreakCheck extends BaseFileCheck {
@Override
protected String doProcess(
String fileName, String absolutePath, String content)
throws Exception {
try (UnsyncBufferedReader unsyncBufferedReader =
new UnsyncBufferedReader(new UnsyncStringReader(content))) {
String line = null;
String previousLine = StringPool.BLANK;
int lineCount = 0;
while ((line = unsyncBufferedReader.readLine()) != null) {
lineCount++;
String trimmedLine = StringUtil.trimLeading(line);
if (trimmedLine.startsWith(StringPool.SEMICOLON)) {
addMessage(
fileName, "Line should not start with ';'", lineCount);
}
if (!trimmedLine.startsWith(StringPool.DOUBLE_SLASH) &&
!trimmedLine.startsWith(StringPool.STAR)) {
if (trimmedLine.startsWith(StringPool.PERIOD)) {
addMessage(
fileName, "Line should not start with '.'",
lineCount);
}
if (previousLine.endsWith(StringPool.OPEN_PARENTHESIS) &&
trimmedLine.startsWith(StringPool.CLOSE_PARENTHESIS)) {
addMessage(
fileName, "Line should not start with ')'",
lineCount);
}
}
int lineLength = getLineLength(line);
if (!line.startsWith("import ") &&
!line.startsWith("package ") &&
!line.matches("\\s*\\*.*") &&
(lineLength <= getMaxLineLength())) {
_checkLineBreaks(line, previousLine, fileName, lineCount);
}
previousLine = line;
}
}
_checkIncorrectLineBreaksInsideChains(content, fileName);
content = _fixIncorrectLineBreaks(content, fileName);
content = _fixLineStartingWithCloseParenthesis(content, fileName);
content = _fixMultiLineComment(content);
content = _fixArrayLineBreaks(content);
content = _fixClassLineLineBreaks(content);
return content;
}
private void _checkIncorrectLineBreaksInsideChains(
String content, String fileName) {
Matcher matcher = _incorrectLineBreakInsideChainPattern.matcher(
content);
while (matcher.find()) {
int x = matcher.end();
while (true) {
x = content.indexOf(StringPool.CLOSE_PARENTHESIS, x + 1);
if (x == -1) {
return;
}
if (ToolsUtil.isInsideQuotes(content, x)) {
continue;
}
String s = content.substring(matcher.end(), x);
if (getLevel(s) != 0) {
continue;
}
char c = content.charAt(x - 1);
if (c == CharPool.TAB) {
break;
}
int y = content.lastIndexOf(StringPool.TAB, x);
s = content.substring(y + 1, x);
addMessage(
fileName, "There should be a line break after '" + s + "'",
getLineCount(content, x));
break;
}
}
}
private void _checkLambdaLineBreaks(
String line, String fileName, int lineCount) {
if (!line.endsWith(StringPool.OPEN_CURLY_BRACE) ||
(getLevel(line) <= 0)) {
return;
}
int pos = line.indexOf("->");
if ((pos == -1) || ToolsUtil.isInsideQuotes(line, pos)) {
return;
}
int x = 1;
while (true) {
String s = line.substring(0, x);
if (getLevel(s) > 0) {
addMessage(
fileName, "There should be a line break after '" + s + "'",
lineCount);
return;
}
x++;
}
}
private void _checkLineBreaks(
String line, String previousLine, String fileName, int lineCount) {
String trimmedLine = StringUtil.trimLeading(line);
if (previousLine.contains("\t/*") || trimmedLine.startsWith("//") |
trimmedLine.startsWith("/*") || trimmedLine.endsWith("*/")) {
return;
}
_checkLambdaLineBreaks(trimmedLine, fileName, lineCount);
if (trimmedLine.startsWith("},") && !trimmedLine.equals("},")) {
addMessage(
fileName, "There should be a line break after '},'", lineCount);
}
int lineLeadingTabCount = getLeadingTabCount(line);
int previousLineLeadingTabCount = getLeadingTabCount(previousLine);
if (previousLine.endsWith(StringPool.COMMA) &&
previousLine.contains(StringPool.OPEN_PARENTHESIS) &&
!previousLine.contains("for (") &&
(lineLeadingTabCount > previousLineLeadingTabCount)) {
addMessage(
fileName, "There should be a line break after '('",
lineCount - 1);
}
if (previousLine.endsWith(StringPool.PERIOD)) {
int x = trimmedLine.indexOf(CharPool.OPEN_PARENTHESIS);
if ((x != -1) &&
((getLineLength(previousLine) + x) < getMaxLineLength()) &&
(trimmedLine.endsWith(StringPool.OPEN_PARENTHESIS) ||
(trimmedLine.charAt(x + 1) != CharPool.CLOSE_PARENTHESIS))) {
addMessage(fileName, "Incorrect line break", lineCount);
}
}
String strippedQuotesLine = stripQuotes(trimmedLine);
int strippedQuotesLineOpenParenthesisCount = StringUtil.count(
strippedQuotesLine, CharPool.OPEN_PARENTHESIS);
if (!trimmedLine.startsWith(StringPool.OPEN_PARENTHESIS) &&
trimmedLine.endsWith(") {") &&
(strippedQuotesLineOpenParenthesisCount > 0) &&
(getLevel(trimmedLine) > 0)) {
addMessage(fileName, "Incorrect line break", lineCount);
}
if (line.endsWith(StringPool.OPEN_PARENTHESIS)) {
int x = line.lastIndexOf(" && ");
int y = line.lastIndexOf(" || ");
int z = Math.max(x, y);
if (z != -1) {
addMessage(
fileName,
"There should be a line break after '" +
line.substring(z + 1, z + 3) + "'",
lineCount);
}
int pos = strippedQuotesLine.indexOf(" + ");
if (pos != -1) {
String linePart = strippedQuotesLine.substring(0, pos);
if ((getLevel(linePart, "(", ")") == 0) &&
(getLevel(linePart, "[", "]") == 0)) {
addMessage(
fileName, "There should be a line break after '+'",
lineCount);
}
}
x = trimmedLine.length() + 1;
while (true) {
x = trimmedLine.lastIndexOf(StringPool.COMMA, x - 1);
if (x == -1) {
break;
}
if (ToolsUtil.isInsideQuotes(trimmedLine, x)) {
continue;
}
String linePart = trimmedLine.substring(x);
if ((getLevel(linePart) == 1) &&
(getLevel(linePart, "<", ">") == 0)) {
addMessage(
fileName,
"There should be a line break after '" +
trimmedLine.substring(0, x + 1) + "'",
lineCount);
break;
}
}
}
if (trimmedLine.matches("\\)\\..*\\([^)].*")) {
int pos = trimmedLine.indexOf(StringPool.OPEN_PARENTHESIS);
addMessage(
fileName,
"There should be a line break after '" +
trimmedLine.substring(0, pos + 1) + "'",
lineCount);
}
if (trimmedLine.matches("^[^(].*\\+$") && (getLevel(trimmedLine) > 0)) {
addMessage(
fileName, "There should be a line break after '('", lineCount);
}
if (!trimmedLine.contains("\t//") && !line.endsWith("{") &&
strippedQuotesLine.contains("{") &&
!strippedQuotesLine.contains("}")) {
addMessage(
fileName, "There should be a line break after '{'", lineCount);
}
if (previousLine.endsWith(StringPool.OPEN_PARENTHESIS) ||
previousLine.endsWith(StringPool.PLUS)) {
int x = -1;
while (true) {
x = trimmedLine.indexOf(StringPool.COMMA_AND_SPACE, x + 1);
if (x == -1) {
break;
}
if (ToolsUtil.isInsideQuotes(trimmedLine, x)) {
continue;
}
String linePart = trimmedLine.substring(0, x + 1);
int level = getLevel(linePart);
if ((previousLine.endsWith(StringPool.OPEN_PARENTHESIS) &&
(level < 0)) ||
(previousLine.endsWith(StringPool.PLUS) && (level <= 0))) {
addMessage(
fileName,
"There should be a line break after '" + linePart + "'",
lineCount);
}
}
}
int x = trimmedLine.indexOf(StringPool.COMMA_AND_SPACE);
if (x != -1) {
if (!ToolsUtil.isInsideQuotes(trimmedLine, x)) {
String linePart = trimmedLine.substring(0, x + 1);
if (getLevel(linePart) < 0) {
addMessage(
fileName,
"There should be a line break after '" + linePart + "'",
lineCount);
}
}
}
else if (trimmedLine.endsWith(StringPool.COMMA) &&
!trimmedLine.startsWith("for (")) {
if (getLevel(trimmedLine) > 0) {
addMessage(fileName, "Incorrect line break", lineCount);
}
}
if (line.endsWith(" +") || line.endsWith(" -") || line.endsWith(" *") ||
line.endsWith(" /")) {
x = line.indexOf(" = ");
if (x != -1) {
int y = line.indexOf(CharPool.QUOTE);
if ((y == -1) || (x < y)) {
addMessage(
fileName, "There should be a line break after '='",
lineCount);
}
}
}
if (line.endsWith(" throws") ||
((previousLine.endsWith(StringPool.COMMA) ||
previousLine.endsWith(StringPool.OPEN_PARENTHESIS)) &&
line.contains(" throws ") &&
(line.endsWith(StringPool.OPEN_CURLY_BRACE) ||
line.endsWith(StringPool.SEMICOLON)))) {
addMessage(
fileName, "There should be a line break before 'throws'",
lineCount);
}
if (line.endsWith(StringPool.PERIOD) &&
line.contains(StringPool.EQUAL)) {
addMessage(
fileName, "There should be a line break after '='", lineCount);
}
if (trimmedLine.matches("^\\} (catch|else|finally) .*")) {
addMessage(
fileName, "There should be a line break after '}'", lineCount);
}
Matcher matcher = _incorrectLineBreakPattern6.matcher(trimmedLine);
if (matcher.find() && (getLevel(matcher.group(4)) > 1)) {
x = trimmedLine.indexOf("(", matcher.start(4));
String linePart = trimmedLine.substring(0, x + 1);
addMessage(
fileName,
"There should be a line break after '" + linePart + "'",
lineCount);
}
}
private String _fixArrayLineBreaks(String content) {
Matcher matcher = _arrayPattern.matcher(content);
while (matcher.find()) {
String newLine =
matcher.group(3) + matcher.group(2) + matcher.group(4) +
matcher.group(5);
if (getLineLength(newLine) <= getMaxLineLength()) {
return StringUtil.replace(
content, matcher.group(),
matcher.group(1) + "\n" + newLine + "\n");
}
}
return content;
}
private String _fixClassLineLineBreaks(String content) {
Matcher matcher = _classPattern.matcher(content);
while (matcher.find()) {
String firstTrailingNonWhitespace = matcher.group(9);
String match = matcher.group(1);
String trailingWhitespace = matcher.group(8);
if (!trailingWhitespace.contains("\n") &&
!firstTrailingNonWhitespace.equals("}")) {
return StringUtil.replace(content, match, match + "\n");
}
String formattedClassLine = _getFormattedClassLine(
matcher.group(2), match);
if (formattedClassLine != null) {
content = StringUtil.replace(
content, match, formattedClassLine);
}
}
return content;
}
private String _fixIncorrectLineBreaks(String content, String fileName) {
while (true) {
Matcher matcher = _incorrectLineBreakPattern1.matcher(content);
while (matcher.find()) {
String matchingLine = matcher.group(2);
if (!matchingLine.startsWith(StringPool.DOUBLE_SLASH) &&
!matchingLine.startsWith(StringPool.STAR)) {
content = StringUtil.replaceFirst(
content, matcher.group(3),
"\n" + matcher.group(1) + "}\n", matcher.start(3) - 1);
break;
}
}
matcher = _incorrectLineBreakPattern2.matcher(content);
while (matcher.find()) {
String tabs = matcher.group(2);
Pattern pattern = Pattern.compile(
"\n" + tabs + "([^\t]{2})(?!.*\n" + tabs + "[^\t])",
Pattern.DOTALL);
Matcher matcher2 = pattern.matcher(
content.substring(0, matcher.start(2)));
if (matcher2.find()) {
String match = matcher2.group(1);
if (!match.equals(").")) {
content = StringUtil.replaceFirst(
content, "\n" + matcher.group(2), StringPool.BLANK,
matcher.end(1));
break;
}
}
}
matcher = _incorrectLineBreakPattern3.matcher(content);
if (matcher.find()) {
content = StringUtil.replaceFirst(
content, "{", "{\n" + matcher.group(1) + "\t",
matcher.start());
}
matcher = _incorrectLineBreakPattern4.matcher(content);
while (matcher.find()) {
if (content.charAt(matcher.end()) != CharPool.NEW_LINE) {
continue;
}
String singleLine =
matcher.group(1) +
StringUtil.trimLeading(matcher.group(2)) +
matcher.group(3);
if (getLineLength(singleLine) <= getMaxLineLength()) {
content = StringUtil.replace(
content, matcher.group(), "\n" + singleLine);
break;
}
}
matcher = _redundantCommaPattern.matcher(content);
if (matcher.find()) {
content = StringUtil.replaceFirst(
content, StringPool.COMMA, StringPool.BLANK,
matcher.start());
continue;
}
break;
}
Matcher matcher = _incorrectLineBreakPattern5.matcher(content);
while (matcher.find()) {
if (getLevel(matcher.group()) == 0) {
int lineCount = getLineCount(content, matcher.start());
addMessage(
fileName,
"There should be a line break before '" + matcher.group(1) +
"'",
lineCount);
}
}
return content;
}
private String _fixLineStartingWithCloseParenthesis(
String content, String fileName) {
Matcher matcher = _lineStartingWithOpenParenthesisPattern.matcher(
content);
while (matcher.find()) {
String tabs = matcher.group(2);
int lineCount = getLineCount(content, matcher.start(2));
String lastCharacterPreviousLine = matcher.group(1);
if (lastCharacterPreviousLine.equals(StringPool.OPEN_PARENTHESIS)) {
addMessage(
fileName, "Line should not start with ')'",
getLineCount(content, matcher.start(1)));
return content;
}
while (true) {
lineCount--;
String line = getLine(content, lineCount);
if (getLeadingTabCount(line) != tabs.length()) {
continue;
}
String trimmedLine = StringUtil.trimLeading(line);
if (trimmedLine.startsWith(").") ||
trimmedLine.startsWith("@")) {
break;
}
return StringUtil.replaceFirst(
content, "\n" + tabs, StringPool.BLANK, matcher.start());
}
}
return content;
}
private String _fixMultiLineComment(String content) {
Matcher matcher = _incorrectMultiLineCommentPattern.matcher(content);
return matcher.replaceAll("$1$2$3");
}
private String _getFormattedClassLine(String indent, String classLine) {
while (classLine.contains(StringPool.TAB + StringPool.SPACE)) {
classLine = StringUtil.replace(
classLine, StringPool.TAB + StringPool.SPACE, StringPool.TAB);
}
String classSingleLine = StringUtil.replace(
classLine.substring(1),
new String[] {StringPool.TAB, StringPool.NEW_LINE},
new String[] {StringPool.BLANK, StringPool.SPACE});
classSingleLine = indent + classSingleLine;
List<String> lines = new ArrayList<>();
outerWhile:
while (true) {
if (getLineLength(classSingleLine) <= getMaxLineLength()) {
lines.add(classSingleLine);
break;
}
String newIndent = indent;
String newLine = classSingleLine;
int x = -1;
while (true) {
int y = newLine.indexOf(" extends ", x + 1);
if (y == -1) {
x = newLine.indexOf(" implements ", x + 1);
}
else {
x = y;
}
if (x == -1) {
break;
}
String linePart = newLine.substring(0, x);
if ((getLevel(linePart, "<", ">") == 0) &&
(getLineLength(linePart) <= getMaxLineLength())) {
if (lines.isEmpty()) {
newIndent = newIndent + StringPool.TAB;
}
lines.add(linePart);
newLine = newIndent + newLine.substring(x + 1);
if (getLineLength(newLine) <= getMaxLineLength()) {
lines.add(newLine);
break outerWhile;
}
x = -1;
}
}
if (lines.isEmpty()) {
return null;
}
x = newLine.length();
while (true) {
x = newLine.lastIndexOf(", ", x - 1);
if (x == -1) {
return null;
}
String linePart = newLine.substring(0, x + 1);
if ((getLevel(linePart, "<", ">") == 0) &&
(getLineLength(linePart) <= getMaxLineLength())) {
lines.add(linePart);
if (linePart.contains("\textends")) {
newIndent = newIndent + "\t\t";
}
else if (linePart.contains("\timplements")) {
newIndent = newIndent + "\t\t ";
}
newLine = newIndent + newLine.substring(x + 2);
if (getLineLength(newLine) <= getMaxLineLength()) {
lines.add(newLine);
break outerWhile;
}
x = newLine.length();
}
}
}
String formattedClassLine = null;
for (String line : lines) {
if (formattedClassLine == null) {
formattedClassLine = "\n" + line;
}
else {
formattedClassLine = formattedClassLine + "\n" + line;
}
}
return formattedClassLine;
}
private final Pattern _arrayPattern = Pattern.compile(
"(\n\t*.* =) (new \\w*\\[\\] \\{)\n(\t*)(.+)\n\t*(\\};)\n");
private final Pattern _classPattern = Pattern.compile(
"(\n(\t*)(private|protected|public) ((abstract|static) )*" +
"(class|enum|interface) ([\\s\\S]*?) \\{)\n(\\s*)(\\S)");
private final Pattern _incorrectLineBreakInsideChainPattern =
Pattern.compile("\t\\)\\..*\\(\n");
private final Pattern _incorrectLineBreakPattern1 = Pattern.compile(
"\n(\t*)(.*\\) \\{)([\t ]*\\}\n)");
private final Pattern _incorrectLineBreakPattern2 = Pattern.compile(
"\n(\t*).*\\}\n(\t*)\\);");
private final Pattern _incorrectLineBreakPattern3 = Pattern.compile(
"\n(\t*)\\{.+(?<!\\}(,|;)?)\n");
private final Pattern _incorrectLineBreakPattern4 = Pattern.compile(
"\n(\t+\\{)\n(.*[^;])\n\t+(\\},?)");
private final Pattern _incorrectLineBreakPattern5 = Pattern.compile(
", (new .*\\(.*\\) \\{)\n");
private final Pattern _incorrectLineBreakPattern6 = Pattern.compile(
"^(((else )?if|for|try|while) \\()?\\(*(.*\\()$");
private final Pattern _incorrectMultiLineCommentPattern = Pattern.compile(
"(\n\t*/\\*)\n\t*(.*?)\n\t*(\\*/\n)", Pattern.DOTALL);
private final Pattern _lineStartingWithOpenParenthesisPattern =
Pattern.compile("(.)\n+(\t+)\\)[^.].*\n");
private final Pattern _redundantCommaPattern = Pattern.compile(",\n\t+\\}");
}