TriggerPatternEngine.java

/*******************************************************************************
 * Copyright (c) 2025 Carsten Hammer.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Carsten Hammer - initial API and implementation
 *******************************************************************************/
package org.sandbox.jdt.triggerpattern.api;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Statement;
import org.sandbox.jdt.triggerpattern.internal.PatternParser;
import org.sandbox.jdt.triggerpattern.internal.PlaceholderAstMatcher;

/**
 * Engine for finding pattern matches in Java code.
 * 
 * <p>This engine traverses a compilation unit and identifies all occurrences
 * that match a given pattern. It supports both expression and statement patterns
 * with placeholder binding.</p>
 * 
 * @since 1.2.2
 */
public class TriggerPatternEngine {
	
	private final PatternParser parser = new PatternParser();
	
	/**
	 * Finds all matches of the given pattern in the compilation unit.
	 * 
	 * @param cu the compilation unit to search
	 * @param pattern the pattern to match
	 * @return a list of all matches (may be empty)
	 */
	public List<Match> findMatches(CompilationUnit cu, Pattern pattern) {
		if (cu == null || pattern == null) {
			return List.of();
		}
		
		ASTNode patternNode = parser.parse(pattern);
		if (patternNode == null) {
			return List.of();
		}
		
		List<Match> matches = new ArrayList<>();
		
		cu.accept(new ASTVisitor() {
			@Override
			public void preVisit(ASTNode node) {
				// Only check nodes of compatible types
				if (pattern.getKind() == PatternKind.EXPRESSION && node instanceof Expression) {
					checkMatch(node, patternNode, matches);
				} else if (pattern.getKind() == PatternKind.STATEMENT && node instanceof Statement) {
					checkMatch(node, patternNode, matches);
				} else if (pattern.getKind() == PatternKind.ANNOTATION && node instanceof Annotation) {
					checkMatch(node, patternNode, matches);
				} else if (pattern.getKind() == PatternKind.METHOD_CALL && node instanceof MethodInvocation) {
					checkMatch(node, patternNode, matches);
				} else if (pattern.getKind() == PatternKind.IMPORT && node instanceof ImportDeclaration) {
					checkMatch(node, patternNode, matches);
				} else if (pattern.getKind() == PatternKind.FIELD && node instanceof FieldDeclaration) {
					checkMatch(node, patternNode, matches);
				}
			}
		});
		
		return matches;
	}
	
	/**
	 * Finds all matches of the given pattern in the compilation unit.
	 * 
	 * @param icu the ICompilationUnit to search
	 * @param pattern the pattern to match
	 * @return a list of all matches (may be empty)
	 */
	public List<Match> findMatches(ICompilationUnit icu, Pattern pattern) {
		if (icu == null || pattern == null) {
			return List.of();
		}
		
		ASTParser astParser = ASTParser.newParser(AST.getJLSLatest());
		astParser.setSource(icu);
		astParser.setResolveBindings(false);
		CompilationUnit cu = (CompilationUnit) astParser.createAST(null);
		
		return findMatches(cu, pattern);
	}
	
	/**
	 * Checks if a candidate node matches the pattern node and adds a Match if it does.
	 */
	private void checkMatch(ASTNode candidate, ASTNode patternNode, List<Match> matches) {
		PlaceholderAstMatcher matcher = new PlaceholderAstMatcher();
		
		if (patternNode.subtreeMatch(matcher, candidate)) {
			// We have a match! Create a Match object with bindings and position
			int offset = candidate.getStartPosition();
			int length = candidate.getLength();
			
			Match match = new Match(candidate, matcher.getBindings(), offset, length);
			matches.add(match);
		}
	}
}