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

com.liferay.source.formatter.parser.JavaClassParser Maven / Gradle / Ivy

There is a newer version: 1.0.739
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.parser;

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.portal.tools.ToolsUtil;
import com.liferay.source.formatter.check.util.JavaSourceUtil;
import com.liferay.source.formatter.check.util.SourceUtil;

import java.io.IOException;

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

/**
 * @author Hugo Huijser
 */
public class JavaClassParser {

	public static List parseAnonymousClasses(String content)
		throws IOException, ParseException {

		return parseAnonymousClasses(content, null, Collections.emptyList());
	}

	public static List parseAnonymousClasses(
			String content, String packageName, List importNames)
		throws IOException, ParseException {

		List anonymousClasses = new ArrayList<>();

		Matcher matcher = _anonymousClassPattern.matcher(content);

		while (matcher.find()) {
			String anonymousClassContent = _getAnonymousClassContent(
				content, matcher.start() + 1,
				StringUtil.equals(matcher.group(1), "<"));

			if (anonymousClassContent != null) {
				anonymousClasses.add(
					_parseJavaClass(
						JavaTerm.ACCESS_MODIFIER_PRIVATE, true,
						anonymousClassContent,
						SourceUtil.getLineNumber(content, matcher.start()),
						StringPool.BLANK, importNames, false, false, false,
						false, false, false, false, packageName, false));
			}
		}

		return anonymousClasses;
	}

	public static JavaClass parseJavaClass(String fileName, String content)
		throws IOException, ParseException {

		String className = JavaSourceUtil.getClassName(fileName);

		Pattern pattern = Pattern.compile(
			StringBundler.concat(
				"\n(public\\s+)?(abstract\\s+)?(final\\s+)?@?",
				"(strictfp\\s+)?((non-)?sealed\\s+)?(class|enum|interface|",
				"record)\\s+", className,
				"(\\s*\\(.*?\\))?([<|\\s][^\\{]*)\\{"));

		Matcher matcher = pattern.matcher(content);

		if (!matcher.find()) {
			throw new ParseException("Parsing error");
		}

		int x = matcher.start() + 1;

		int y = x + 1;

		while (true) {
			y = content.lastIndexOf("\n\n", y - 1);

			if (y == -1) {
				throw new ParseException("Parsing error");
			}

			if (ToolsUtil.getLevel(content.substring(y, x)) == 0) {
				break;
			}
		}

		int lineNumber = SourceUtil.getLineNumber(content, y + 2);

		String classContent = content.substring(y + 2);

		boolean isAbstract = false;

		if (matcher.group(2) != null) {
			isAbstract = true;
		}

		boolean isEnum = false;

		boolean isFinal = false;

		if (matcher.group(3) != null) {
			isFinal = true;
		}

		boolean isStrictfp = false;

		if (matcher.group(4) != null) {
			isStrictfp = true;
		}

		boolean nonsealed = false;
		boolean sealed = false;

		String s = matcher.group(5);

		if (s != null) {
			s = s.trim();

			if (s.equals("sealed")) {
				sealed = true;
			}
			else {
				nonsealed = true;
			}
		}

		boolean isInterface = false;

		if (matcher.group(7) != null) {
			String token = matcher.group(7);

			if (token.equals("enum")) {
				isEnum = true;
			}
			else if (token.equals("interface")) {
				isInterface = true;
			}
		}

		JavaClass javaClass = _parseJavaClass(
			JavaTerm.ACCESS_MODIFIER_PUBLIC, false, classContent, lineNumber,
			className, JavaSourceUtil.getImportNames(content), isAbstract,
			isEnum, isFinal, isInterface, false, isStrictfp, nonsealed,
			JavaSourceUtil.getPackageName(content), sealed);

		return _parseExtendsImplementsPermits(
			javaClass, StringUtil.trim(matcher.group(9)));
	}

	private static String _getAnonymousClassContent(
		String content, int start, boolean genericClass) {

		int x = start;

		if (genericClass) {
			while (true) {
				x = content.indexOf('>', x + 1);

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

				int level = ToolsUtil.getLevel(
					content.substring(start, x + 1), "<", ">");

				if (level == 0) {
					break;
				}
			}

			if (!Objects.equals(content.charAt(x + 1), '(')) {
				return null;
			}
		}

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

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

			if (ToolsUtil.getLevel(content.substring(start, x + 1), "(", ")") ==
					0) {

				break;
			}
		}

		String s = StringUtil.trim(content.substring(x + 1));

		if (!s.startsWith("{\n")) {
			return null;
		}

		while (true) {
			x = content.indexOf('}', x + 1);

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

			String anonymousClassContent = content.substring(start, x + 1);

			if (ToolsUtil.getLevel(anonymousClassContent, "{", "}") == 0) {
				return anonymousClassContent;
			}
		}
	}

	private static String _getClassName(String line) {
		int pos = line.indexOf(" extends ");

		if (pos == -1) {
			pos = line.indexOf(" implements ");
		}

		if (pos == -1) {
			pos = line.indexOf(CharPool.OPEN_CURLY_BRACE);
		}

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

		pos = line.indexOf(CharPool.LESS_THAN);

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

		line = line.trim();

		pos = line.lastIndexOf(CharPool.SPACE);

		return line.substring(pos + 1);
	}

	private static int _getCommentEndLineNumber(
		String classContent, int lineNumber) {

		while (true) {
			String line = SourceUtil.getLine(classContent, lineNumber);

			if (line.endsWith("*/")) {
				return lineNumber;
			}

			lineNumber++;
		}
	}

	private static String _getConstructorOrMethodName(String line) {
		int x = line.lastIndexOf(CharPool.SPACE);

		return line.substring(x + 1);
	}

	private static JavaTerm _getJavaTerm(
			String packageName, List importNames, String metadata,
			String javaTermContent, int lineNumber)
		throws IOException, ParseException {

		Matcher matcher1 = _javaTermStartLinePattern.matcher(javaTermContent);

		if (!matcher1.find()) {
			return null;
		}

		String startLine = StringUtil.trim(matcher1.group());

		int x = startLine.indexOf(CharPool.OPEN_PARENTHESIS);

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

		startLine = StringUtil.replace(
			startLine, new String[] {"\t", "\n", " synchronized "},
			new String[] {"", " ", " "});

		javaTermContent = metadata + javaTermContent;

		if (startLine.startsWith("static {")) {
			return new JavaStaticBlock(javaTermContent, lineNumber);
		}

		String accessModifier = JavaTerm.ACCESS_MODIFIER_DEFAULT;

		for (String curAccessModifier : JavaTerm.ACCESS_MODIFIERS) {
			if (startLine.startsWith(curAccessModifier)) {
				accessModifier = curAccessModifier;

				break;
			}
		}

		boolean isAbstract = SourceUtil.containsUnquoted(
			startLine, " abstract ");
		boolean isEnum = SourceUtil.containsUnquoted(startLine, " enum ");
		boolean isFinal = SourceUtil.containsUnquoted(startLine, " final ");
		boolean isInterface = SourceUtil.containsUnquoted(
			startLine, " interface ");
		boolean isStatic = SourceUtil.containsUnquoted(startLine, " static ");

		int y = startLine.indexOf(CharPool.EQUAL);

		if (SourceUtil.containsUnquoted(startLine, " @interface ") ||
			SourceUtil.containsUnquoted(startLine, " class ") ||
			SourceUtil.containsUnquoted(startLine, " enum ") ||
			SourceUtil.containsUnquoted(startLine, " interface ")) {

			JavaClass javaClass = _parseJavaClass(
				accessModifier, false, javaTermContent, lineNumber,
				_getClassName(startLine), importNames, isAbstract, isEnum,
				isFinal, isInterface, isStatic, false, false, packageName,
				false);

			Pattern pattern = Pattern.compile(
				StringBundler.concat(
					"\\s(class|enum|interface)\\s+", javaClass.getName(),
					"([<|\\s][^\\{]*)\\{"));

			Matcher matcher2 = pattern.matcher(javaTermContent);

			if (matcher2.find()) {
				javaClass = _parseExtendsImplementsPermits(
					javaClass, matcher2.group(2));
			}

			return javaClass;
		}

		if (((y > 0) && ((x == -1) || (x > y))) ||
			(startLine.endsWith(StringPool.SEMICOLON) && (x == -1))) {

			return new JavaVariable(
				accessModifier, javaTermContent, isAbstract, isFinal, isStatic,
				lineNumber, _getVariableName(startLine));
		}

		int spaceCount = StringUtil.count(startLine, CharPool.SPACE);

		if (x == -1) {
			if (!accessModifier.equals(JavaTerm.ACCESS_MODIFIER_PUBLIC) ||
				(spaceCount != 2)) {

				return null;
			}

			return new JavaConstructor(
				accessModifier, javaTermContent, isAbstract, isFinal, isStatic,
				lineNumber, _getConstructorOrMethodName(startLine));
		}

		if (isStatic || (spaceCount > 1) ||
			(accessModifier.equals(JavaTerm.ACCESS_MODIFIER_DEFAULT) &&
			 (spaceCount > 0))) {

			return new JavaMethod(
				accessModifier, javaTermContent, isAbstract, isFinal, isStatic,
				lineNumber, _getConstructorOrMethodName(startLine));
		}

		if ((spaceCount == 1) ||
			(accessModifier.equals(JavaTerm.ACCESS_MODIFIER_DEFAULT) &&
			 (spaceCount == 0))) {

			return new JavaConstructor(
				accessModifier, javaTermContent, isAbstract, isFinal, isStatic,
				lineNumber, _getConstructorOrMethodName(startLine));
		}

		return null;
	}

	private static int _getJavaTermEndLineNumber(
		String classContent, int lineNumber) {

		int x = SourceUtil.getLineStartPos(classContent, lineNumber);

		String s = classContent.substring(x);

		Matcher matcher = _javaTermEndPattern.matcher(s);

		while (matcher.find()) {
			String javaTermContent = s.substring(0, matcher.end());

			if ((ToolsUtil.getLevel(javaTermContent, "(", ")") == 0) &&
				(ToolsUtil.getLevel(javaTermContent, "{", "}") == 0)) {

				return lineNumber + StringUtil.count(javaTermContent, "\n") - 1;
			}
		}

		return -1;
	}

	private static int _getMatchingEndLineNumber(
		String classContent, int lineNumber, String increaseLevelString,
		String decreaseLevelString) {

		int level = 0;

		while (true) {
			level += ToolsUtil.getLevel(
				SourceUtil.getLine(classContent, lineNumber),
				increaseLevelString, decreaseLevelString);

			if (level == 0) {
				return lineNumber;
			}

			lineNumber++;
		}
	}

	private static String _getVariableName(String line) {
		int x = line.indexOf(CharPool.EQUAL);
		int y = line.lastIndexOf(CharPool.SPACE);

		if (x != -1) {
			line = line.substring(0, x);
			line = StringUtil.trim(line);

			y = line.lastIndexOf(CharPool.SPACE);

			return line.substring(y + 1);
		}

		if (line.endsWith(StringPool.SEMICOLON)) {
			return line.substring(y + 1, line.length() - 1);
		}

		return StringPool.BLANK;
	}

	private static JavaClass _parseExtendsImplementsPermits(
			JavaClass javaClass, String s)
		throws ParseException {

		if (ToolsUtil.getLevel(s, "<", ">") != 0) {
			throw new ParseException("Parsing error around class declaration");
		}

		outerLoop:
		while (true) {
			int x = s.indexOf("<");

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

			int y = x;

			while (true) {
				y = s.indexOf(">", y + 1);

				if (ToolsUtil.getLevel(s.substring(x, y + 1), "<", ">") == 0) {
					s = StringUtil.trim(s.substring(0, x) + s.substring(y + 1));

					continue outerLoop;
				}
			}
		}

		Matcher matcher = _permitsPattern.matcher(s);

		if (matcher.find()) {
			javaClass.addPermittedClassNames(
				StringUtil.split(s.substring(matcher.end())));

			s = s.substring(0, matcher.start());
		}

		s = StringUtil.trim(s);

		matcher = _implementsPattern.matcher(s);

		if (matcher.find()) {
			javaClass.addImplementedClassNames(
				StringUtil.split(s.substring(matcher.end())));

			s = s.substring(0, matcher.start());
		}

		s = StringUtil.trim(s);

		if (s.startsWith("extends")) {
			javaClass.addExtendedClassNames(StringUtil.split(s.substring(7)));
		}

		return javaClass;
	}

	private static JavaClass _parseJavaClass(
			String accessModifier, boolean anonymous, String classContent,
			int classLineNumber, String className, List importNames,
			boolean isAbstract, boolean isEnum, boolean isFinal,
			boolean isInterface, boolean isStatic, boolean isStrictfp,
			boolean nonsealed, String packageName, boolean sealed)
		throws IOException, ParseException {

		JavaClass javaClass = new JavaClass(
			accessModifier, anonymous, classContent, importNames, isAbstract,
			isFinal, isInterface, isStatic, isStrictfp, classLineNumber,
			className, nonsealed, packageName, sealed);

		int lineNumber = 0;

		int annotationLevel = 0;
		int level = 0;

		if (classContent.startsWith("/*")) {
			while (true) {
				String line = SourceUtil.getLine(classContent, ++lineNumber);

				if (line.endsWith("*/")) {
					break;
				}
			}
		}

		while (true) {
			String line = SourceUtil.getLine(classContent, ++lineNumber);

			annotationLevel += ToolsUtil.getLevel(line);
			level += ToolsUtil.getLevel(line, "{", "}");

			if ((annotationLevel == 0) && (level != 0)) {
				break;
			}
		}

		if (isEnum) {
			while (true) {
				String line = StringUtil.trim(
					SourceUtil.getLine(classContent, ++lineNumber));

				level += ToolsUtil.getLevel(line, "{", "}");

				if (level == 0) {
					return javaClass;
				}

				if (line.endsWith(";") && (level == 1)) {
					break;
				}
			}
		}

		int javaTermLineNumber = -1;

		while (true) {
			String line = StringUtil.trim(
				SourceUtil.getLine(classContent, ++lineNumber));

			if ((line == null) || line.equals("}")) {
				return javaClass;
			}

			if (line.equals(StringPool.BLANK) || line.startsWith("//")) {
				continue;
			}

			if (line.equals("/*") || line.matches("/\\*[^*].*")) {
				lineNumber = _getCommentEndLineNumber(classContent, lineNumber);

				continue;
			}

			if (line.equals("{")) {
				lineNumber = _getMatchingEndLineNumber(
					classContent, lineNumber, "{", "}");

				continue;
			}

			if (javaTermLineNumber == -1) {
				javaTermLineNumber = lineNumber;
			}

			if (line.startsWith("@")) {
				lineNumber = _getMatchingEndLineNumber(
					classContent, lineNumber, "(", ")");
			}
			else if (line.startsWith("/**")) {
				lineNumber = _getCommentEndLineNumber(classContent, lineNumber);
			}
			else {
				int x = SourceUtil.getLineStartPos(
					classContent, javaTermLineNumber);
				int y = SourceUtil.getLineStartPos(classContent, lineNumber);

				String metadata = classContent.substring(x, y);

				int javaTermEndLineNumber = _getJavaTermEndLineNumber(
					classContent, lineNumber);

				if (javaTermEndLineNumber == -1) {
					throw new ParseException(
						"Parsing error at line \"" + StringUtil.trim(line) +
							"\"");
				}

				int z = SourceUtil.getLineStartPos(
					classContent, javaTermEndLineNumber + 1);

				String javaTermContent = classContent.substring(y, z);

				JavaTerm javaTerm = _getJavaTerm(
					packageName, importNames, metadata, javaTermContent,
					classLineNumber + javaTermLineNumber - 1);

				if (javaTerm == null) {
					throw new ParseException(
						"Parsing error at line \"" + StringUtil.trim(line) +
							"\"");
				}

				javaClass.addChildJavaTerm(javaTerm);

				javaTermLineNumber = -1;
				lineNumber = javaTermEndLineNumber;
			}
		}
	}

	private static final Pattern _anonymousClassPattern = Pattern.compile(
		"\\snew [\\w\\.\t\n]+(\\(|\\<)");
	private static final Pattern _implementsPattern = Pattern.compile(
		"(\\A|\\s)implements\\s");
	private static final Pattern _javaTermEndPattern = Pattern.compile(
		"[;}]\\s*?\n");
	private static final Pattern _javaTermStartLinePattern = Pattern.compile(
		".*?[{;]\\s*?\n", Pattern.DOTALL);
	private static final Pattern _permitsPattern = Pattern.compile(
		"(\\A|\\s)permits\\s");

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy