AnnotationUtils.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
 *******************************************************************************/
package org.sandbox.jdt.internal.corext.util;

import java.util.List;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.Modifier;

/**
 * Utility class for annotation and modifier operations on AST declarations.
 * Provides methods for checking, finding, and validating annotations and modifiers
 * commonly needed during code cleanup and migration.
 * 
 * <p>This class operates on {@link BodyDeclaration#modifiers()}, which contains
 * both annotations and Java modifiers. Methods are organized into two sections:</p>
 * <ul>
 * <li><b>Annotation Operations</b> — checking, finding, and creating annotations</li>
 * <li><b>Modifier Operations</b> — checking for specific modifiers (static, final, etc.)</li>
 * </ul>
 */
public final class AnnotationUtils {

	private AnnotationUtils() {
		// Utility class - prevent instantiation
	}

	// ========== Annotation Operations ==========

	/**
	 * Creates a new MarkerAnnotation AST node with the given annotation name.
	 * This centralizes the common pattern of creating a marker annotation and setting its type name.
	 *
	 * @param ast the AST to create the annotation in
	 * @param annotationName the simple or qualified name of the annotation (e.g., "Test", "BeforeEach")
	 * @return the created MarkerAnnotation
	 */
	public static MarkerAnnotation createMarkerAnnotation(AST ast, String annotationName) {
		MarkerAnnotation annotation= ast.newMarkerAnnotation();
		annotation.setTypeName(ast.newName(annotationName));
		return annotation;
	}

	/**
	 * Checks if a field has the specified annotation.
	 * 
	 * @param field the field declaration to check
	 * @param annotationClass the fully qualified annotation class name
	 * @return true if the field has the annotation
	 */
	public static boolean isFieldAnnotatedWith(FieldDeclaration field, String annotationClass) {
		return hasAnnotation(field.modifiers(), annotationClass);
	}

	/**
	 * Checks if a body declaration has the specified annotation.
	 * 
	 * @param declaration the body declaration to check
	 * @param annotationClass the fully qualified annotation class name
	 * @return true if the declaration has the annotation
	 */
	public static boolean isAnnotatedWith(BodyDeclaration declaration, String annotationClass) {
		return hasAnnotation(declaration.modifiers(), annotationClass);
	}

	/**
	 * Checks if a list of modifiers contains an annotation with the specified qualified name.
	 * 
	 * @param modifiers the list of modifiers to check
	 * @param annotationClass the fully qualified annotation class name
	 * @return true if the annotation is present
	 */
	public static boolean hasAnnotation(List<?> modifiers, String annotationClass) {
		return modifiers.stream()
				.filter(Annotation.class::isInstance)
				.map(Annotation.class::cast)
				.map(Annotation::resolveTypeBinding)
				.anyMatch(binding -> binding != null && annotationClass.equals(binding.getQualifiedName()));
	}

	/**
	 * Checks if the given modifiers list contains an annotation with the specified simple name.
	 * 
	 * @param modifiers the list of modifiers
	 * @param annotationSimpleName the simple name of the annotation
	 * @return true if the annotation is present
	 */
	public static boolean hasAnnotationBySimpleName(List<?> modifiers, String annotationSimpleName) {
		return modifiers.stream()
				.anyMatch(modifier -> modifier instanceof Annotation && ((Annotation) modifier).getTypeName()
						.getFullyQualifiedName().equals(annotationSimpleName));
	}

	// ========== Modifier Operations ==========

	/**
	 * Checks if a field has the static modifier.
	 * 
	 * @param field the field declaration to check
	 * @return true if the field is static
	 */
	public static boolean isFieldStatic(FieldDeclaration field) {
		return hasModifier(field.modifiers(), Modifier.ModifierKeyword.STATIC_KEYWORD);
	}

	/**
	 * Checks if a list of modifiers contains the specified modifier keyword.
	 * 
	 * @param modifiers the list of modifiers
	 * @param keyword the modifier keyword to check for
	 * @return true if the modifier is present
	 */
	public static boolean hasModifier(List<?> modifiers, Modifier.ModifierKeyword keyword) {
		return modifiers.stream()
				.filter(Modifier.class::isInstance)
				.map(Modifier.class::cast)
				.anyMatch(modifier -> modifier.getKeyword().equals(keyword));
	}
}