ExampleHintProvider.java

/*******************************************************************************
 * Copyright (c) 2025, 2026 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.examples;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.correction.ASTRewriteCorrectionProposal;
import org.sandbox.jdt.triggerpattern.api.Hint;
import org.sandbox.jdt.triggerpattern.api.PatternKind;
import org.sandbox.jdt.triggerpattern.api.TriggerPattern;
import org.sandbox.jdt.triggerpattern.eclipse.HintContext;

/**
 * A small, behaviour-preserving annotation-based hint.
 *
 * <p>Double negation of a primitive boolean preserves its value, evaluation
 * count and exceptions. A boxed Boolean is deliberately excluded: replacing
 * {@code !!boxed} by {@code boxed} could remove unboxing and its null check.</p>
 *
 * @since 1.2.2
 */
public class ExampleHintProvider {

	/** Removes double negation only when the bound expression is a resolved primitive boolean. */
	@TriggerPattern(value= "!!$x", kind= PatternKind.EXPRESSION) //$NON-NLS-1$
	@Hint(displayName= "Remove boolean double negation", //$NON-NLS-1$
			description= "Replaces !!expression with a primitive boolean expression evaluated once") //$NON-NLS-1$
	public static IJavaCompletionProposal simplifyDoubleNegation(HintContext context) {
		ASTNode matchedNode= context.getMatch().getMatchedNode();
		ASTNode boundNode= context.getMatch().getBinding("$x"); //$NON-NLS-1$
		if (!(matchedNode instanceof PrefixExpression outer)
				|| outer.getOperator() != PrefixExpression.Operator.NOT
				|| !(outer.getOperand() instanceof PrefixExpression inner)
				|| inner.getOperator() != PrefixExpression.Operator.NOT
				|| !(boundNode instanceof Expression expression)
				|| inner.getOperand() != expression) {
			return null;
		}
		ITypeBinding type= expression.resolveTypeBinding();
		if (type == null || type.isRecovered() || !type.isPrimitive() || !"boolean".equals(type.getName())) { //$NON-NLS-1$
			return null;
		}
		for (Object value : context.getCompilationUnit().getCommentList()) {
			ASTNode comment= (ASTNode) value;
			int start= comment.getStartPosition();
			int end= start + comment.getLength();
			if (start >= outer.getStartPosition() && end <= outer.getStartPosition() + outer.getLength()
					&& !(start >= expression.getStartPosition() && end <= expression.getStartPosition() + expression.getLength())) {
				// Copying only the operand would discard a comment between the operators.
				return null;
			}
		}
		Expression replacement= (Expression) context.getASTRewrite().createCopyTarget(expression);
		if (outer.getParent() instanceof LambdaExpression) {
			// A method invocation alone would also make the lambda void-compatible.
			ParenthesizedExpression parenthesized= outer.getAST().newParenthesizedExpression();
			parenthesized.setExpression(replacement);
			replacement= parenthesized;
		}
		context.getASTRewrite().replace(outer, replacement, null);
		return new ASTRewriteCorrectionProposal("Remove boolean double negation", //$NON-NLS-1$
				context.getICompilationUnit(), context.getASTRewrite(), 10, null);
	}
}