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

com.liferay.source.formatter.check.XMLCustomSQLStylingCheck Maven / Gradle / Ivy

There is a newer version: 1.0.1457
Show newest version
/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

package com.liferay.source.formatter.check;

import com.liferay.petra.string.CharPool;
import com.liferay.petra.string.StringBundler;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.source.formatter.check.util.SourceUtil;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.dom4j.Document;
import org.dom4j.Element;

/**
 * @author Hugo Huijser
 */
public class XMLCustomSQLStylingCheck extends BaseFileCheck {

	@Override
	public boolean isLiferaySourceCheck() {
		return true;
	}

	@Override
	protected String doProcess(
		String fileName, String absolutePath, String content) {

		if (!fileName.contains("/custom-sql/")) {
			return content;
		}

		_checkIncorrectLineBreakAfterComma(fileName, content);
		_checkMissingLineBreakAfterKeyword(fileName, content);
		_checkMissingParentheses(fileName, content);
		_checkMultiLineClause(fileName, content);
		_checkScalability(fileName, absolutePath, content);
		_checkUnionStatement(fileName, content);

		content = _fixIncorrectAndOr(content);
		content = _fixLowerCaseKeywords(content);
		content = _fixMissingCountValue(content);
		content = _fixMissingLineBreakAfterOpenParenthesis(content);
		content = _fixMissingLineBreakBeforeOpenParenthesis(content);
		content = _fixRedundantParenthesesForSingleLineClause(content);
		content = _fixSinglePredicateClause(content);
		content = _formatSingleLineClauseWithMultiplePredicates(
			fileName, content);

		return content;
	}

	private String _addTabs(String content, int start, int end) {
		for (int i = start; i <= end; i++) {
			int lineStartPos = getLineStartPos(content, i);

			content =
				content.substring(0, lineStartPos) + "\t" +
					content.substring(lineStartPos);
		}

		return content;
	}

	private void _checkIncorrectLineBreakAfterComma(
		String fileName, String content) {

		if (true) {
			return;
		}

		Matcher matcher = _incorrectLineBreakAfterCommaPattern.matcher(content);

		while (matcher.find()) {
			addMessage(
				fileName, "Incorrect line break after \",\"",
				getLineNumber(content, matcher.start()));
		}
	}

	private void _checkMissingLineBreakAfterKeyword(
		String fileName, String content) {

		Matcher matcher = _missingLineBreakAfterKeywordPattern.matcher(content);

		while (matcher.find()) {
			addMessage(
				fileName,
				"There should be a line break after \"" +
					StringUtil.trim(matcher.group(1)) + "\"",
				getLineNumber(content, matcher.end()));
		}
	}

	private void _checkMissingParentheses(String fileName, String content) {
		Matcher matcher = _missingParenthesesPattern1.matcher(content);

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

			if (charBeforeOperator.equals(StringPool.CLOSE_PARENTHESIS)) {
				String s = matcher.group(1);

				if (s.equals(StringPool.CLOSE_PARENTHESIS) ||
					(s.startsWith(StringPool.OPEN_PARENTHESIS) &&
					 (getLevel(s) == 0))) {

					continue;
				}
			}
			else if (charBeforeOperator.equals(StringPool.CLOSE_BRACKET)) {
				String s = matcher.group(1);

				if (s.startsWith(StringPool.OPEN_BRACKET) &&
					(getLevel(s, "[", "]") == 0)) {

					continue;
				}
			}

			addMessage(
				fileName, "Missing parentheses",
				getLineNumber(content, matcher.start()));
		}

		matcher = _missingParenthesesPattern2.matcher(content);

		while (matcher.find()) {
			String nextLine = getLine(
				content, getLineNumber(content, matcher.end()));

			if (!nextLine.endsWith(" IN") && !nextLine.endsWith("EXISTS") &&
				!nextLine.matches(".*\\s+BETWEEN\\s+\\?\\s+AND\\s+\\?.*")) {

				addMessage(
					fileName, "Missing parentheses",
					getLineNumber(content, matcher.end()));
			}
		}
	}

	private void _checkMultiLineClause(String fileName, String content) {
		int startPos = -1;

		while (true) {
			startPos = content.indexOf("\t(\n", startPos + 1);

			if (startPos == -1) {
				return;
			}

			int endPos = _getCloseParenthesisPos(content, startPos);

			int endLineNumber = getLineNumber(content, endPos);
			int endLineStartPos = content.lastIndexOf("\n", endPos);

			char c = content.charAt(endPos - 1);

			if (c != CharPool.TAB) {
				String s = StringUtil.trim(
					content.substring(endLineStartPos, endPos));

				addMessage(
					fileName,
					"There should be a line break after \"" + s + "\"",
					endLineNumber);

				continue;
			}

			String afterCloseParenthesis = StringUtil.trim(
				content.substring(endPos + 1));
			String beforeOpenParenthesis = StringUtil.trim(
				content.substring(0, startPos));

			if ((beforeOpenParenthesis.endsWith(" ON") ||
				 beforeOpenParenthesis.endsWith("\tWHERE")) &&
				!afterCloseParenthesis.startsWith("AND") &&
				!afterCloseParenthesis.startsWith("OR") &&
				!afterCloseParenthesis.startsWith("[")) {

				addMessage(
					fileName, "redundant parentheses",
					getLineNumber(content, startPos));
			}

			int endLineTabCount = endPos - endLineStartPos - 1;

			int startLineStartPos = content.lastIndexOf("\n", startPos);

			int startLineTabCount = startPos - startLineStartPos;

			if (endLineTabCount != startLineTabCount) {
				addMessage(
					fileName,
					StringBundler.concat(
						"Line starts with \"", endLineTabCount,
						"\" tabs, but \"", startLineTabCount,
						"\" tabs are expected"),
					endLineNumber);
			}
		}
	}

	private void _checkScalability(
		String fileName, String absolutePath, String content) {

		Document document = SourceUtil.readXML(content);

		if (document == null) {
			return;
		}

		Element rootElement = document.getRootElement();

		for (Element sqlElement : (List)rootElement.elements("sql")) {
			String sql = sqlElement.getText();

			Matcher matcher = _whereNotInSQLPattern.matcher(sql);

			while (matcher.find()) {
				String id = sqlElement.attributeValue("id");

				int x = id.lastIndexOf(CharPool.PERIOD);

				int y = id.lastIndexOf(CharPool.PERIOD, x - 1);

				String entityName = id.substring(y + 1, x);

				if (!isExcludedPath(
						_CUSTOM_FINDER_SCALABILITY_EXCLUDES, absolutePath,
						entityName)) {

					addMessage(
						fileName,
						"Avoid using WHERE ... NOT IN: " + id +
							", see LPS-51315");
				}
			}
		}
	}

	private void _checkUnionStatement(String fileName, String content) {
		Matcher matcher = _unionPattern.matcher(content);

		while (matcher.find()) {
			String beforeUnionChar = matcher.group(1);

			if (beforeUnionChar.equals(StringPool.CLOSE_PARENTHESIS)) {
				int openParenthesisPos = _getOpenParenthesisPos(
					content, matcher.start(1));

				String s = StringUtil.trim(
					content.substring(openParenthesisPos + 1, matcher.start()));

				if (s.startsWith("SELECT")) {
					addMessage(
						fileName, "Do not use parentheses before UNION",
						getLineNumber(content, matcher.start()));

					continue;
				}
			}

			String afterUnionChar = matcher.group(4);

			if (afterUnionChar.equals(StringPool.OPEN_PARENTHESIS)) {
				addMessage(
					fileName, "Do not use parentheses after UNION",
					getLineNumber(content, matcher.start(3)));
			}
		}
	}

	private String _fixIncorrectAndOr(String content) {
		Matcher matcher = _incorrectAndOrpattern.matcher(content);

		if (matcher.find()) {
			String whitespace = matcher.group(3);

			if (whitespace.equals(StringPool.SPACE)) {
				return StringUtil.replaceFirst(
					content, matcher.group(),
					StringPool.SPACE + matcher.group(2) + matcher.group(1),
					matcher.start() - 1);
			}

			return StringUtil.replaceFirst(
				content, matcher.group(1), StringPool.SPACE,
				matcher.start() - 1);
		}

		return content;
	}

	private String _fixLowerCaseKeywords(String content) {
		for (String keyword : _SQL_KEYWORDS) {
			Pattern pattern = Pattern.compile(
				"[^\\w.$'\"](" + keyword + ")[^\\w.$'\"]",
				Pattern.CASE_INSENSITIVE);

			Matcher matcher = pattern.matcher(content);

			while (matcher.find()) {
				int level = getLevel(
					content.substring(0, matcher.start()), "");

				if (level != 0) {
					content = StringUtil.replaceFirst(
						content, matcher.group(1), keyword, matcher.start());
				}
			}
		}

		return content;
	}

	private String _fixMissingCountValue(String content) {
		Matcher matcher = _missingCountValuePattern.matcher(content);

		if (matcher.find()) {
			return StringUtil.insert(
				content, " AS COUNT_VALUE", matcher.end(1));
		}

		return content;
	}

	private String _fixMissingLineBreakAfterOpenParenthesis(String content) {
		Matcher matcher = _missingLineBreakAfterOpenParenthesisPattern.matcher(
			content);

		while (matcher.find()) {
			if (getLevel(matcher.group()) == 0) {
				continue;
			}

			int startPos = matcher.end(1);

			int startLineNumber = getLineNumber(content, startPos);

			int endPos = _getCloseParenthesisPos(content, startPos);

			int endLineNumber = getLineNumber(content, endPos);

			content = _addTabs(content, startLineNumber + 1, endLineNumber - 1);

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

		return content;
	}

	private String _fixMissingLineBreakBeforeOpenParenthesis(String content) {
		Matcher matcher = _missingLineBreakBeforeOpenParenthesisPattern.matcher(
			content);

		if (!matcher.find()) {
			return content;
		}

		int startPos = matcher.end() - 2;

		int startLineNumber = getLineNumber(content, startPos);

		int endPos = _getCloseParenthesisPos(content, startPos);

		int endLineNumber = getLineNumber(content, endPos);

		content = _addTabs(content, startLineNumber + 1, endLineNumber);

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

	private String _fixRedundantParenthesesForSingleLineClause(String content) {
		Matcher matcher =
			_redundantParenthesesForSingleLineClausePattern.matcher(content);

		while (matcher.find()) {
			String trimmedNextLine = StringUtil.trim(matcher.group(3));

			if (!trimmedNextLine.startsWith("AND") &&
				!trimmedNextLine.startsWith("OR") &&
				!trimmedNextLine.startsWith("[$AND_OR_CONNECTOR$]")) {

				return StringUtil.replaceFirst(
					content, "(" + matcher.group(2) + ")", matcher.group(2),
					matcher.start());
			}
		}

		return content;
	}

	private String _fixSinglePredicateClause(String content) {
		Matcher matcher = _multiLineSinglePredicatePattern.matcher(content);

		while (matcher.find()) {
			String line = StringUtil.trim(matcher.group(1));

			if (!line.startsWith("[") || !line.endsWith("]")) {
				return StringUtil.replace(
					content, matcher.group(), "\t(" + line + ")");
			}
		}

		return content;
	}

	private String _formatSingleLineClauseWithMultiplePredicates(
		String fileName, String content) {

		Matcher matcher = _singleLineClauseWitMultiplePredicatesPattern.matcher(
			content);

		while (matcher.find()) {
			String afterOperator = matcher.group(5);
			String beforeOperator = matcher.group(3);
			String indent = matcher.group(1);
			String match = matcher.group(2);
			String operator = matcher.group(4);

			StringBundler sb = new StringBundler(11);

			if (beforeOperator.equals(")")) {
				sb.append(") ");
				sb.append(operator);
				sb.append("\n");
				sb.append(indent);
				sb.append(afterOperator);

				return StringUtil.replaceFirst(
					content, match, sb.toString(), matcher.start());
			}

			int lineNumber = getLineNumber(content, matcher.start(3));

			if ((getLevel(match) != 0) || !match.startsWith("(")) {
				addMessage(fileName, "One SQL predicate per line", lineNumber);

				continue;
			}

			int beforeOperatorlevel = getLevel(beforeOperator);

			if ((beforeOperatorlevel < 0) || (beforeOperatorlevel > 1)) {
				addMessage(fileName, "One SQL predicate per line", lineNumber);

				continue;
			}

			if (beforeOperatorlevel == 0) {
				sb.append(beforeOperator);
				sb.append(StringPool.SPACE);
				sb.append(operator);
				sb.append("\n");
				sb.append(indent);
				sb.append(afterOperator);

				return StringUtil.replaceFirst(
					content, match, sb.toString(), matcher.start());
			}

			sb.append("(\n\t");
			sb.append(indent);
			sb.append(beforeOperator.substring(1));
			sb.append(StringPool.SPACE);
			sb.append(operator);
			sb.append("\n\t");
			sb.append(indent);

			int pos = afterOperator.lastIndexOf(StringPool.CLOSE_PARENTHESIS);

			sb.append(afterOperator.substring(0, pos));

			sb.append("\n");
			sb.append(indent);
			sb.append(afterOperator.substring(pos));

			return StringUtil.replaceFirst(
				content, match, sb.toString(), matcher.start());
		}

		return content;
	}

	private int _getCloseParenthesisPos(String content, int startPos) {
		int endPos = startPos;

		while (true) {
			endPos = content.indexOf(")", endPos + 1);

			if (getLevel(content.substring(startPos, endPos + 1)) == 0) {
				return endPos;
			}
		}
	}

	private int _getOpenParenthesisPos(String content, int endPos) {
		int startPos = endPos;

		while (true) {
			startPos = content.lastIndexOf("(", startPos - 1);

			if (getLevel(content.substring(startPos, endPos + 1)) == 0) {
				return startPos;
			}
		}
	}

	private static final String _CUSTOM_FINDER_SCALABILITY_EXCLUDES =
		"custom.finder.scalability.excludes";

	private static final String[] _SQL_KEYWORDS = {
		"ALL", "AND", "AS", "ASC", "BITAND", "BY", "COUNT", "CROSS", "DELETE",
		"DESC", "DISTINCT", "EXISTS", "FROM", "GROUP", "HAVING", "IN", "INNER",
		"IS", "JOIN", "LEFT", "LIKE", "LOWER", "MAX", "MOD", "NOT", "NULL",
		"ON", "OR", "ORDER", "OUTER", "SELECT", "SET", "SUM", "UNION", "UPDATE",
		"WHERE"
	};

	private static final Pattern _incorrectAndOrpattern = Pattern.compile(
		"(\n\t*)(AND|OR|\\[\\$AND_OR_CONNECTOR\\$\\])( |\n)");
	private static final Pattern _incorrectLineBreakAfterCommaPattern =
		Pattern.compile(".(?




© 2015 - 2024 Weber Informatics LLC | Privacy Policy