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

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

There is a newer version: 1.0.1437
Show newest version
/**
 * 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.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.ListUtil;
import com.liferay.portal.kernel.util.NaturalOrderStringComparator;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.tools.GitUtil;
import com.liferay.portal.tools.ToolsUtil;
import com.liferay.source.formatter.SourceFormatterArgs;
import com.liferay.source.formatter.check.util.BNDSourceUtil;
import com.liferay.source.formatter.check.util.JavaSourceUtil;
import com.liferay.source.formatter.check.util.SourceUtil;
import com.liferay.source.formatter.parser.JavaClass;
import com.liferay.source.formatter.parser.JavaMethod;
import com.liferay.source.formatter.parser.JavaParameter;
import com.liferay.source.formatter.parser.JavaSignature;
import com.liferay.source.formatter.parser.JavaTerm;
import com.liferay.source.formatter.processor.SourceProcessor;

import java.io.File;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Hugo Huijser
 */
public class JavaComponentAnnotationsCheck extends JavaAnnotationsCheck {

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

	@Override
	protected String formatAnnotation(
			String fileName, String absolutePath, JavaClass javaClass,
			String fileContent, String annotation, String indent)
		throws Exception {

		String trimmedAnnotation = StringUtil.trim(annotation);

		if (!trimmedAnnotation.equals("@Component") &&
			!trimmedAnnotation.startsWith("@Component(")) {

			return annotation;
		}

		List importNames = javaClass.getImportNames();

		if (!importNames.contains(
				"org.osgi.service.component.annotations.Component")) {

			return annotation;
		}

		_checkImmediateAttribute(fileName, absolutePath, annotation);

		annotation = _formatAnnotationParameterProperties(annotation);
		annotation = _formatConfigurationAttributes(
			fileName, absolutePath, javaClass, annotation);
		annotation = _formatEnabledAttribute(absolutePath, annotation);
		annotation = _formatServiceAttribute(
			fileName, absolutePath, javaClass.getName(), annotation,
			javaClass.getImplementedClassNames());

		List extendedClassNames = javaClass.getExtendedClassNames(
			false);

		if (extendedClassNames.contains("MVCPortlet")) {
			annotation = _formatMVCPortletProperties(absolutePath, annotation);
		}

		return annotation;
	}

	private String _addAttribute(
		String annotation, String attributeName, String attributeValue) {

		if (!annotation.contains("(")) {
			return StringBundler.concat(
				annotation.substring(0, annotation.length() - 1), "(",
				attributeName, " = ", attributeValue, ")\n");
		}

		Matcher matcher = _attributePattern.matcher(annotation);

		while (matcher.find()) {
			if (!ToolsUtil.isInsideQuotes(annotation, matcher.end(1)) &&
				(getLevel(annotation.substring(0, matcher.end()), "{", "}") ==
					0)) {

				String curAttributeName = matcher.group(1);

				if (curAttributeName.compareTo(attributeName) > 0) {
					return StringUtil.insert(
						annotation,
						StringBundler.concat(
							attributeName, " = ", attributeValue, ", "),
						matcher.start(1));
				}
			}
		}

		String indent = SourceUtil.getIndent(annotation);

		if (annotation.endsWith("\n" + indent + ")\n")) {
			int pos = annotation.lastIndexOf("\n", annotation.length() - 2);

			return StringUtil.insert(
				annotation,
				StringBundler.concat(
					",\n\t", indent, attributeName, " = ", attributeValue),
				pos);
		}

		return StringUtil.replaceLast(
			annotation, ')',
			StringBundler.concat(
				", ", attributeName, " = ", attributeValue, ")"));
	}

	private String _addNewProperties(String newProperties, String properties) {
		newProperties = StringUtil.trimTrailing(newProperties);

		if (!newProperties.endsWith(StringPool.COMMA)) {
			newProperties += StringPool.COMMA;
		}

		return newProperties + properties;
	}

	private void _checkHasMultipleServiceTypes(
			String fileName, String absolutePath)
		throws Exception {

		SourceProcessor sourceProcessor = getSourceProcessor();

		SourceFormatterArgs sourceFormatterArgs =
			sourceProcessor.getSourceFormatterArgs();

		if (!sourceFormatterArgs.isFormatCurrentBranch()) {
			return;
		}

		List allowedMultipleServicesClassNames = getAttributeValues(
			_ALLOWED_MULTIPLE_SERVICE_TYPES_CLASS_NAMES_KEY, absolutePath);

		for (String allowedMultipleServicesClassName :
				allowedMultipleServicesClassNames) {

			if (absolutePath.contains(allowedMultipleServicesClassName)) {
				return;
			}
		}

		String currentBranchFileDiff = GitUtil.getCurrentBranchFileDiff(
			sourceFormatterArgs.getBaseDirName(),
			sourceFormatterArgs.getGitWorkingBranchName(), absolutePath);

		for (String currentBranchFileDiffBlock :
				StringUtil.split(currentBranchFileDiff, "\n@@")) {

			if (currentBranchFileDiffBlock.startsWith("diff") ||
				!currentBranchFileDiffBlock.contains("@Component")) {

				continue;
			}

			for (String line :
					StringUtil.splitLines(currentBranchFileDiffBlock)) {

				if (!line.startsWith(StringPool.PLUS)) {
					continue;
				}

				if (line.contains("service = {") &&
					!line.contains("service = {}")) {

					addMessage(
						fileName,
						"@Component classes should only specify one service " +
							"type in the 'service' attribute, see LPS-180838");

					break;
				}
			}
		}
	}

	private void _checkImmediateAttribute(
		String fileName, String absolutePath, String annotation) {

		if (absolutePath.contains("/modules/apps/archived/") ||
			!isAttributeValue(_CHECK_IMMEDIATE_ATTRIBUTE_KEY, absolutePath)) {

			return;
		}

		List allowedImmediateAttributeClassNames = getAttributeValues(
			_ALLOWED_IMMEDIATE_ATTRIBUTE_CLASS_NAMES_KEY, absolutePath);

		for (String allowedImmediateAttributeClassName :
				allowedImmediateAttributeClassNames) {

			if (absolutePath.contains(allowedImmediateAttributeClassName)) {
				return;
			}
		}

		String immediateAttributeValue = getAnnotationAttributeValue(
			annotation, "immediate");

		if ((immediateAttributeValue != null) &&
			immediateAttributeValue.equals("true")) {

			addMessage(fileName, "Do not use 'immediate = true' in @Component");
		}
	}

	private String _formatAnnotationParameterProperties(String annotation) {
		Matcher matcher = _annotationParameterPropertyPattern.matcher(
			annotation);

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

			while (true) {
				x = annotation.indexOf(CharPool.CLOSE_CURLY_BRACE, x + 1);

				if (!ToolsUtil.isInsideQuotes(annotation, x)) {
					break;
				}
			}

			String parameterProperties = annotation.substring(matcher.end(), x);

			String newParameterProperties = StringUtil.replace(
				parameterProperties, new String[] {" =", "= "},
				new String[] {"=", "="});

			if (!parameterProperties.equals(newParameterProperties)) {
				return StringUtil.replaceFirst(
					annotation, parameterProperties, newParameterProperties);
			}

			parameterProperties = StringUtil.replace(
				parameterProperties,
				new String[] {
					StringPool.TAB, StringPool.FOUR_SPACES, StringPool.NEW_LINE
				},
				new String[] {
					StringPool.BLANK, StringPool.BLANK, StringPool.SPACE
				});

			parameterProperties = StringUtil.trim(parameterProperties);

			if (parameterProperties.startsWith(StringPool.AT)) {
				continue;
			}

			String[] parameterPropertiesArray = StringUtil.split(
				parameterProperties, StringPool.COMMA_AND_SPACE);

			AnnotationParameterPropertyComparator comparator =
				new AnnotationParameterPropertyComparator(matcher.group(1));

			for (int i = 1; i < parameterPropertiesArray.length; i++) {
				String parameterProperty = parameterPropertiesArray[i];
				String previousParameterProperty =
					parameterPropertiesArray[i - 1];

				int compare = comparator.compare(
					previousParameterProperty, parameterProperty);

				if (compare > 0) {
					annotation = StringUtil.replaceFirst(
						annotation, previousParameterProperty,
						parameterProperty);
					annotation = StringUtil.replaceLast(
						annotation, parameterProperty,
						previousParameterProperty);

					return annotation;
				}
			}
		}

