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

com.liferay.source.formatter.checkstyle.check.ChainingCheck 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.checkstyle.check;

import com.liferay.petra.string.StringBundler;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.URLUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.source.formatter.parser.JavaClass;
import com.liferay.source.formatter.parser.JavaClassParser;
import com.liferay.source.formatter.parser.JavaMethod;
import com.liferay.source.formatter.parser.JavaSignature;
import com.liferay.source.formatter.parser.JavaTerm;
import com.liferay.source.formatter.util.FileUtil;
import com.liferay.source.formatter.util.SourceFormatterUtil;

import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

import java.io.File;

import java.net.URL;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author Hugo Huijser
 */
public class ChainingCheck extends BaseCheck {

	@Override
	public int[] getDefaultTokens() {
		return new int[] {
			TokenTypes.CLASS_DEF, TokenTypes.ENUM_DEF, TokenTypes.INTERFACE_DEF,
			TokenTypes.RPAREN
		};
	}

	@Override
	protected void doVisitToken(DetailAST detailAST) {
		if (detailAST.getType() == TokenTypes.RPAREN) {
			_checkChainingOnParentheses(detailAST);
		}

		DetailAST parentDetailAST = detailAST.getParent();

		if (parentDetailAST != null) {
			return;
		}

		_checkChainingOnMethodCalls(detailAST);
	}

	private void _checkChainingOnMethodCalls(DetailAST detailAST) {
		List methodCallDetailASTList = getAllChildTokens(
			detailAST, true, TokenTypes.METHOD_CALL);

		for (DetailAST methodCallDetailAST : methodCallDetailASTList) {
			DetailAST dotDetailAST = methodCallDetailAST.findFirstToken(
				TokenTypes.DOT);

			if (dotDetailAST != null) {
				List childMethodCallDetailASTList =
					getAllChildTokens(
						dotDetailAST, false, TokenTypes.METHOD_CALL);

				// Only check the method that is first in the chain

				if (!childMethodCallDetailASTList.isEmpty()) {
					continue;
				}
			}

			ChainInformation chainInformation = getChainInformation(
				methodCallDetailAST);

			List chainedMethodNames = chainInformation.getMethodNames();

			_checkRequiredChaining(methodCallDetailAST, chainedMethodNames);

			int chainSize = chainedMethodNames.size();

			if (chainSize > 3) {
				_checkChainOrder(methodCallDetailAST, chainedMethodNames);
			}
		}
	}

	private void _checkChainingOnParentheses(DetailAST detailAST) {
		if (_isInsideConstructorThisCall(detailAST) ||
			hasParentWithTokenType(detailAST, TokenTypes.SUPER_CTOR_CALL)) {

			return;
		}

		DetailAST parentDetailAST = detailAST.getParent();

		if (parentDetailAST.getType() != TokenTypes.DOT) {
			return;
		}

		DetailAST previousSiblingDetailAST = detailAST.getPreviousSibling();

		if (previousSiblingDetailAST.getType() != TokenTypes.TYPECAST) {
			log(detailAST, _MSG_AVOID_PARENTHESES_CHAINING);
		}
		else if (isAttributeValue(_APPLY_TO_TYPE_CAST_KEY)) {
			log(detailAST, _MSG_AVOID_TYPE_CAST_CHAINING);
		}
	}

	private void _checkChainOrder(
		DetailAST methodCallDetailAST, List chainedMethodNames) {

		if (!Objects.equals(chainedMethodNames.get(0), "status") ||
			!Objects.equals(
				chainedMethodNames.get(chainedMethodNames.size() - 1),
				"build") ||
			!Objects.equals(
				getClassOrVariableName(methodCallDetailAST), "Response")) {

			return;
		}

		List middleMethodNames = chainedMethodNames.subList(
			1, chainedMethodNames.size() - 1);

		String unsortedNames = middleMethodNames.toString();

		Collections.sort(middleMethodNames);

		if (!unsortedNames.equals(middleMethodNames.toString())) {
			log(methodCallDetailAST, _MSG_UNSORTED_RESPONSE);
		}
	}

	private void _checkRequiredChaining(
		DetailAST methodCallDetailAST, List chainedMethodNames) {

		String classOrVariableName = getClassOrVariableName(
			methodCallDetailAST);

		if (classOrVariableName == null) {
			return;
		}

		String variableTypeName = getVariableTypeName(
			methodCallDetailAST, classOrVariableName, false);

		String fullyQualifiedClassName = variableTypeName;

		for (String importName : getImportNames(methodCallDetailAST)) {
			if (importName.endsWith("." + variableTypeName)) {
				fullyQualifiedClassName = importName;

				break;
			}
		}

		List requiredChainingMethodNames = null;

		if (fullyQualifiedClassName.equals("org.json.JSONObject")) {
			requiredChainingMethodNames = Arrays.asList(
				"put", "putOnce", "putOpt");
		}
		else if (fullyQualifiedClassName.startsWith(
					"com.liferay.frontend.data.set.view.table.") &&
				 fullyQualifiedClassName.endsWith("FDSTableSchemaField")) {

			requiredChainingMethodNames = Arrays.asList(
				"setActionId", "setContentRenderer",
				"setContentRendererClientExtension",
				"setContentRendererModuleURL", "setFieldName", "setLabel",
				"setLocalizeLabel", "setSortable", "setSortingOrder");
		}
		else {
			requiredChainingMethodNames = _getRequiredChainingMethodNames(
				fullyQualifiedClassName);
		}

		if (requiredChainingMethodNames == null) {
			return;
		}

		String methodName = chainedMethodNames.get(
			chainedMethodNames.size() - 1);

		if (!requiredChainingMethodNames.contains(methodName)) {
			return;
		}

		DetailAST topLevelMethodCallDetailAST = getTopLevelMethodCallDetailAST(
			methodCallDetailAST);

		DetailAST parentDetailAST = topLevelMethodCallDetailAST.getParent();

		if (parentDetailAST.getType() != TokenTypes.EXPR) {
			return;
		}

		DetailAST nextSiblingDetailAST = parentDetailAST.getNextSibling();

		if ((nextSiblingDetailAST == null) ||
			(nextSiblingDetailAST.getType() != TokenTypes.SEMI)) {

			return;
		}

		nextSiblingDetailAST = nextSiblingDetailAST.getNextSibling();

		if ((nextSiblingDetailAST == null) ||
			(nextSiblingDetailAST.getType() != TokenTypes.EXPR)) {

			return;
		}

		DetailAST nextMethodCallDetailAST =
			nextSiblingDetailAST.getFirstChild();

		if (nextMethodCallDetailAST.getType() != TokenTypes.METHOD_CALL) {
			return;
		}

		while (true) {
			DetailAST firstChildDetailAST =
				nextMethodCallDetailAST.getFirstChild();

			if (firstChildDetailAST.getType() != TokenTypes.DOT) {
				break;
			}

			firstChildDetailAST = firstChildDetailAST.getFirstChild();

			if (firstChildDetailAST.getType() != TokenTypes.METHOD_CALL) {
				break;
			}

			nextMethodCallDetailAST = firstChildDetailAST;
		}

		if (classOrVariableName.equals(
				getClassOrVariableName(nextMethodCallDetailAST)) &&
			requiredChainingMethodNames.contains(
				getMethodName(nextMethodCallDetailAST))) {

			log(
				methodCallDetailAST, _MSG_REQUIRED_CHAINING,
				classOrVariableName + "." + methodName);
		}
	}

	private JavaClass _getJavaClass(String requiredChainingClassFileName) {
		File file = SourceFormatterUtil.getFile(
			getBaseDirName(), requiredChainingClassFileName, getMaxDirLevel());

		try {
			if (file != null) {
				return JavaClassParser.parseJavaClass(
					requiredChainingClassFileName, FileUtil.read(file));
			}

			String portalBranchName = getAttributeValue(
				SourceFormatterUtil.GIT_LIFERAY_PORTAL_BRANCH);

			if (Validator.isNull(portalBranchName)) {
				return null;
			}

			URL url = new URL(
				StringBundler.concat(
					SourceFormatterUtil.GIT_LIFERAY_PORTAL_URL,
					portalBranchName, StringPool.SLASH,
					requiredChainingClassFileName));

			return JavaClassParser.parseJavaClass(
				requiredChainingClassFileName, URLUtil.toString(url));
		}
		catch (Exception exception) {
			if (_log.isDebugEnabled()) {
				_log.debug(exception);
			}

			return null;
		}
	}

	private List _getRequiredChainingMethodNames(
		String fullyQualifiedClassName) {

		if (_requiredChainingMethodNamesMap != null) {
			return _requiredChainingMethodNamesMap.get(fullyQualifiedClassName);
		}

		_requiredChainingMethodNamesMap = new HashMap<>();

		List requiredChainingClassFileNames = getAttributeValues(
			_REQUIRED_CHAINING_CLASS_FILE_NAMES_KEY);

		for (String requiredChainingClassFileName :
				requiredChainingClassFileNames) {

			JavaClass javaClass = _getJavaClass(requiredChainingClassFileName);

			if (javaClass == null) {
				continue;
			}

			List requiredChainingMethodNames = new ArrayList<>();

			for (JavaTerm javaTerm : javaClass.getChildJavaTerms()) {
				if (!(javaTerm instanceof JavaMethod)) {
					continue;
				}

				JavaMethod javaMethod = (JavaMethod)javaTerm;

				if (!javaMethod.isPublic()) {
					continue;
				}

				JavaSignature javaSignature = javaMethod.getSignature();

				if (Objects.equals(
						javaClass.getName(), javaSignature.getReturnType())) {

					requiredChainingMethodNames.add(javaMethod.getName());
				}
			}

			_requiredChainingMethodNamesMap.put(
				javaClass.getPackageName() + "." + javaClass.getName(),
				requiredChainingMethodNames);
		}

		return _requiredChainingMethodNamesMap.get(fullyQualifiedClassName);
	}

	private boolean _isInsideConstructorThisCall(DetailAST detailAST) {
		DetailAST parentDetailAST = detailAST.getParent();

		while (parentDetailAST != null) {
			String parentDetailASTText = parentDetailAST.getText();

			if ((parentDetailAST.getType() == TokenTypes.CTOR_CALL) &&
				parentDetailASTText.equals("this")) {

				return true;
			}

			parentDetailAST = parentDetailAST.getParent();
		}

		return false;
	}

	private static final String _APPLY_TO_TYPE_CAST_KEY = "applyToTypeCast";

	private static final String _MSG_AVOID_PARENTHESES_CHAINING =
		"chaining.avoid.parentheses";

	private static final String _MSG_AVOID_TYPE_CAST_CHAINING =
		"chaining.avoid.type.cast";

	private static final String _MSG_REQUIRED_CHAINING = "chaining.required";

	private static final String _MSG_UNSORTED_RESPONSE = "response.unsorted";

	private static final String _REQUIRED_CHAINING_CLASS_FILE_NAMES_KEY =
		"requiredChainingClassFileNames";

	private static final Log _log = LogFactoryUtil.getLog(ChainingCheck.class);

	private Map> _requiredChainingMethodNamesMap;

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy