All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.source.formatter.checks.JavaLineBreakCheck Maven / Gradle / Ivy

/**
 * 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.petra.string.CharPool;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
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 LineBreakCheck {

	@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) &&
					trimmedLine.startsWith(StringPool.PERIOD)) {

					addMessage(
						fileName, "Line should not start with '.'", lineCount);
				}

				int lineLength = getLineLength(line);

				if (!line.startsWith("import ") &&
					!line.startsWith("package ") &&
					!trimmedLine.startsWith(StringPool.STAR) &&
					(lineLength <= getMaxLineLength())) {

					_checkLineBreaks(line, previousLine, fileName, lineCount);
				}

				previousLine = line;
			}
		}

		content = _fixIncorrectCatchStatementLineBreaks(content);

		content = _fixIncorrectLineBreaksInsideChains(content, fileName);

		content = _fixIncorrectLineBreaks(content, fileName);

		content = _fixLineStartingWithCloseParenthesis(content, fileName);

		content = _fixMultiLineComment(content);

		content = _fixArrayLineBreaks(content);

		content = _fixClassOrEnumLineLineBreaks(content);

		content = fixRedundantCommaInsideArray(content);

		return content;
	}

	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) {

		checkLineBreaks(line, previousLine, fileName, lineCount);

		String trimmedLine = StringUtil.trimLeading(line);

		if (previousLine.contains("\t/*") || trimmedLine.startsWith("//") |
			trimmedLine.startsWith("/*") || trimmedLine.endsWith("*/")) {

			return;
		}

		_checkLambdaLineBreaks(trimmedLine, fileName, lineCount);

		if (trimmedLine.endsWith("( {")) {
			addMessage(
				fileName, "There should be a line before ' {'", lineCount);
		}

		for (int x = 0;;) {
			x = trimmedLine.indexOf("}", x + 1);

			if (x == -1) {
				break;
			}

			if (!ToolsUtil.isInsideQuotes(trimmedLine, x) &&
				(getLevel(trimmedLine.substring(0, x + 1), "{", "}") < 0)) {

				addMessage(
					fileName,
					"There should be a line break after '" +
						trimmedLine.substring(0, x) + "'",
					lineCount);
			}
		}

		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);

		if (line.matches(".*(\\(|->( \\{)?)")) {
			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);
				}
			}
		}

		if (line.matches(".*(\\(|\\^|\\&|\\||->( \\{)?)")) {
			int 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, trimmedLine.length() - 1);

				linePart = StringUtil.removeSubstring(linePart, "->");

				if ((getLevel(linePart) == 0) &&
					(getLevel(linePart, "<", ">") == 0)) {

					addMessage(
						fileName,
						"There should be a line break after '" +
							trimmedLine.substring(0, x + 1) + "'",
						lineCount);

					break;
				}
			}
		}

		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 (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)) {
			int x = trimmedLine.indexOf("(", matcher.start(4));

			String linePart = trimmedLine.substring(0, x + 1);

			addMessage(
				fileName,
				"There should be a line break after '" + linePart + "'",
				lineCount);
		}

		if (trimmedLine.matches("for \\(.*[^;{]")) {
			int x = trimmedLine.length();

			while (true) {
				x = trimmedLine.lastIndexOf(StringPool.SEMICOLON, x - 1);

				if (x == -1) {
					break;
				}

				if (!ToolsUtil.isInsideQuotes(trimmedLine, x)) {
					addMessage(
						fileName,
						"There should be a line break after '" +
							trimmedLine.substring(0, x + 1) + "'",
						lineCount);
				}
			}
		}
	}

	private String _fixArrayLineBreaks(String content) {
		Matcher matcher = _arrayPattern.matcher(content);

		while (matcher.find()) {
			String newLine =
				matcher.group(4) + matcher.group(2) + matcher.group(5) +
					matcher.group(6);

			if (getLineLength(newLine) <= getMaxLineLength()) {
				return StringUtil.replace(
					content, matcher.group(),
					StringBundler.concat(
						matcher.group(1), "\n", newLine, "\n"));
			}
		}

		return content;
	}

	private String _fixClassOrEnumLineLineBreaks(String content) {
		Matcher matcher = _classOrEnumPattern.matcher(content);

		while (matcher.find()) {
			String indent = matcher.group(2);
			String match = matcher.group(1);

			String inBetweenCurlyBraces = matcher.group(9);

			if (inBetweenCurlyBraces != null) {
				if (Validator.isNull(inBetweenCurlyBraces)) {

					// Case: public class Test {}

					return StringUtil.replaceFirst(
						content, "}", "\n" + indent + "}", matcher.end(9));
				}

				// Case: public enum Test { VALUE1, VALUE2 }

				return StringUtil.replaceFirst(
					content, inBetweenCurlyBraces + "}",
					StringBundler.concat(
						"\n\n\t", indent, StringUtil.trim(inBetweenCurlyBraces),
						"\n\n", indent, "}"),
					matcher.start(8));
			}

			String firstTrailingNonWhitespace = matcher.group(13);

			String trailingWhitespace = matcher.group(12);

			if (!trailingWhitespace.contains("\n") &&
				!firstTrailingNonWhitespace.equals("}")) {

				return StringUtil.replace(content, match, match + "\n");
			}

			String formattedClassLine = _getFormattedClassLine(indent, match);

			if (formattedClassLine != null) {
				content = StringUtil.replace(
					content, match, formattedClassLine);
			}
		}

		return content;
	}

	private String _fixIncorrectCatchStatementLineBreaks(String content) {
		Matcher matcher = _catchStatemementPattern.matcher(content);

		while (matcher.find()) {
			String catchStatement = matcher.group(1);
			String indent = matcher.group(2);

			String singleLineCatchStatement = indent;

			for (String line : StringUtil.splitLines(catchStatement)) {
				if (!singleLineCatchStatement.equals(indent) &&
					!singleLineCatchStatement.endsWith(
						StringPool.OPEN_PARENTHESIS) &&
					!singleLineCatchStatement.endsWith(StringPool.PERIOD)) {

					singleLineCatchStatement += StringPool.SPACE;
				}

				singleLineCatchStatement += StringUtil.trim(line);
			}

			if (getLineLength(singleLineCatchStatement) <= getMaxLineLength()) {
				return StringUtil.replaceFirst(
					content, catchStatement, singleLineCatchStatement,
					matcher.start());
			}

			int x = _getLastIndexOf(
				singleLineCatchStatement, CharPool.PIPE, getMaxLineLength());

			if (x != -1) {
				String newCatchStatement = StringUtil.insert(
					singleLineCatchStatement, "\n" + indent, x + 1);

				if (!catchStatement.equals(newCatchStatement)) {
					return StringUtil.replaceFirst(
						content, catchStatement, newCatchStatement,
						matcher.start());
				}

				continue;
			}

			if (singleLineCatchStatement.contains(StringPool.PIPE)) {
				continue;
			}

			x = singleLineCatchStatement.indexOf(CharPool.OPEN_PARENTHESIS);

			String firstLine = singleLineCatchStatement.substring(0, x + 1);

			String remainder =
				indent + "\t" + singleLineCatchStatement.substring(x + 1);

			if (getLineLength(remainder) <= getMaxLineLength()) {
				String newCatchStatement = firstLine + "\n" + remainder;

				if (!catchStatement.equals(newCatchStatement)) {
					return StringUtil.replaceFirst(
						content, catchStatement, newCatchStatement,
						matcher.start());
				}

				continue;
			}

			x = _getLastIndexOf(remainder, CharPool.SPACE, getMaxLineLength());

			if (x == -1) {
				x = _getLastIndexOf(
					remainder, CharPool.PERIOD, getMaxLineLength());
			}

			if (x != -1) {
				String secondLine = remainder.substring(0, x + 1);
				String thirdLine = indent + "\t\t" + remainder.substring(x + 1);

				String newCatchStatement = StringBundler.concat(
					firstLine, "\n", secondLine, "\n", thirdLine);

				if (!catchStatement.equals(newCatchStatement)) {
					return StringUtil.replaceFirst(
						content, catchStatement, newCatchStatement,
						matcher.start());
				}
			}
		}

		return content;
	}

	private String _fixIncorrectLineBreaks(String content, String fileName) {
		Matcher matcher = _incorrectLineBreakPattern1.matcher(content);

		while (matcher.find()) {
			String matchingLine = matcher.group(2);

			if (!matchingLine.startsWith(StringPool.DOUBLE_SLASH) &&
				!matchingLine.startsWith(StringPool.STAR)) {

				return StringUtil.replaceFirst(
					content, matcher.group(3), "\n" + matcher.group(1) + "}\n",
					matcher.start(3) - 1);
			}
		}

		matcher = _incorrectLineBreakPattern2.matcher(content);

		while (matcher.find()) {
			String tabs = matcher.group(2);

			Pattern pattern = Pattern.compile(
				StringBundler.concat(
					"\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(").")) {
					return StringUtil.replaceFirst(
						content, "\n" + matcher.group(2), StringPool.BLANK,
						matcher.end(1));
				}
			}
		}

		matcher = _incorrectLineBreakPattern3.matcher(content);

		if (matcher.find()) {
			return 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()) {
				return StringUtil.replace(
					content, matcher.group(), "\n" + singleLine);
			}
		}

		matcher = _incorrectLineBreakPattern7.matcher(content);

		while (matcher.find()) {
			String linePart = matcher.group(2);

			if (getLevel(linePart) != 1) {
				continue;
			}

			if (StringUtil.count(matcher.group(), CharPool.NEW_LINE) > 2) {
				addMessage(
					fileName,
					"For better readability, create new var for the array in " +
						"the 'for' statement",
					getLineCount(content, matcher.start()));

				continue;
			}

			String match = matcher.group();

			String replacement = StringUtil.replace(match, "\n\t", "\n\t\t");

			replacement = StringUtil.replaceFirst(
				replacement, linePart,
				"\n\t\t" + matcher.group(1) + StringUtil.trim(linePart));

			return StringUtil.replace(content, match, replacement);
		}

		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 _fixIncorrectLineBreaksInsideChains(
		String content, String fileName) {

		Matcher matcher = _incorrectLineBreakInsideChainPattern1.matcher(
			content);

		while (matcher.find()) {
			String linePart = matcher.group(2);

			if (linePart.matches("\\)[^\\)]+[\\(;]")) {
				return StringUtil.insert(
					content, "\n" + matcher.group(1), matcher.start(2));
			}
		}

		matcher = _incorrectLineBreakInsideChainPattern2.matcher(content);

		while (matcher.find()) {
			int x = matcher.end();

			while (true) {
				x = content.indexOf(StringPool.CLOSE_PARENTHESIS, x + 1);

				if (x == -1) {
					return content;
				}

				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;
			}
		}

		matcher = _incorrectLineBreakInsideChainPattern3.matcher(content);

		while (matcher.find()) {
			if (getLevel(matcher.group(1)) <= 0) {
				String methodName = matcher.group(2);

				if (!methodName.equals("concat")) {
					addMessage(
						fileName,
						"Chaining on method '" + methodName +
							"' is allowed, but incorrect styling",
						"chaining.markdown",
						getLineCount(content, matcher.end(1)));
				}
			}
		}

		matcher = _incorrectLineBreakInsideChainPattern4.matcher(content);

		while (matcher.find()) {
			String s = matcher.group(2);

			if (!s.matches("\\)+;?")) {
				addMessage(
					fileName,
					"There should be a line break after '" + matcher.group(1) +
						"'",
					getLineCount(content, matcher.start()));
			}
		}

		return content;
	}

	private String _fixLineStartingWithCloseParenthesis(
		String content, String fileName) {

		Matcher matcher = _lineStartingWithCloseParenthesisPattern.matcher(
			content);

		while (matcher.find()) {
			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;
			}

			int x = matcher.end(2) + 1;

			int y = x - 1;

			while (true) {
				if (ToolsUtil.isInsideQuotes(content, y) ||
					(getLevel(content.substring(y, x)) != 0)) {

					y--;

					continue;
				}

				String trimmedLine = StringUtil.trimLeading(
					getLine(content, getLineCount(content, y)));

				if (trimmedLine.startsWith(").") ||
					trimmedLine.startsWith("@")) {

					break;
				}

				return StringUtil.replaceFirst(
					content, "\n" + matcher.group(2), 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 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 int _getLastIndexOf(String s, char c, int fromIndex) {
		int x = s.length();

		while (true) {
			x = s.lastIndexOf(c, x - 1);

			if ((x == -1) ||
				(getLineLength(s.substring(0, x + 1)) <= fromIndex)) {

				return x;
			}
		}
	}

	private final Pattern _arrayPattern = Pattern.compile(
		"(\n\t*.* =) ((new \\w*\\[\\] )?\\{)\n(\t*)([^\t\\{].*)\n\t*(\\};?)\n");
	private final Pattern _catchStatemementPattern = Pattern.compile(
		"\n((\t*)catch \\((.*[^{|\n])?\n[\\s\\S]*?\\) \\{)\n");
	private final Pattern _classOrEnumPattern = Pattern.compile(
		"(\n(\t*)(private|protected|public) ((abstract|static) )*" +
			"(class|enum|interface) ([\\s\\S]*?)\\{)((.*)\\})?" +
				"([ \t]*(\\Z|\n)(\\s*)(\\S))");
	private final Pattern _incorrectLineBreakInsideChainPattern1 =
		Pattern.compile("\n(\t*)\\).*?\\((.+)");
	private final Pattern _incorrectLineBreakInsideChainPattern2 =
		Pattern.compile("\t\\)\\..*\\(\n");
	private final Pattern _incorrectLineBreakInsideChainPattern3 =
		Pattern.compile("\n(.*\\S)\\)\\.(.*)\\(\n");
	private final Pattern _incorrectLineBreakInsideChainPattern4 =
		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*)\\{.+(?




© 2015 - 2024 Weber Informatics LLC | Privacy Policy