		return annotation;
	}

	private String _formatConfigurationAttributes(
		String fileName, String absolutePath, JavaClass javaClass,
		String annotation) {

		String configurationPid = getAnnotationAttributeValue(
			annotation, "configurationPid");

		if (configurationPid != null) {
			return _formatConfigurationPid(
				fileName, absolutePath, javaClass, annotation,
				configurationPid);
		}

		for (JavaMethod javaMethod :
				_getJavaMethods(javaClass, "Activate", "Modified")) {

			String javaMethodContent = javaMethod.getContent();

			if (javaMethodContent.contains(
					"ConfigurableUtil.createConfigurable")) {

				addMessage(
					fileName,
					"Missing @Component 'configurationPid' attribute, see " +
						"LPS-88783");

				break;
			}
		}

		if (!isAttributeValue(
				_CHECK_CONFIGURATION_POLICY_ATTRIBUTE_KEY, absolutePath)) {

			return annotation;
		}

		List imports = javaClass.getImportNames();

		if (imports.contains(
				"org.osgi.service.component.annotations.Modified") ||
			(getAnnotationAttributeValue(annotation, "configurationPolicy ") !=
				null)) {

			return annotation;
		}

		for (JavaMethod javaMethod : _getJavaMethods(javaClass, "Activate")) {
			JavaSignature signature = javaMethod.getSignature();

			for (JavaParameter parameter : signature.getParameters()) {
				String parameterType = parameter.getParameterType();

				if (parameterType.equals("ComponentContext") ||
					parameterType.startsWith("Map<")) {

					return annotation;
				}
			}
		}

		return _addAttribute(
			annotation, "configurationPolicy", "ConfigurationPolicy.IGNORE");
	}

	private String _formatConfigurationPid(
		String fileName, String absolutePath, JavaClass javaClass,
		String annotation, String configurationPid) {

		if (!isAttributeValue(
				_CHECK_CONFIGURATION_PID_ATTRIBUTE_KEY, absolutePath)) {

			return annotation;
		}

		List configurationClasses = new ArrayList<>();

		if (StringUtil.startsWith(configurationPid, '{')) {
			configurationPid = configurationPid.substring(
				1, configurationPid.length() - 1);

			Collections.addAll(
				configurationClasses, StringUtil.split(configurationPid, ", "));
		}
		else {
			configurationClasses.add(configurationPid);
		}

		if (isAttributeValue(
				_CHECK_HAS_MULTIPLE_CONFIGURATION_PIDS_KEY, absolutePath) &&
			(configurationClasses.size() > 1)) {

			addMessage(
				fileName,
				"Component classes cannot have multiple configuration PIDs");

			return annotation;
		}

		List importNames = javaClass.getImportNames();

		for (String configurationClass : configurationClasses) {
			configurationClass = StringUtil.unquote(configurationClass);

			if (!configurationClass.startsWith("com.liferay")) {
				continue;
			}

			int pos = configurationClass.lastIndexOf(".scoped");

			if (pos != -1) {
				configurationClass = configurationClass.substring(0, pos);
			}

			if (importNames.contains(configurationClass)) {
				continue;
			}

			File javaFile = JavaSourceUtil.getJavaFile(
				configurationClass, _getRootDirName(absolutePath),
				_getBundleSymbolicNamesMap(absolutePath));

			if (javaFile == null) {
				String message = StringBundler.concat(
					"Remove '", configurationClass,
					"' from 'configurationPid' as the configuration class ",
					"does not exist");

				addMessage(fileName, message);
			}
		}

		return annotation;
	}

	private String _formatEnabledAttribute(
		String absolutePath, String annotation) {

		if (absolutePath.contains("-test/") ||
			absolutePath.contains("-test-util/")) {

			return annotation;
		}

		List enterpriseAppModulePathNames = getAttributeValues(
			_ENTERPRISE_APP_MODULE_PATH_NAMES_KEY, absolutePath);

		if (enterpriseAppModulePathNames.isEmpty()) {
			return annotation;
		}

		for (String enterpriseAppModulePathName :
				enterpriseAppModulePathNames) {

			if (!absolutePath.contains(enterpriseAppModulePathName)) {
				continue;
			}

			String enabledAttributeValue = getAnnotationAttributeValue(
				annotation, "enabled");

			if (enabledAttributeValue == null) {
				return _addAttribute(annotation, "enabled", "false");
			}
		}

		return annotation;
	}

	private String _formatMVCPortletProperties(
		String absolutePath, String annotation) {

		int x = annotation.indexOf("property = {");

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

		int y = x;

		while (true) {
			y = annotation.indexOf(CharPool.CLOSE_CURLY_BRACE, y + 1);

			if (!ToolsUtil.isInsideQuotes(annotation, y)) {
				break;
			}
		}

		String properties = annotation.substring(x, y);

		String newProperties = StringUtil.replace(
			properties,
			new String[] {
				"\"javax.portlet.supports.mime-type=text/html\",",
				"\"javax.portlet.supports.mime-type=text/html\""
			},
			new String[] {StringPool.BLANK, StringPool.BLANK});

		if (newProperties.contains(
				"\"javax.portlet.init-param.config-template=") &&
			!newProperties.contains("javax.portlet.portlet-mode=")) {

			newProperties = _addNewProperties(
				newProperties,
				"\"javax.portlet.portlet-mode=text/html;config\"");
		}

		if (isAttributeValue(_CHECK_PORTLET_VERSION_KEY, absolutePath) &&
			!absolutePath.contains("/modules/apps/archived/") &&
			!absolutePath.contains("/modules/sdk/") &&
			!newProperties.contains("\"javax.portlet.version=3.0\"")) {

			String serviceAttributeValue = getAnnotationAttributeValue(
				annotation, "service");

			if (serviceAttributeValue.startsWith(StringPool.OPEN_CURLY_BRACE) &&
				serviceAttributeValue.endsWith(StringPool.CLOSE_CURLY_BRACE)) {

				serviceAttributeValue = serviceAttributeValue.substring(
					1, serviceAttributeValue.length() - 1);
			}

			List serviceAttributeValues = ListUtil.fromString(
				serviceAttributeValue, StringPool.COMMA);

			if (serviceAttributeValues.contains("Portlet.class")) {
				newProperties = _addNewProperties(
					newProperties, "\"javax.portlet.version=3.0\"");
			}
		}

		return StringUtil.replace(annotation, properties, newProperties);
	}

	private String _formatServiceAttribute(
			String fileName, String absolutePath, String className,
			String annotation, List implementedClassNames)
		throws Exception {

		String expectedServiceAttributeValue =
			_getExpectedServiceAttributeValue(implementedClassNames);

		String serviceAttributeValue = getAnnotationAttributeValue(
			annotation, "service");

		if (serviceAttributeValue == null) {
			return _addAttribute(
				annotation, "service", expectedServiceAttributeValue);
		}

		boolean checkMismatchedServiceAttribute = isAttributeValue(
			_CHECK_MISMATCHED_SERVICE_ATTRIBUTE_KEY, absolutePath);
		boolean checkSelfRegistration = isAttributeValue(
			_CHECK_SELF_REGISTRATION_KEY, absolutePath);
		boolean checkHasMultipleServiceTypes = isAttributeValue(
			_CHECK_HAS_MULTIPLE_SERVICE_TYPES_KEY, absolutePath);

		if (checkMismatchedServiceAttribute &&
			!serviceAttributeValue.equals(expectedServiceAttributeValue)) {

			addMessage(fileName, "Mismatched @Component 'service' attribute");
		}

		if (checkSelfRegistration &&
			serviceAttributeValue.matches(".*\\b" + className + "\\.class.*")) {

			addMessage(
				fileName,
				"No need to register '" + className +
					"' in @Component 'service' attribute");
		}

		if (checkHasMultipleServiceTypes) {
			_checkHasMultipleServiceTypes(fileName, absolutePath);
		}

		return annotation;
	}

	private synchronized Map _getBundleSymbolicNamesMap(
		String absolutePath) {

		if (_bundleSymbolicNamesMap == null) {
			_bundleSymbolicNamesMap = BNDSourceUtil.getBundleSymbolicNamesMap(
				_getRootDirName(absolutePath));
		}

		return _bundleSymbolicNamesMap;
	}

	private String _getExpectedServiceAttributeValue(
		List implementedClassNames) {

		if (implementedClassNames.isEmpty()) {
			return "{}";
		}

		if (implementedClassNames.size() == 1) {
			return implementedClassNames.get(0) + ".class";
		}

		StringBundler sb = new StringBundler(
			(implementedClassNames.size() * 3) + 1);

		sb.append("{");

		for (String implementedClassName : implementedClassNames) {
			sb.append(implementedClassName);
			sb.append(".class");
			sb.append(", ");
		}

		sb.setIndex(sb.index() - 1);

		sb.append("}");

		return sb.toString();
	}

	private List _getJavaMethods(
		JavaClass javaClass, String... annotations) {

		List javaMethods = new ArrayList<>();

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

			for (String annotation : annotations) {
				if (javaTerm.hasAnnotation(annotation)) {
					javaMethods.add((JavaMethod)javaTerm);

					break;
				}
			}
		}

		return javaMethods;
	}

	private synchronized String _getRootDirName(String absolutePath) {
		if (_rootDirName == null) {
			_rootDirName = SourceUtil.getRootDirName(absolutePath);
		}

		return _rootDirName;
	}

	private static final String _ALLOWED_IMMEDIATE_ATTRIBUTE_CLASS_NAMES_KEY =
		"allowedImmediateAttributeClassNames";

	private static final String
		_ALLOWED_MULTIPLE_SERVICE_TYPES_CLASS_NAMES_KEY =
			"allowedMultipleServiceTypesClassNames";

	private static final String _CHECK_CONFIGURATION_PID_ATTRIBUTE_KEY =
		"checkConfigurationPidAttribute";

	private static final String _CHECK_CONFIGURATION_POLICY_ATTRIBUTE_KEY =
		"checkConfigurationPolicyAttribute";

	private static final String _CHECK_HAS_MULTIPLE_CONFIGURATION_PIDS_KEY =
		"checkHasMultipleConfigurationPids";

	private static final String _CHECK_HAS_MULTIPLE_SERVICE_TYPES_KEY =
		"checkHasMultipleServiceTypes";

	private static final String _CHECK_IMMEDIATE_ATTRIBUTE_KEY =
		"checkImmediateAttribute";

	private static final String _CHECK_MISMATCHED_SERVICE_ATTRIBUTE_KEY =
		"checkMismatchedServiceAttribute";

	private static final String _CHECK_PORTLET_VERSION_KEY =
		"checkPortletVersion";

	private static final String _CHECK_SELF_REGISTRATION_KEY =
		"checkSelfRegistration";

	private static final String _ENTERPRISE_APP_MODULE_PATH_NAMES_KEY =
		"enterpriseAppModulePathNames";

	private static final Pattern _annotationParameterPropertyPattern =
		Pattern.compile("\\s(\\w+) = \\{");
	private static final Pattern _attributePattern = Pattern.compile(
		"\\W(\\w+)\\s*=");

	private Map _bundleSymbolicNamesMap;
	private String _rootDirName;

	private class AnnotationParameterPropertyComparator
		extends NaturalOrderStringComparator {

		public AnnotationParameterPropertyComparator(String parameterName) {
			_parameterName = parameterName;
		}

		public int compare(String property1, String property2) {
			if (!_parameterName.equals("property")) {
				return super.compare(property1, property2);
			}

			String propertyName1 = _getPropertyName(property1);
			String propertyName2 = _getPropertyName(property2);

			if (propertyName1.equals(propertyName2)) {
				return super.compare(property1, property2);
			}

			int value = super.compare(propertyName1, propertyName2);

			if (propertyName1.startsWith(StringPool.QUOTE) ^
				propertyName2.startsWith(StringPool.QUOTE)) {

				return -value;
			}

			return value;
		}

		private String _getPropertyName(String property) {
			int x = property.indexOf(StringPool.EQUAL);

			if (x != -1) {
				return property.substring(0, x);
			}

			return property;
		}

		private final String _parameterName;

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy